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.)
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
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
- The native-Lua Cardano-Ferrari quartic formula by John Kushmer (GNU General Public License, version 3). Slight edits were made for the purpose of this module.
- @PerfectlySquared for his original algebraic solver algorithm which I built upon with heavy modifications. (No longer in use.)
- Forrest Smith’s blog post where I basically plagiarized everything from.
- @Sensei_Developer for being the first to try this idea (unfortunately, his module’s accuracy does not scale with distance)