Help With Figuring Out the Physics Formula to Get the Angle of Which to Fire a Projectile in the Module FastCast

What I’m trying to achieve is a formula for a Projectile Firing Angle to make it go from an Origin Position to a Goal position with a set Initial Velocity and Acceleration.

The issue is the physics calculations aren’t Roblox’s itself, but the module FastCast made by Xan_TheDragon. The module runs basically as a tiny game engine, with a game loop that runs projectile physic calculations, then updates projectile positions, and even checks for hits with RayCasting. The forum post for it can be found here, Making a combat game with ranged weapons? FastCast may be the module for you! .

I’ve asked around and some formulas do exist for real physics calculations and Roblox’s, but my understanding of physics isn’t enough to make a formula for it, especially from the custom physics in FastCast.

A basic rundown of the FastCasts engine is as follows.
You start with a new Cast the main physic inputs being origin, direction, velocity, and acceleration. Then a final Velocity is constructed by multiplying the direction.Unit * velocity. These inputs are then run through a loop I’ve shortened for just the physics portion.

-- This returns the location that the bullet will be at when you specify the amount of time the bullet has existed, the original location of the bullet, and the velocity it was launched with.
local function GetPositionAtTime(time: number, origin: Vector3, initialVelocity: Vector3, acceleration: Vector3): Vector3
	local force = Vector3.new((acceleration.X * time^2) / 2,(acceleration.Y * time^2) / 2, (acceleration.Z * time^2) / 2)
	return origin + (initialVelocity * time) + force
end

-- A variant of the function above that returns the velocity at a given point in time.
local function GetVelocityAtTime(time: number, initialVelocity: Vector3, acceleration: Vector3): Vector3
	return initialVelocity + acceleration * time
end

-- Run in a loop
local function SimulateCast(cast: ActiveCast, delta: number, expectingShortCall: boolean)
	delta = 0.01667 -- 1/60th of a second, makes the physics calculations deterministic 

	local latestTrajectory = cast.StateInfo.Trajectories[#cast.StateInfo.Trajectories]
	
	local origin = latestTrajectory.Origin
	local totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime
	local initialVelocity = latestTrajectory.InitialVelocity
	local acceleration = latestTrajectory.Acceleration
	
	local lastPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration)
	local lastVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration)
	
	cast.StateInfo.TotalRuntime += delta
	
	totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime	-- Recalculate this.
	
	local currentTarget = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration)
	local segmentVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration) 
end
1 Like

The GetPositionAtTime function will be of use for you, input your values into it, time (in seconds) that has passed, the origin Position, your set initialVelocity, and your set acceleration, it will then output the calculated goalPosition your projectile will be at your set time, to then get the angle, simply do

local difference = goalPosition - originPosition
local angleX = math.atan2(difference.y, math.sqrt(difference.x^2 + difference.z^2))
local angleY = math.atan2(-difference.x, -difference.z)

If you want to use your own goalPosition, then just ditch GetPositionAtTime and use the above formula directly, it’ll give you the X and Y angles (in radians), you’ll need to fire you’re projectile in, you can then apply that angle to your projectile using

Projectile.CFrame = CFrame.fromOrientation(angleX, angleY, 0) + Projectile.Position --The angles returned must be applied on the global axis, not the local, so I'm overriding the Projectile's CFrame with the rotated CFrame, then adding the Position of the projectile back so it retains it's position

I did end up getting a solution and function to make my goal work. To any future readers you use this for the direction in local simBullet = Caster:Fire(origin, direction, velcoity, castDataPacket)
I also was getting this for a 2D simulation, and I did some testing to make sure it worked in 3D simulations, but things like which arc you choose should be in consideration for use.

All credits to Change FastCast's projectile acceleration to achieve parabolic trajectory - #8 by nicemike40, as in reality I still know little about how the calculations work

local function ProjectileDirection(Origin: Vector3, Target: Vector3, Speed: number, Acceleration: Vector3): Vector3?
	local Displacement = Target - Origin

	local AccelerationMagnitude = Acceleration.Magnitude
	if AccelerationMagnitude == 0 then return (Displacement).Unit end -- No acceleration? Straight line.

	local SpeedSquared = Speed^2
	local XZ = Vector3.new(Displacement.X, 0, Displacement.Z)
	local Y = Displacement.Y
	local DirectionXZ = XZ.Unit

	local HorizontalDistance = XZ.Magnitude
	if HorizontalDistance == 0 then -- Vertical shot (directly above or below)
		return Vector3.new(0, Y > 0 and 1 or -1, 0) -- Upward or downward
	end

	local RootTerm = SpeedSquared^2 - AccelerationMagnitude * (AccelerationMagnitude * HorizontalDistance^2 + 2 * Y * SpeedSquared)
	if RootTerm < 0 then -- No solution: Target out of range at given speed, solve for closest
		local Angle = math.rad(45) -- You can adjust this for better approximations under gravity
		local bestDirection = (DirectionXZ * math.cos(Angle) + Vector3.yAxis * math.sin(Angle)).Unit
		return bestDirection
	end 

	local Root = math.sqrt(RootTerm)
	-- (SpeedSquared - Root) = Low Arc || (SpeedSquared + Root) = High Arc
	local Angle = math.atan((SpeedSquared - Root) / (AccelerationMagnitude * HorizontalDistance)) -- lower trajectory (faster)
	local Direction = (DirectionXZ * math.cos(Angle) + Vector3.yAxis * math.sin(Angle)).Unit -- Final velocity vector
	return Direction
end

1 Like

I also hope to one day release my own version of FastCast that has improved additions like Deterministic outcomes instead of FPS based, client visuals with server hitboxes for Model projectiles, fixed projectile hitboxes for projectiles bigger than a RayCast with the implementation of ShapeCast, and possibly integration of Parallel Luau for performance improvements. This is all a big dream someday, but my current game I’m working on hopefully should build a base for a FastCast that’ll do some on this list

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.