Unofficial continuation of FastCast for Roblox
An improved version of FastCast with Parallel scripting, more extensions, and statically typed.
A powerful modern projectile library
FastCast2 It's a Roblox projectile library powered by VMsDispatcher designed to simulate thousands of projectiles without relying on physics replication.
Because FastCast is no longer actively maintained by EtiTheSpirit, this repository continues the project with updates and adaptations.
- Highly customizable
- Versatile projectile library
- Parallel scripting support
- Easy to use and integrate
- Raycast and Blockcast, Spherecast support
- BulkMoveTo support
- Built-in ObjectCache
- Flexible and extensible design
- Improves development productivity
- High Performance
- Completely free
FastCast2 is an open-source project, and contributions from the community are welcome.
Read more on FastCast2 devforum
- Go to Releases and install the
.rbxmfile from the latest release. - Open Roblox Studio and open any project.
- Go to File → Import Roblox Model and import the
.rbxmfile. - After importing, FastCast2 will appear in your Workspace.
- Drag FastCast2 into ReplicatedStorage.
- Create a Part and set:
- Size to
1, 1, 1 CanTouch= falseCanCollide= falseCanQuery= false
- Size to
- Done — you’re ready to use FastCast2.
- Install the Rojo CLI for your system.
- Clone this repository:
git clone https://github.com/weenachuangkud/FastCast2.git cd FastCast2 rm -rf .git - Sync to Roblox:
Or serve live with:
rojo sync -o <place-name>
Then connect in Roblox Studio via Studio → Plugins → Rojo.rojo serve
Shooting projectiles from your head (Serial mode - simpler, main thread):
-- Services
local Rep = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- Requires
local FastCast2 = require(Rep:WaitForChild("FastCast2"))
local FastCastEnums = require(Rep:WaitForChild("FastCast2"):WaitForChild("FastCastEnums"))
-- CONSTANTS
local SPEED = 500
-- Variables
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local Head = character:WaitForChild("Head")
local Mouse = player:GetMouse()
local ProjectileContainer = workspace:WaitForChild("Projectiles")
local ProjectileTemplate = Rep:WaitForChild("Projectile")
-- CastParams
local CastParams = RaycastParams.new()
CastParams.FilterDescendantsInstances = {character}
CastParams.FilterType = Enum.RaycastFilterType.Exclude
CastParams.IgnoreWater = true
-- Behavior (FastCastBehavior)
local behavior = FastCast2.newBehavior()
behavior.MaxDistance = 1000
behavior.RaycastParams = CastParams
behavior.HighFidelityBehavior = FastCastEnums.HighFidelityBehavior.Default
behavior.HighFidelitySegmentSize = 1
behavior.Acceleration = Vector3.new(0, -workspace.Gravity/2.3, 0)
behavior.CosmeticBulletContainer = ProjectileContainer
behavior.CosmeticBulletTemplate = ProjectileTemplate
-- Serial Caster (runs on main thread, simpler)
local Caster = FastCast2.new()
Caster:Init("BulkMoveTo", false) -- movementMode, useObjectCache
-- Events (can be set before Init)
Caster.Hit = function(cast, result, velocity, bullet)
print("Hit: " .. result.Instance.Name)
end
-- Fire
local function fire()
local origin = Head.Position
local direction = (Mouse.Hit.Position - origin).Unit
Caster:RaycastFire(origin, direction, SPEED, behavior)
end
-- Input
game:GetService("UserInputService").InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
fire()
end
end)Swap RaycastFire for BlockcastFire or SpherecastFire to change cast type:
-- Blockcast: pass a Vector3 size after origin
Caster:BlockcastFire(origin, Vector3.new(2, 4, 2), direction, SPEED, behavior)
-- Spherecast: pass a number radius after origin
Caster:SpherecastFire(origin, 3, direction, SPEED, behavior)ObjectCache reuses cosmetic bullet instances instead of creating/destroying them every shot:
local Caster = FastCast2.new()
Caster:Init("BulkMoveTo", true, ProjectileTemplate, 500, workspace)
-- ^template ^size ^holderThe cache pre-allocates 500 parts by default, auto-expands when exhausted, and moves retired
parts to a far-away CFrame via BulkMoveTo — no instance creation/destruction overhead.
local Caster = FastCast2.newParallel()
Caster:Init(
4, -- numWorkers
workspace, -- VM folder parent
"FastCastVMs", -- VM folder name
workspace, -- container parent
"VMContainer", -- container name
"VM", -- VM name
"BulkMoveTo", -- movementMode
nil, -- FastCastEventsModule (optional)
false -- useObjectCache
)
-- Events work the same as serial
Caster.Hit = function(cast, result, velocity, bullet)
print("Hit: " .. result.Instance.Name)
end
--[[
-- Except:
-- Caster.CanPierce
--> You will have to use FastCastEventsModule for that
]]
-- Fire the same as serial
Caster:RaycastFire(origin, direction, SPEED, behavior)
Caster:BlockcastFire(origin, Vector3.new(2, 4, 2), direction, SPEED, behavior)
Caster:SpherecastFire(origin, 3, direction, SPEED, behavior)How to set up FastCastEventsModule
-- Services
local Rep = game:GetService("ReplicatedStorage")
-- Modules
local FastCast2 = Rep:WaitForChild("FastCast2")
-- Requires
local TypeDef = require(FastCast2:WaitForChild("TypeDefinitions"))
-- Module
local module: TypeDef.FastCastEvents = {}
local debounce = false
local debounce_time = 0.2
module.LengthChanged = function(cast : TypeDef.ActiveCastData)
if not debounce then
debounce = true
print("OnLengthChanged Test")
task.delay(debounce_time, function()
debounce = false
end)
end
end
module.CastFire = function()
print("CastFire!")
end
module.CastTerminating = function()
print("CastTerminating!")
end
module.Hit = function()
print("Hit!")
end
module.CanPierce = function(cast : TypeDef.ActiveCastData, resultOfCast : RaycastResult, segmentVelocity, CosmeticBulletObject)
local CanPierce = false
if resultOfCast.Instance:GetAttribute("CanPierce") == true then
CanPierce = true
end
print(CanPierce)
return CanPierce
end
module.Pierced = function()
print("Pierced!")
end
return moduleRegister it on your parallel caster after Init:
Caster:SetFastCastEventsModule(pathTo.FastCastEventsModule)Note:
SetFastCastEventsModuleis only available on parallel casters. In serial mode, set event handlers directly on the caster (e.g.,Caster.Hit = function(...)).
Motor6D mode uses Motor6D.Transform for more performance instead of BulkMoveTo:
local Caster = FastCast2.new()
Caster:Init("Motor6D", false) -- movementMode = "Motor6D"All active casts automatically get a Motor6D connection on registration and disconnection on cleanup. You can switch modes at runtime:
Caster:SetMovementModeEnabled(true, "Motor6D") -- enable Motor6D
Caster:SetMovementModeEnabled(true, "BulkMoveTo") -- switch backModify active casts at runtime using the static FastCast methods:
-- Read state
local pos = FastCast2.GetPositionCast(cast)
local vel = FastCast2.GetVelocityCast(cast)
local accel = FastCast2.GetAccelerationCast(cast)
-- Modify state (automatically rebases trajectory)
FastCast2.SetPositionCast(cast, Vector3.new(0, 50, 0))
FastCast2.SetVelocityCast(cast, Vector3.new(0, 100, 0))
FastCast2.SetAccelerationCast(cast, Vector3.new(0, -workspace.Gravity, 0))
-- Relative changes
FastCast2.AddPositionCast(cast, Vector3.new(0, 10, 0))
FastCast2.AddVelocityCast(cast, Vector3.new(0, 20, 0))
FastCast2.AddAccelerationCast(cast, Vector3.new(0, -50, 0))
-- Terminate a cast early (fires CastTerminating and cleans up)
FastCast2.TerminateCast(cast)Tip: In parallel mode, call
Caster:SyncChangesToCast(cast)after modifying to push changes into the worker VM.
-> Get started with the FastCast2 documentation
- Mawin CK: Main developer, Maintainer, Graphic designer
- Naymmmm: Help with proper docs, CI, Rojo supports, wally supports, Github pages(Moonwave)
- EtiTheSpirit: Original developer
Thanks to the following people from the Suphi Kaner Discord Server:
- @avibah — For helping me create VMDispatcher
- @ace9b472eeec4f53ba9e8d91bo87c636 — For advice and ideas
- @23sinek345 — For code review and improvement suggestions
