[SOLVED] Calculating time of arrival

I’ve been experimenting with methods of projectile replication and projectile hit detection, and I thought of one method that uses a projectile’s speed (velocity) and the distance between the start point and endpoint (in magnitude) to calculate a time of arrival. This yields similar behavior to a touched event except that it doesn’t rely on any actual collision, only on distance and time. The code i’m currently using is this:

local speed = 100
local function launch(position)
local distance = (script.Parent.Position - position).Magnitude
wait(1)
script.Parent.Anchored = false
script.Parent.CFrame = CFrame.new(script.Parent.Position, position)
script.Parent.BodyVelocity.Velocity = script.Parent.CFrame.LookVector * speed
print("launch")
wait(distance/speed) -- divides the distance by a studs per second speed (giving a proper wait time, I think)
print("hit target")
end

For some reason the projectile always either overshoots/undershoots the target by a small margin (~1 stud). Off the top of my head I wonder if it may have something to do with latency or some acceleration wait time that is messing up the shot, but I haven’t been able to confirm either of these, and I’m not even sure if this is a viable method.

I have tried removing any acceleration time from the equation, but that didn’t work. It’s a very short script and there aren’t any clear fixes or solutions that I could find.

A bit of extra information: I am using BodyVelocity to move the projectile (a part) and the part’s CanCollide is false so that it isn’t colliding with anything.

please don’t suggest any alternative projectile methods, that’s not what the post is for

4 Likes

The small discrepancy is probably due to wait() not waiting the precise amount of time.
wait() will wait at least the amount of time and then resume as soon as possible after that.

If you time how long “wait()” runs for, you’ll see it will be around 1/30th of a second. (This may be 1/60th now, I haven’t checked in a while).
With that extra split second of timing the part travels the extra distance.

1 Like

That makes sense. Could you tell me how might I correct for that?

You could try connecting to RunService.Stepped and checking if the time has passed.

Something like:

local EndTime = tick() + 12
game:GetService("RunService").Stepped:Connect(function()
    if tick() >= EndTime then
        print("It just hit")
    end
end)

This will run at approximately 60 times per second.

1 Like

Wait returns the amount of time it actually waited for

1 Like

What is 12? distance/speed?

Regardless I tried it and it hit 44 studs off target. i’m probably implementing it wrong

I tried this:

local t = wait(1)
print(t)

and it printed around 0.1-0.2 seconds inaccuracy. Thanks for the tip!

1 Like

12 is supposed to be the number of seconds before the projectile lands.

I feel like I should be adding more but that’s all I wanted to say

Okay. I tried it and it had even worse results, so I am completely lost.

Well, the way I would implement it is this, without a BodyVelocity:

local speed = 100
local function Launch(position)
    local distance = (script.Parent.Position - position).Magnitude
    --wait(1)
    script.Parent.Anchored = true 

    local i = 0
    local span = distance/speed -- d / (d/t) = t

    local start = CFrame.lookAt(script.Parent.Position, position)

    local lastParent = script.Parent.Parent
    local m = Instance.new("Model")
    script.Parent.Parent = m
    m.PrimaryPart = script.Parent
    print("launch")
    m:SetPrimaryPartCFrame(start)
    while i < span do
        i = math.clamp(i + RunService.Stepped:Wait(), 0, 1)
        m:MoveTo(start.Position:Lerp(position, i/span))
    end
    print("hit target")
    script.Parent.Parent = lastParent
    m:Destroy()
end

What I did was use the Vector3:Lerp method to move the part from its start point to the destination instead of the BodyVelocity doing it, which means that everything is controlled from one script instead of forcing two separate systems (that being the physics engine and the Lua script) to cooperate.

I hope this helps…

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 :wink:

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 :man_shrugging:

Edit:

Heres a video of it in action

36 Likes