Sorry to bump this post, but the topic never was solved, and I’ve since found a solution to the problem. I thought this might be helpful for anyone who stumbles over this topic later
Initially, I thought the issue was with the time-of-arrival equation, (distance/speed)
. This was not the case. The actual problem was with the slight inconsistencies with wait()
not actually waiting the precise time, going over or under by a few decimals. Another problem was with lag causing the projectile to register a hit before the visual part/tracer actually reached it. This was my solution:
I’ve heard that TweenService
is handled mostly on the C++ side of things, and I have found that the numbertime of how long it takes to complete does not suffer inconsistencies like wait()
does, and so the Tween.Completed
event will fire a precisely the right moment.
So what I’m doing for my projectile now is this:
- cast a ray to wherever you are aiming.
- get the LookVector of where your shot is facing, and the Magnitude or distance between the Ray.Origin and the position where it hit.
- get our arrival time using (distance/speed) (speed is given in studs per second, to mimic physics velocity, even though the part is anchored).
- create a tween on our tracer (a part or a placeholder containing a CylinderHandleAdornment) with the easing style being Linear, and number time being our arrival time equation, distance/speed.
- call
Tween:Play()
, and listen for the Tween.Completed
event. Note that we aren’t listening for the .Touched event of the tracer, nor are we casting a ray each frame (both are less efficient and less reliable.)
- Once tween is completed, we cast a second ray. This ray will handle the actual hit registration part of the projectile, as the initial ray was just to calculate the End Point.
- deal out damage from the ray we just cast, add any trivial effects such as bulletholes, impact particles, etc. Then we destroy the tracer and the tween, disconnecting all connections.
Advantages to this method:
- can be used with any speed, don’t have to worry about the bullet moving to fast to register.
- Extremely efficient, only uses two rays and a tween. (Other methods might involve manually lerping the bullet each frame, casting hundreds of rays.)
- No inconsistencies with arrival time!!!
- Uses raycasting so it is a bit easier to work with security-wise.
- Can use a plethora of Physical Objects, not just parts, since it doesn’t rely on the
.Touched
event.
Disadvantages
- cannot detect if a player moves into the path of the bullet before the bullet hits the End Point, however this can easily be circumvented by casting a few rays at different points of travel.
- if the thing that the initial placement ray had hit moves away, the bullet will still go to that destination.
This method can be implemented on the server without that nasty projectile lag, or it can be rendered locally on each client.
Here’s a code example:
local StartPos = Vector3.new(0,100,0)
local EndPos = Vector3.new(0,50,50)
local Speed = 50
local function fire()
local ray = Ray.new(StartPos, (EndPos-StartPos).Unit*500)
local _, position = workspace:FindPartOnRay(ray)
local Distance = (ray.Origin - position).Magnitude
local ArrivalTime = (Distance/Speed)
local Tracer = Instance.new("Part")
Tracer.Material = Enum.Material.Neon
Tracer.Anchored = true
Tracer.CanCollide = false
Tracer.Size = Vector3.new(0.3,0.3,Distance)
Tracer.CFrame = CFrame.new(ray.Origin, position)
Tracer.Position = StartPos
local Tween = game:GetService("TweenService"):Create(Tracer, TweenInfo.new(ArrivalTime), {Position = EndPos})
Tween:Play()
Tween.Completed:connect(function()
Tracer:Destroy()
local ray2 = Ray.new(StartPos, (EndPos - StartPos).Unit*500)
local hit, Position = workspace:FindPartOnRay(ray2) -- hitreg ray
-- find the player you hit, deal out damage, add bulletholes, etc.
end)
end
So yeah this was a really long post but I had fun messing around with this method, maybe ill make a post in #learning-resources, who knows
Edit:
Heres a video of it in action