I’m using FindPartOnRay for a projectile, and the ray is of course the same as the trajectory of the projectile, but how do I check when the projectile is touching the part from FindPartOnRay (if it’s not nil)? Do I just check the projectile’s position every heartbeat or instead take the unit-ray (of the trajectory) instead and then if there’s a part using FindPartOnRay that means it touched since it’s just one?
I’m gonna list some possible approaches from the top of my head, the choice is yours.
-
Use
:GetTouchingParts()
on the projectile every heartbeat (wouldn’t recommend) -
Use the
.Touched
event on the projectile and check if theBasePart
that the projectile hit is the sameBasePart
from:FindPartOnRay()
. The variable passed through the.Touched
event is theBasePart
that the projectile hit, by the way.
That is a horrible idea. The projectile can just fly through a part in case of lag, meaning no collision for the gameplay.
If your projectile’s physics are scripted then:
local ignpart
local whlist = {ignpart}
local pos0 = Vector3.new()
local pos1 = Vector3.new()
local acc = Vector3.new(0,-9.81*2.5,0)
local con;con = game:GetService("RunService").Heartbeat:Connect(function(dt)
pos1,pos0 = 2*pos1 - pos0 + acc * dt*dt, pos1
local ray = Ray.new(pos0,pos1-pos0)
if not workspace:FindPartOnRay(ray,ignpart) then
local P,p,n = workspace:FindPartOnRayWithWhitelist(ray,whlist)
if P then
--stuff
end
end
end)
Otherwise, you could replace pos0 with the old position of the projectile part and pos1 with its current.
Run it on the client to minimise that
Still a horrible practice. Also, clients are more prone to unstable frame rate.
I’m using BodyVelocity so how do I know what the acceleration is?
Idk if there’s an inbuilt function, but:
Acceleration = Change in velocity / Change in time
- Record the position of the projectile.
- Record the position again, lets say after 0.25 seconds.
- Work out the magnitude between the two vectors
(Vector1 - Vector2).magnitude
- Divide the magnitude by the time elapsed (0.25 seconds) to work out the acceleration (studs per second squared)
Then we are talking about the second mentioned case. You are moving the projectile with BodyMovers, not a function.
local ignlist = {ignpart,projectile}
local whlist = {ignpart}
local pos0 = projectile.Position
local con;con = game:GetService("RunService").Heartbeat:Connect(function(dt)
if not projectile then con:Disconnect() return end
local pos1 = projectile.Position
local ray = Ray.new(pos0,pos1-pos0)
if not workspace:FindPartOnRayWithIgnoreList(ray,ignlist) then
local P,p,n = workspace:FindPartOnRayWithWhitelist(ray,whlist)
if P then
--stuff
end
end
pos0 = pos1
end)
Why would you whitelist “ignpart”?
What I do in this case is every heartbeat fire a ray from the bullet’s last position to its current position, and handle it on the client for smoother and less delayed shots.
Because that’s the piece that detects if the projectile is inside the part you wanted ignored.
Oh I was over complicating that lol. Also, do you know if just CFraming the projectile would cause less lag than using a BodyMover?
Not really less lag, but it will make it more stable as BodyMovers will freak out at high velocities, while custom physics only react the way you want them to.
I feel like CFraming would be easier and leave less room for error
Custom physics are CFraming. The issues with BodyMovers is that, while they are perfect for stuff like vehicle simulation, they don’t work well with anything that could potentially reach 1000 studs/ second.
Okay how do I make an object move at a certain speed given it’ll move every heartbeat?
Maybe just every heartbeat move it Speed*HeartbeatTime
?
EDIT: Okay so I did this and it seems to work great. All you have to do is something like
local Force = Physics:CreateForce("Direction")
Force:Activate(Object, Vector3.new(), Vector3.new(), 100)
and now I can start to code other forces
so thanks for all your help!
Code
function Physics.Forces.Direction()
local Force = {}
Force.Active = false
function Force:Activate(Object, Direction, Origin, Speed) -- Instance:Object, Vector3:Direction, Vector3:Origin, Number:Speed
if (Force.Active == true) then return end
if (Object.Parent == nil) and (self.Object == nil) then error("No object present to activate force.") end
self.Active = true
self.Object = Object
self.Direction = Direction
self.Origin = Origin
self.Speed = Speed
while (self.Active == true) do
local Interval = RunService.Heartbeat:Wait()
local Move = CFrame.new(0, 0, -(Speed*Interval))
Object.CFrame = Object.CFrame * Move
end
end
function Force:Deactivate()
if (Force.Active == false) then return end
self.Active = false
end
return Force
end
-----
function Physics:Create(forceName) -- String:forceName
local Force = Physics.Forces[forceName]
if (Force) then
return Force()
end
end
For your case, the Euler integration you are using (velocities) will work pretty well; however, if you are working with bodies with variable forces, you should try checking Verlet integration (what I used in the no-part example). It is much less prone to imprecisions as it avoids using velocities all together by comparing positions.
An image that compares them in a harmonic oscillation. The blue line is Euler (velocity), Verlet is red (positions) and black represents the real-life value.
What do you mean by variable forces? Aren’t all BodyMovers on here constant?
If you look at the graph I’ve shown, you’ll see that the force acts downwards when the line is above 0 and upwards when the line is below. Verlet is much better at energy conservation, while Euler builds up the energy and releases it whenever the forces change (the line above is higher and has a shorter period than the lower).