Predict projectile ballistics, including gravity and motion

I don’t think anyone here would want to learn how it works, and I don’t blame you. I plagiarized all of this from the internet and somehow botched it into a working package so that you don’t have to.

What is this?

An actual, accurate projectile ballistic trajectory calculator. If you’ve ever played WarThunder, you should be familiar with this (the little reticle that leads ahead of an enemy craft? That’s exactly what this is.)
image

What can this be used for?

If you’re designing a war-themed game with things like guns, artilleries, and batteries, you’ll find this especially useful. It can be used for, as just mentioned, a little aiming reticle for your players to aim at to make the game more engaging and predictable.

Some other examples of use:

  • Bullseye artillery
  • CIWS guns
  • NPC battleships
  • Automatic defense turrets
  • Aimbot :joy:


I don’t really think a dedicated module was necessary for this because of how trivially easy it is to use. But I made it anyways.


CalculateTrajectory.rbxm (3.5 KB)

Module

See Credits for the original source code of the quartic module

local module = {}

local quartic = require(script.CardanoFerrari).solveQuartic

function module.SolveTrajectory(
	
	origin: Vector3,
	projectileSpeed: number,
	targetPos: Vector3,
	targetVelocity: Vector3,
	pickLongest: boolean?,
	gravity: number?
	
	): Vector3?
	
	local g: number = gravity or workspace.Gravity

	local disp: Vector3 = targetPos - origin
	local p, q, r: number = targetVelocity.X, targetVelocity.Y, targetVelocity.Z
	local h, j, k: number = disp.X, disp.Y, disp.Z
	local l: number = -.5 * g 

	local solutions = quartic(
		l*l,
		-2*q*l,
		q*q - 2*j*l - projectileSpeed*projectileSpeed + p*p + r*r,
		2*j*q + 2*h*p + 2*k*r,
		j*j + h*h + k*k
	)
	if solutions then
		local posRoots: {number} = table.create(2)
		for _, v in solutions do --filter out the negative roots
			if v > 0 then
				table.insert(posRoots, v)
			end
		end
		if posRoots[1] then
			local t: number = posRoots[if pickLongest then 2 else 1]
			local d: number = (h + p*t)/t
			local e: number = (j + q*t - l*t*t)/t
			local f: number = (k + r*t)/t
			return origin + Vector3.new(d, e, f)
		end
	end
	return
end

return module

<Vector3?> module.SolveTrajectory(
	<Vector3> origin,
	<number> projectileSpeed,
	<Vector3> targetPos,
	<Vector3> targetVelocity,
	<boolean?> pickLongest,
	<number?> gravity
)

origin
The location of where the projectile was fired from. (i.e. the gun)

projectileSpeed
The scalar speed of the projectile (muzzle velocity)

targetPos
The world position of the target.

targetVelocity
The velocity of the target. If you plan on implementing velocity inheritance, this needs to change accordingly.

pickLongest
Whether or not to use the trajectory with the longest time of flight. Defaults to false (nil).

gravity
Optional. The scalar acceleration of gravity, uses the workspace’s gravity as default.

Returns A position in worldspace representing the point to which to aim the gun at. If no solution exists, it returns nil.


Usage example:

local function aim()
	local aimAt: Vector3? = calcTrajectory.SolveTrajectory(gun.Position, muzzleV, target.Position, target.AssemblyLinearVelocity)
	if aimAt then
		gun.CFrame = CFrame.lookAt(gun.Position, aimAt)
	end
end

Demo file:

projectileBallistics.rbxl (41.6 KB)

Credits

98 Likes

DANG, i think i will use this some time! very cool!

2 Likes

Hi I literally made a post about Arrow Projection Rotation Help could you look at scripting help topic and see if you could advise me on how to do it? ;/

2 Likes

Nvm this script doesn’t even use rotation and is overcomplicated

1 Like

Gee, Rule 17 is coming for you

2 Likes

Update! I swapped out the old quartic formula (which used Newton Iteration) for another one that is much more efficient (Cardano’s formula & Ferrari’s method). See Credits for the source.

You should be able to squeeze out even more performance now!

3 Likes


how did you make it shoot like that?

2 Likes

