Thank you for this! I can finally get past the range mechanic and continue progressing on my game!
I’ve been working on a rebuild of FC on and off recently. I will address this in the new API once it’s all released. No ETA, since my job takes precedence.
Hi Eti, I wanted to ask, will you add support for bigger hitboxes anytime soon? I’ve been using your module for a while, but for projects that require ranged abilities with bigger hitboxes, I can’t use it, and it’s a huge bummer since it’s the only thing i can think of using for ranged attacks or weapons.
What’s the recommended way to add a “lifetime” for the bullet (i.e. to purposefully proc the CastTerminating event after x seconds)?
I’ve decided to use FastCast in a project but the issue is it seems really expensive. I have multiple weapons in my game so in the server I’m looping through the data for the weapons and creating casters and cast behaviors for each one as well as establishing their respective events. I have a single remote event for when any weapon fires and find the caster setup for that weapon and use it to fire the weapon. This feels expensive though. Is there not some better way to do this?
Do the caster setup entirely client sided. Attach caster objects to instances via a dictionary to keep track and avoid having to create a new caster object.
On the server use projectile logic based sanity checks
Hi, Excellent module! Yielding good results for my project right now.
On another note, I am curious, why do you use your own website for the API instead of GitHub, I see the github page you had set up is obsolete now. Just curious.
Thank you for the contribution to the community!
Is there no support for multipart projectiles when using PartCache? My bullet consists of two mesh parts but I can’t seem to use a model because PartCache only accepts a BasePart.
I’ve faced the same issue, I had to change my projectile model to a single mesh. I don’t think there’s any support for Model projectiles yet.
Hey! I just want my CosmeticBullet to go “straight” (like a knife should go) but it gets thrown from aside.
Like this:
I tried to change the CosmeticBullet orientation directly from the Properties in the union (my CosmeticBullet is an union) and also via script (tried out with .fromMatrix and some lookAt functions but nothing works). Why can’t I change the orientation and how can I fix it?
ty!
You’d need to modify PartCache to support models. It’s not too hard, considering its just two tables and a cloning function.
I think I help you with that, can you send your length changed event code?
sry idk what that exactly is so imma send u my whole server script fastcast code (i’ve undone the part i changed CosmeticBullet Orientation/CFrame bcs it didn’t work as i said)
local knifeThrow = game.ReplicatedStorage:WaitForChild("KnifeThrow")
local tweenService = game:GetService("TweenService")
local health_module = require(game:GetService("ServerStorage").ModuleLibrary.HealthModule)
local DEBUG = false -- Whether or not to use debugging features of FastCast, such as cast visualization.
local BULLET_SPEED = 250 -- Studs/second - the speed of the bullet
local BULLET_MAXDIST = 1000 -- The furthest distance the bullet can travel
local BULLET_GRAVITY = Vector3.new(0, -7.5, 0) -- The amount of gravity applied to the bullet in world space (so yes, you can have sideways gravity)
local MIN_BULLET_SPREAD_ANGLE = 0 -- THIS VALUE IS VERY SENSITIVE. Try to keep changes to it small. The least accurate the bullet can be. This angle value is in degrees. A value of 0 means straight forward. Generally you want to keep this at 0 so there's at least some chance of a 100% accurate shot.
local MAX_BULLET_SPREAD_ANGLE = 0.25 -- THIS VALUE IS VERY SENSITIVE. Try to keep changes to it small. The most accurate the bullet can be. This angle value is in degrees. A value of 0 means straight forward. This cannot be less than the value above. A value of 90 will allow the gun to shoot sideways at most, and a value of 180 will allow the gun to shoot backwards at most. Exceeding 180 will not add any more angular varience.
local FIRE_DELAY = 0 -- The amount of time that must pass after firing the gun before we can fire again.
local BULLETS_PER_SHOT = 1 -- The amount of bullets to fire every shot. Make this greater than 1 for a shotgun effect.
local PIERCE_DEMO = true
local Tool = script.Parent
local Handle = Tool.Handle
local FastCast = require(script.FastCastRedux)
local CanFire = true
local RNG = Random.new() -- Set up a randomizer.
local TAU = math.pi * 2
local MouseEvent = Tool.MouseEvent
FastCast.DebugLogging = DEBUG
FastCast.VisualizeCasts = DEBUG
--caster--
local CosmeticBulletsFolder = workspace:FindFirstChild("CosmeticBulletsFolder") or Instance.new("Folder", workspace)
CosmeticBulletsFolder.Name = "CosmeticBulletsFolder"
local Caster = FastCast.new()
local CosmeticBullet = game.ServerStorage.Knife
CosmeticBullet.CanCollide = false
CosmeticBullet.Anchored = true
local CastParams = RaycastParams.new()
CastParams.IgnoreWater = true
CastParams.FilterType = Enum.RaycastFilterType.Blacklist
CastParams.FilterDescendantsInstances = {}
local CastBehavior = FastCast.newBehavior()
CastBehavior.RaycastParams = CastParams
CastBehavior.MaxDistance = BULLET_MAXDIST
CastBehavior.HighFidelityBehavior = FastCast.HighFidelityBehavior.Default
CastBehavior.CosmeticBulletContainer = CosmeticBulletsFolder
CastBehavior.Acceleration = BULLET_GRAVITY
CastBehavior.AutoIgnoreContainer = false
CastBehavior.CosmeticBulletTemplate = CosmeticBullet
--fire--
function Fire(direction)
if Tool.Parent:IsA("Backpack") then return end
local directionalCF = CFrame.new(Vector3.new(), direction)
local direction = (directionalCF * CFrame.fromOrientation(0, 0, RNG:NextNumber(0, TAU)) * CFrame.fromOrientation(math.rad(RNG:NextNumber(MIN_BULLET_SPREAD_ANGLE, MAX_BULLET_SPREAD_ANGLE)), 0, 0)).LookVector
local simBullet = Caster:Fire(Handle.Position, direction, BULLET_SPEED, CastBehavior)
end
--events--
function OnRayHit(cast, raycastResult, segmentVelocity, cosmeticBulletObject)
-- This function will be connected to the Caster's "RayHit" event.
local hitPart = raycastResult.Instance
local hitPoint = raycastResult.Position
local normal = raycastResult.Normal
if hitPart ~= nil and hitPart.Parent ~= nil then
local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
health_module.TakeClampedDamage(humanoid, 28)
end
end
end
function OnRayUpdated(cast, segmentOrigin, segmentDirection, length, segmentVelocity, cosmeticBulletObject)
-- Whenever the caster steps forward by one unit, this function is called.
-- The bullet argument is the same object passed into the fire function.
if cosmeticBulletObject == nil then return end
local bulletLength = cosmeticBulletObject.Size.Z / 2
local baseCFrame = CFrame.new(segmentOrigin, segmentOrigin + segmentDirection)
cosmeticBulletObject.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength))
end
function OnRayTerminated(cast)
local cosmeticBullet = cast.RayInfo.CosmeticBulletObject
if cosmeticBullet ~= nil then
-- This code here is using an if statement on CastBehavior.CosmeticBulletProvider so that the example gun works out of the box.
-- In your implementation, you should only handle what you're doing (if you use a PartCache, ALWAYS use ReturnPart. If not, ALWAYS use Destroy.
if CastBehavior.CosmeticBulletProvider ~= nil then
CastBehavior.CosmeticBulletProvider:ReturnPart(cosmeticBullet)
else
cosmeticBullet:Destroy()
end
end
end
MouseEvent.OnServerEvent:Connect(function (clientThatFired, mousePoint)
if not CanFire then
return
end
CanFire = false
local mouseDirection = (mousePoint - Handle.Position).Unit
for i = 1, BULLETS_PER_SHOT do
Fire(mouseDirection)
end
if FIRE_DELAY > 0.8 then wait(FIRE_DELAY) end
CanFire = true
end)
Caster.RayHit:Connect(OnRayHit)
Caster.LengthChanged:Connect(OnRayUpdated)
Caster.CastTerminating:Connect(OnRayTerminated)
Tool.Equipped:Connect(function ()
CastParams.FilterDescendantsInstances = {Tool.Parent, CosmeticBulletsFolder}
end)
First make sure your bullets’ front face is actually the front of the bullet. Then try changing this line
cosmeticBulletObject.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength))
for this: cosmeticBulletObject.CFrame = CFrame.lookAt(baseCFrame * CFrame.new(0, 0, -(length - bulletLength)), segmentOrigin + segmentDirection)
Thanks! I’ve changed the front face and that was the only problem here!
Hey! I’m back here with a different problem. I just finished my previous weapon (throwing knife) successfully but this new one (crossbow) has a little problem: bullet goes way up the mouse position.
VIDEO: Desktop 2021.10.06 - 18.50.31.01
SERVER SCRIPT:
local tweenService = game:GetService("TweenService")
local health_module = require(game:GetService("ServerStorage").ModuleLibrary.HealthModule)
local DEBUG = false -- Whether or not to use debugging features of FastCast, such as cast visualization.
local BULLET_SPEED = 300 -- Studs/second - the speed of the bullet
local BULLET_MAXDIST = 1000 -- The furthest distance the bullet can travel
local BULLET_GRAVITY = Vector3.new(0, -5, 0) -- The amount of gravity applied to the bullet in world space (so yes, you can have sideways gravity)
local MIN_BULLET_SPREAD_ANGLE = 0 -- THIS VALUE IS VERY SENSITIVE. Try to keep changes to it small. The least accurate the bullet can be. This angle value is in degrees. A value of 0 means straight forward. Generally you want to keep this at 0 so there's at least some chance of a 100% accurate shot.
local MAX_BULLET_SPREAD_ANGLE = 0 -- THIS VALUE IS VERY SENSITIVE. Try to keep changes to it small. The most accurate the bullet can be. This angle value is in degrees. A value of 0 means straight forward. This cannot be less than the value above. A value of 90 will allow the gun to shoot sideways at most, and a value of 180 will allow the gun to shoot backwards at most. Exceeding 180 will not add any more angular varience.
local FIRE_DELAY = 0 -- The amount of time that must pass after firing the gun before we can fire again.
local BULLETS_PER_SHOT = 1 -- The amount of bullets to fire every shot. Make this greater than 1 for a shotgun effect.
local PIERCE_DEMO = true
local COOLDOWN = true
local Tool = script.Parent
local Handle = Tool.Handle
local Arrow = Tool.Arrow --i use the arrow to get the fire position
local FastCast = require(script.FastCastRedux)
local CanFire = true
local RNG = Random.new() -- Set up a randomizer.
local TAU = math.pi * 2
local MouseEvent = Tool.MouseEvent
local ReloadEvent = Tool.ReloadEvent
FastCast.DebugLogging = DEBUG
FastCast.VisualizeCasts = DEBUG
Tool.Equipped:Connect(function ()
Handle.CanCollide = false
Arrow.CanCollide = false
Arrow.Spikes.CanCollide = false
Arrow.Tail.CanCollide = false
Arrow.Spike.CanCollide = false
Tool.Metal.CanCollide = false
Tool.Rope.CanCollide = false
Tool.Fabric.CanCollide = false
end)
--caster--
local CosmeticBulletsFolder = workspace:FindFirstChild("CosmeticBulletsFolder") or Instance.new("Folder", workspace)
CosmeticBulletsFolder.Name = "CosmeticBulletsFolder"
local Caster = FastCast.new()
local CosmeticBullet = game.ServerStorage.Arrow
CosmeticBullet.CanCollide = false
CosmeticBullet.Anchored = true
local CastParams = RaycastParams.new()
CastParams.IgnoreWater = true
CastParams.FilterType = Enum.RaycastFilterType.Blacklist
CastParams.FilterDescendantsInstances = {}
local CastBehavior = FastCast.newBehavior()
CastBehavior.RaycastParams = CastParams
CastBehavior.MaxDistance = BULLET_MAXDIST
CastBehavior.HighFidelityBehavior = FastCast.HighFidelityBehavior.Default
CastBehavior.CosmeticBulletContainer = CosmeticBulletsFolder
CastBehavior.Acceleration = BULLET_GRAVITY
CastBehavior.AutoIgnoreContainer = false
CastBehavior.CosmeticBulletTemplate = CosmeticBullet
--fire--
function Fire(direction)
if Tool.Parent:IsA("Backpack") then return end
local directionalCF = CFrame.new(Vector3.new(), direction)
local direction = (directionalCF * CFrame.fromOrientation(0, 0, RNG:NextNumber(0, TAU)) * CFrame.fromOrientation(math.rad(RNG:NextNumber(MIN_BULLET_SPREAD_ANGLE, MAX_BULLET_SPREAD_ANGLE)), 0, 0)).LookVector
local simBullet = Caster:Fire(Handle.Position, direction, BULLET_SPEED, CastBehavior)
end
--events--
function OnRayHit(cast, raycastResult, segmentVelocity, cosmeticBulletObject)
-- This function will be connected to the Caster's "RayHit" event.
local cosmeticBullet = cast.RayInfo.CosmeticBulletObject
local hitPart = raycastResult.Instance
local hitPoint = raycastResult.Position
local normal = raycastResult.Normal
cosmeticBullet.CFrame = cosmeticBullet.CFrame + cosmeticBullet.CFrame.LookVector
local weld = Instance.new("WeldConstraint")
weld.Part0 = hitPart
weld.Part1 = cosmeticBullet
weld.Parent = cosmeticBullet
if hitPart ~= nil and hitPart.Parent ~= nil then
local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
health_module.TakeClampedDamage(humanoid, 55)
cosmeticBullet.Anchored = false
elseif hitPart.Parent:IsA("Accoutrement") then
health_module.TakeClampedDamage(hitPart.Parent.Parent.Humanoid, 55)
cosmeticBullet.Anchored = false
end
end
wait(4)
cosmeticBullet:Destroy()
end
function OnRayUpdated(cast, segmentOrigin, segmentDirection, length, segmentVelocity, cosmeticBulletObject)
-- Whenever the caster steps forward by one unit, this function is called.
-- The bullet argument is the same object passed into the fire function.
if cosmeticBulletObject == nil then return end
local bulletLength = cosmeticBulletObject.Size.Z / 2
local baseCFrame = CFrame.new(segmentOrigin, segmentOrigin + segmentDirection)
cosmeticBulletObject.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength))
end
function OnRayTerminated(cast)
local cosmeticBullet = cast.RayInfo.CosmeticBulletObject
if cosmeticBullet ~= nil then
-- This code here is using an if statement on CastBehavior.CosmeticBulletProvider so that the example gun works out of the box.
-- In your implementation, you should only handle what you're doing (if you use a PartCache, ALWAYS use ReturnPart. If not, ALWAYS use Destroy.
if CastBehavior.CosmeticBulletProvider ~= nil then
CastBehavior.CosmeticBulletProvider:ReturnPart(cosmeticBullet)
end
end
end
function Transparency()
Arrow.Transparency = 1
Arrow.Spike.Transparency = 1
Arrow.Spikes.Transparency = 1
Arrow.Tail.Transparency = 1
end
function UnTransparency()
Arrow.Transparency = 0
Arrow.Spike.Transparency = 0
Arrow.Spikes.Transparency = 0
Arrow.Tail.Transparency = 0
end
ReloadEvent.OnServerEvent:Connect(function()
CanFire = true
UnTransparency()
end)
MouseEvent.OnServerEvent:Connect(function (clientThatFired, mousePoint)
if CanFire == false then return end
CanFire = false
local mouseDirection = (mousePoint - Arrow.Spike.Position).Unit
for i = 1, BULLETS_PER_SHOT do
Fire(mouseDirection)
end
Transparency()
end)
Caster.RayHit:Connect(OnRayHit)
Caster.LengthChanged:Connect(OnRayUpdated)
Caster.CastTerminating:Connect(OnRayTerminated)
Tool.Equipped:Connect(function ()
CastParams.FilterDescendantsInstances = {Tool.Parent, CosmeticBulletsFolder}
end)
LOCAL SCRIPT:
local Tool = script.Parent
local Handle = Tool:WaitForChild("Handle")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local MouseEvent = Tool:WaitForChild("MouseEvent")
local ReloadEvent = Tool:WaitForChild("ReloadEvent")
local player = game.Players.LocalPlayer
local character = player.Character
local cooldown = 0.8
local debounce = false
local charged = true
local Mouse = nil
local mouseget = player:GetMouse()
local Mousepl = nil
local ExpectingInput = false
local Camera = workspace.CurrentCamera
local IsMouseDown = false
local animation1 = Instance.new("Animation")
animation1.AnimationId = "http://www.roblox.com/asset/?id=7659776195"
local animationIdle
local animation2 = Instance.new("Animation")
animation2.AnimationId = "http://www.roblox.com/asset/?id=7659853751"
local animationReload
local reloadanimOn = false
local function LoadAnim(character)
local humanoid = character.Humanoid
animationIdle = humanoid.Animator:LoadAnimation(animation1)
animationReload = humanoid.Animator:LoadAnimation(animation2)
end
local connection
if player.Character then
LoadAnim(player.Character)
end
function OnEquipped(playerMouse)
Mouse = playerMouse
ExpectingInput = true
IsMouseDown = false
animationIdle:Play()
end
function OnUnequipped()
ExpectingInput = false
IsMouseDown = false
animationIdle:Stop()
animationIdle:Destroy()
animationReload:Stop()
animationReload:Destroy()
reloadanimOn = false
if character then
character.runn.Disabled = false
end
script.Disabled = true
script.Disabled = false
end
Tool.Equipped:Connect(OnEquipped)
Tool.Unequipped:Connect(OnUnequipped)
Tool.Activated:Connect(function()
if player.Character then
local humanoid = player.Character:FindFirstChild("Humanoid")
if humanoid then
if charged == true then
MouseEvent:FireServer(Mouse.Hit.Position)
charged = false
elseif charged == false then
if reloadanimOn == false then
animationReload:Play()
reloadanimOn = true
if character then
character.runn.Disabled = true
end
wait(3)
if character then
character.runn.Disabled = false
end
ReloadEvent:FireServer()
reloadanimOn = false
charged = true
end
end
end
end
end)
I think the problem is about how I get the mouse.Hit.Position (line 70 local script), in the throwing knife I get it from the “Mouse” variable in OnEquipped function and it works perfectly (I can’t do that in this script).
Hello, could you give me the game’s link or a video, at least? I’m very curious about it(yes, this post is from like 3 years ago but…)
Do you know if it’s possible to remove the bouncing?
How would you set a max speed for a projectile?
there is a “bullet-speed” value, and im pretty sure bullet isnt allowed to pass that limit