Making a Lead Shot Indicator?

When shooting with moving projectiles, one thing to account for is travel speed. If your bullet takes x seconds to reach a target, and your target is moving, you have a problem; Aiming directly at your moving target won’t work because by the time your bullet reaches the destination the target will have moved out of place, and your shot misses. This is why you have to aim ahead of the target to account for travel time, and we see this in most projectile-based shooters.

But even more so in dogfights/space fights, because targets move at incredibly high speeds.

That’s why some dogfight games introduce a Lead Shot Indicator: a target that shows you how far ahead to shoot.

Here’s one in War Thunder:
Capture

I also saw that @Widgeon implemented one in Hostile Skies.

My question is, how can I implement this into my game, what is the formula? How can I account for distance?

3 Likes

You could use WorldToScreenPoint but you would need to calculate the offset from the current target position.

At a guess it would be something like the time it takes for the shot to reach the target at the current distance scaled to the targets current speed (look vector).

I hope this helps.

I plan on using BillboardGuis, but if I ever do switch I’d use this method.

I feel like it would be more complex / nuanced than this. I’m definitely going to try this, though.

If you define the target’s position as vector T, target velocity as vector V, the projectile velocity s, collision time t and the bullet is fired from origin, then you can derive the kinematic equation:

st = T_init + Vt

You can then square both sides and rearrange to get to equation (s^2 - V^2)*t^2 - 2(T_init * V)*t - T_init^2 = 0

You can then solve for shot impact time t. With the impact time, you can use the first equation to solve for shot velocity s. Normalize the velocity vector to get the shot direction, which can then be used for your lead direction. This assumes all velocities are constant.

1 Like

What if I take travel time of projectile (distance/projectile speed) then use that arrival time with the target’s velocity to get

projectedposition = target.Position + target.CFrame.LookVector * (targetvelocity*projectilearrivaltime)

edit: Tried this, and it doesn’t seem to account for any other angle than 0


It only hit the target once, when there was a 0* angle.

Perhaps I should shift the shot over by the angular difference between origin-target?

How are you calculating projectilearrivaltime?

local distance = (origin-targetposition).Magnitude

local arrivaltime = (distance/projectilespeed)

I should probably be accounting for target speed as well, no?

This is not an accurate way of calculating the impact time. This will only hold true if the target is stationary. However, your target is moving. To properly calculate the impact time, you need to use the kinematic equation and solve the quadratic for impact time t.

edit: sircfenner made a pretty good explanation of how to do this

There are two stages to solving this problem:

  1. Calculate the required launch direction of the projectile to hit the moving target
  2. Compute a screen coordinate that corresponds to firing in this direction

You can use standard equations of motion to approach the first stage:

f = the projectile’s speed (number)
s = the target’s current displacement (vector: start position to target position)
m = the target’s velocity (vector: assumes this stays constant)
v = our launch velocity (vector: we are going to calculate this)
t = time (number: we are going to calculate this - it will equal the time taken for the projectile to hit)

(1) vt = s + mt

This means that after time t, moving at velocity v, the projectile will have reached the point where the target is at that time (this ensures we have a collision).

(2) vx^2 + vy^2 + vz^2 = f^2

This means that the squared length of the velocity vector must be equal to the square of the projectile’s speed. You may have noticed that we are applying the Pythagorean Theorem here.

We want to solve for t, and to do that we need to eliminate the unknown v. We can do this by first squaring equation (1). Note that squaring a vector here can be considered the same as taking a dot product (•) of the vector with itself.

(3) (v•v)t^2 = (s + mt)^2
(3) (v•v)t^2 = s•s + 2t(m•s) + (m•m)t^2

We can also rewrite equation (2) in dot product form (see the algebraic definition of the dot product).

(4) v•v = f^2

Now we can substitute equation (4) into equation (3).

(5) f^2t^2 = s•s + 2t(m•s) + (m•m)t^2

Now we can rearrange this to express it in terms of t (this is what we are solving for).

(5) t^2(f^2 - m•m) - t(2 * m•s) - s•s = 0

Expressed in this form, we can apply the Quadratic Formula to solve for t.

a = f^2 - m•m
b = -2 * m•s
c = -s•s

A preliminary step is to calculate the discriminant and check its value to determine if a solution exists.

d = b^2 - 4*a*c

If d is less than zero, there are no solutions - this means it’s impossible to hit the moving target.

We can now solve for two values of t.

t0 = (-b + sqrt(d)) / (2 * a)
t1 = (-b - sqrt(d)) / (2 * a)

If both values are less than zero, then there are no solutions. If one value is less than zero and the other is not, choose the one which is greater than zero. If both values are greater than zero, then there are two possible launch directions. You can decide which to use / display.

Having calculated t, we can plug this back into (1) to solve for the launch velocity, v.

(6) v = (s + mt) / t
(6) v = s / t + m

The launch direction is simply the normalized v vector (in Roblox Lua, you can get this with v.unit).


I’ll leave the second problem for you or someone else to solve.

Hopefully I understood your question correctly and haven’t made any mistakes in my explanation - please let me know if I have (or if there is a much better way to do this)!

19 Likes

Wow that is a lot to chew on! This will take me a while, I’ll get back to you when I can. Thanks for writing this.

No need to do that, I plan on using 3D gui object to represent the projected position. Regardless you could just calculate the position using the first problem and use camera:WorldToViewportPoint() for an easy conversion.

1 Like