I’d like to share a resource that may help you implement bullet firing mechanics in your games.
While systems like FastCast are excellent and widely used, most rely on continuous loops (e.g. Heartbeat, Stepped) to update projectile positions. BulletCaster takes a different approach: it uses Roblox’s built-in Physics components to handle bullet travel without relying on per-frame loops.
Key Features
Physics-driven travel – Bullets move naturally using LinearVelocity and related constraints.
Gravity cancellation – Prevents bullets from dropping due to gravity, unless you specifically want them to.
Reflection support – Bullets can bounce at reflective angles after hitting environment objects, based on RaycastParams you configure.
Event callbacks – Easily hook into bullet behavior with functions like:
onHit – Triggered when the bullet collides with something.
onPathChanged – When the bullet’s trajectory is updated.
onPathError – Handles unexpected issues.
onPathCompleted – When the bullet finishes its travel.
Configurable input – Pass in a structured config (typed export available) to quickly adjust bullet properties without digging through the code.
Community Collaboration
If you find BulletCaster useful, I’d love to hear your feedback!
You’re welcome to extend or modify the module to add new features—please share your updates here so others can benefit as well.
Let’s keep improving together.
Thanks!
Dilpreet Singh
Unity/Roblox Dev
Here’s the example code, which uses BulletCaster module:-
local function getCasterBehavior(teamName:string)
-- Configure raycast params to exclude the firing boat and cosmetic bullets container
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {
workspace.Scriptables.Bullets[teamName], workspace.Scriptables.Boats[teamName],
workspace.Scriptables.Characters, workspace.Scriptables.GameTriggers
}
return params
end
function Bullet:_StarCasting()
local bulletData :CT.BulletDataType = self.Data
local castData :BulletCaster.Config = {}
castData.object = self.Instance
castData.worldRoot = self.Instance.Parent
castData.finalSpeed = bulletData.BulletSpeed
castData.maxBounces = bulletData.MaxCollisions
castData.speedCurve = bulletData.SpeedCurve
castData.rayParams = getCasterBehavior(self.Team)
castData.extraVelocity = self.ExtraVelocity or Vector3.zero
castData.onHit = function(rayResult, caster:BulletCaster)
print("onHit:", rayResult and rayResult.Instance)
end
castData.onPathError = function(caster:BulletCaster)
print("onPathError", caster.Bullet)
self.Remote:FireServer("onPathError")
self.Instance:Destroy()
end
castData.onPathChanged = function(newDir)
print("onPathChanged:", newDir)
self.Remote:FireServer("onPathChanged")
end
castData.onPathCompleted = function(caster:BulletCaster)
print("onPathCompleted:", caster.Bullet)
self.Remote:FireServer("onPathCompleted")
self.Instance:Destroy()
end
BulletCaster.new(castData)
end
---------------------------------------------------------------> Public Method <-----------------------------------------------------------
function Bullet:SetConfig()
--Set data in Attributes
self.BulletId = self.Instance:GetAttribute("BulletId")
self.Team = self.Instance:GetAttribute("Team")
self.UserId = self.Instance:GetAttribute("UserId")
self.ExtraVelocity = self.Instance:GetAttribute("ExtraVelocity")
--Bullet Data
self.Data = BulletsData[self.BulletId]
--Remote Event
self.Remote = self.Instance:WaitForChild("RemoteEvent")
return (self.Data ~= nil)
end
function Bullet:Construct()
self.isActive = self:SetConfig()
end
function Bullet:Start()
warn("Client received to cast bullet:", self.BulletId)
if(self.isActive) then
print(self.Instance)
self.Instance:SetAttribute("Testing", true)
self:_StarCasting()
end
end
function Bullet:SteppedUpdate(dt)
if(self.Instance) then
self.Instance.AssemblyAngularVelocity = Vector3.zero
end
end
Here’s the BulletCaster module script. Download the file and import in your project. BulletCaster.lua (11.2 KB)
I don’t really get what’s wrong with this, loops are widely used and pose no performance impact if that’s what you’re getting at
Wait till bro hears about physics steps!!
This isn’t really a feature imho, bullet drop would however be a feature :V Just a lil nitpick, sorry!
Wait, the bullet can change paths? I don’t really know if that’s optimal depending on wether or not it’s controllable or not
Aside from that I just wanna say that this physics based implementation has actually been quite neat I guess, it’s just alot slower for large amounts of bullets (cuz physics and you’re doing alot of expensive value sets on roblox physics instance properties), not to mention the fact that this library could’ve been made using deterministic physics similarly to FastCast and it would’ve been very nice :V
The cool thing about FastCast also is since it’s deterministic I can get the current position, trajectory etc about the bullet without doing any accesses into the DataModel, which means it’s very cheap to replicate server-sided bullets to be visualized on the client
Alongside that, FastCast doesn’t require your bullet to even exist in Workspace to work, which means you can simulate bullets server-side and again just replicate it to the client :V Quite nice
Thanks a lot for the detailed feedback! Let me clarify some of the design choices and differences here:-
Loops (CFrame Updates) vs Physics-Based Movement
Loop-based (e.g. FastCast)
Updates position every Heartbeat/Stepped by manually setting CFrame or Position.
Highly deterministic – you always know exactly where the bullet is mathematically, since it’s not bound by Roblox physics.
Efficient for large-scale bullet simulations because you avoid creating physical instances in Workspace.
Great for games that need pure mathematical predictability (e.g. precise hit-scan simulation, server/client reconciliation).
Physics-based (BulletCaster approach)
Uses LinearVelocity/VectorForce etc. so Roblox’s physics engine handles the interpolation and travel.
No per-frame loops in Lua → less script-side overhead. The heavy lifting is done natively by the engine’s physics step, which is written in C++ and highly optimized.
Gives natural collision events (touches, reflection, etc.) without recalculating manually in a loop.
Easier for devs who prefer to rely on Roblox’s physics rather than writing their own projectile math.
So, it’s not that one is “always better.”
Loops shine for deterministic, easily replicated projectiles (e.g. FastCast).
Physics-based shines when you want bullets to behave like actual rigid bodies, bounce naturally, interact with moving parts, or reduce Lua-side math complexity.
It comes down to the use case:
If you’re making something like a military FPS with heavy replication, FastCast’s deterministic model is perfect.
If you’re making something like an arcade shooter or a physics-y sandbox, physics-driven bullets give you more realism with less custom logic.
On the “Gravity Cancellation” & “Path Change” bits
Gravity cancellation is optional — you’re right, bullet drop is often a feature, but I included it since many devs just want straight laser-like projectiles without coding counterforces.
Path change = when reflection or velocity redirection happens (e.g. bouncing off a wall), so the module signals that trajectory changed, not that bullets randomly wander.
Closing Thought
I agree with you that deterministic bullets (like FastCast) have big advantages for replication and cheap data access. My main goal here wasn’t to replace FastCast but to give devs an alternative approach — one that leverages physics for people who want plug-and-play bullets with natural interactions.
Both methods are valid tools. Which one is “better” really depends on your game’s design goals.
I know my code may not be very modular or best, I did it for my one game, currently I’m working on. And thought to share the module with the community, so others may use or take advantage on it.
Thanks for the feedback though.
Unless you plan on riding your bullet like a pony — I do NOT recommend relying on physics for projectiles as this will create more issues for low-end mobile devices and limit accessibility. It’s always going to be faster predicting and rendering projectiles on the client. Check out FastCast for an example
Hi guys, thanks for responses.
I know the post is written with the help of AI. Because I’m not good at explaining things in proper way. So, in the era of AI, I used to rephrase my words and feeling and things in this module.
But I can assure you that the whole module is written by me (Not by AI). I just took AI’s help to write comments (Again because I’m not good in these things).
Let me know if you have any query about the module, I can surely explain you each/everything about the module and logics (Because it is written by myself).
However, respectfully any devs reading this, DO NOT use physics for projectiles. You can probably get away with using it for bigger projectiles (still don’t recommend at all) that may be tedious to cast overlaps per frame, but for most projectiles there is no need to do so in the modern age of Roblox.