This is something I completely forgot about. I’ll update the module to make that an option.


If you look inside the module, the function’s searching for the smallest positive real root of the quartic polynomial for the solution. It’s just a matter of using the bigger positive root instead of the smaller one and it’ll use the longer path.

Another update, completely forgot about implementing this when I first made it. You can now specify which of the two paths you want to use for the projectile in the function parameters.

The demo file has also been updated.

Backwards compatibility note:
The new pickLongest parameter has swapped places with gravity (gravity is still the last parameter).

2 Likes

thanks for that, exacly what i needed

1 Like

CRAMs in Roblox games are gonna be crazy now

1 Like

How would I make this work for something like a missle

1 Like

Is there one that will just tell me where my projectile will hit, rather than what angle I need to hit something. And would there be a way to convert the return value of the first function to orientation

1 Like

You can easily edit the module to do both of those things.

If you look inside the function, you can see it calculating the time of flight denoted as the variable t:

			local t: number = posRoots[if pickLongest then 2 else 1]
			local d: number = (h + p*t)/t
			local e: number = (j + q*t - l*t*t)/t
			local f: number = (k + r*t)/t

If the time of flight is how long it takes for the projectile to reach the target, then it will also tell you how far the projectile has moved since the projectile was launched. You simply just multiply the target’s velocity by t.

--hitAt is just a part in workspace
hitAt.Position = targetPos + targetVelocity * t

To demonstrate, the yellow dot is the target and the red dot is hitAt. You can see it lined up with the trajectory.
(I anchored the target in this screenshot)
ScreenShot_20230429142724

It basically already accomplishes that. The demo file uses CFrame.lookAt to aim the gun. You can then derive the rotation with :ToOrientation().

1 Like

Does this also take in account the spawn orientation? (the finding how far my projectile will land)

1 Like

It only takes into account the velocity of the target. It can be facing any direction but the direction it moves in is what matters

1 Like

So it takes in account what my turret is pointing at, what I’m trying to do is see how far my turret will hit at all times, so 15 degrees is around 1000 studs out.

1 Like

That’s not actually the purpose of this module, I might’ve misunderstood something.

If you just want to find the maximum range of a gun based on the angle and the velocity, you can do so with a different equation from Wikipedia:

image

3 Likes

I apologize for bumping this year old thread. I remember doing this a while ago. I learned this from b2studios video LANGUAGE WARNING Cannons that Never Miss - YouTube . b2studios also extended this to the THIRD derivative of position, jerk, the change of acceleration. It requires a polynomial to the SIXTH degree, which cannot be exactly calculated. b2 also wrote a paper on this topic going into full depth about this topic. Solving Projectile Aimbots with a Root-Finding Algorithm - Google Docs

2 Likes

That is very interesting! It’s a shame that I never found that video when I was still researching on how to do this on my own. I have definitely thought about also accounting for the acceleration of targets, but I gave up early. I might look back into it.

Realistically, though, extending the solver all the way up to jerk is completely overkill. Necessitating higher degrees for the solver is a consequence of a long time of flight for the projectile, which only means more opportunity for the target to evade or the projectile itself to skew off because of drag. This is the reason why many modern-day point defense systems use some sort of guided missile/shell that can actively course-correct to reach a target, or laser-based systems to eliminate the issue of ballistics. An example would be the LRLAP ammunitions used on the Advanced Gun System that were installed aboard the Zumwalt-class destroyers.

There’s also this interesting excerpt from Wikipedia about the USN’s 16" guns:

The 16"/50 caliber’s advanced fire control was designed to allow it to fire accurately at its maximum range. which exceeded any opposing ship’s effective firing range. However, this proved not to be possible. The US soon learned that shell dispersion was not something fire control, no matter how advanced, could solve (this remains true: modern guns with more advanced radar cannot fire accurately from maximum range, being limited to a shorter accurate effective range). Several live-firing tests were conducted by Iowa -class battleships in which the 16"/50 displayed shockingly low hit rates from the extreme ranges it was designed to fight from, even with its very advanced radar.

So yeah, in practice, expanding the solver beyond acceleration is way too much lol, but would still be fun to do in a video game simulation.

1 Like