Making a combat game with ranged weapons? FastCast may be the module for you!

Hey! I’m making a game that will feature quite a few ability-based projectiles, and I’m working to use FastCast for that purpose. My current plan for integrating FastCast is to set up a caster object for each individual ability and having every entity (NPC, player, etc.) that uses that ability use that specific caster object. Is this the proper way to go about it? Or should I be creating entity-exclusive casters instead, such that every player/NPC/whatever has their own ability casters?

@Paintertable Using the system itself from the clientside causes no problems for it, it’s designed to work on both (in fact, you can see evidence of this on the if statement located at line 140 and ending at line 144. I don’t know if this is what you meant to say or not, but I’ll bring it up just in case.

if RunService:IsClient() then
	targetEvent = RunService.RenderStepped
else
	targetEvent = RunService.Heartbeat
end

@ChefJustice Yep! This is one way to go about it. Do be careful when tracking which NPC/Player has used the ability! The same LengthChanged and RayHit functions will fire for everyone if you implement it this way. I recommend instantiating a caster every time the ability is referenced so that each NPC/Player has their own, unless you are comfortable with handling that event call differentiation on your own. There are pros and cons to both, but otherwise there will be no negative impact in terms of performance.

2 Likes

What would be the benefit on doing it on the client? Would the projectiles be more smoother?
Currently the projectiles don’t fire from the bulletpoint directly and spawn (or are visible) some studs later. (ofc they spawn at the bulletpoint but they aren’t visible instantly which makes it looks weird sometimes.)

This is genuinely a lifesaving module, thank you. Any future plans for projectile ricochet?

1 Like

you can make the bullets ricochet using the module. just make it so when it hits something it fires the same bullet in a different direction. if you don’t know what direction to make the bullets go, this thread might help

2 Likes

This module is amazing! Thank you so much for making it

One issue I had was the bullets seemed to be ‘starting’ one segment ahead of the origin position, I found that this was because the initial totalDelta in the Fire function being 0, so the GetPositionAtTime function is multiplying the calculations by 0, therefore positioning it at 0, 0, 0 initially. Changing the initial totalDelta variable to something small like 0.00001 fixed it at bullets now spawn out of the origin position

Edit: you also need to move the totalDelta calculation as shown below

original:

edit:

3 Likes

Thanks alot! This made it alot better for me! But its still not very smooth. I have the feeling that the bullets have some lag. Do you create the bullets on the client to have it look smoother or the server?

I handle the visual side (bullets) on the client, bust I also use fastcast on the server (with no visual bullet) to handle damage. Looks smooth for me!

Can you tell me how you do it? ( Maybe in private chat if this floods the post )

In short
-on firing from client create a fastcast bullet for the local player
-send event to server saying the player fired, create fast cast on server with no bullet (have to edit module a tiny bit, just commenting out some checks)
-send event to other players that player created a bullet, on client create fastcast from the firing players weapon
-handle damage on server from fastcast bullet hit event

If that doesn’t make any sense dm me, I’m on mobile at the moment so I can’t show any example code

2 Likes

Is there any way to always have the bullet come from the attachment every time? I made the spread 0 and it still spreads noticeably if you don’t click anywhere near the center.

6 Likes

I just noticed this, FireWithWhitelist and FireWithBlacklist functions do not pass on the canPierceFunction to the BaseFireMethod function; so it is nil every time.

Oh! Good catch! I’ve just released a patch for that in v9.0.1. Reinserting the model should fix it.

2 Likes

Is there a way to use fast cast to cause the cosmetic object to home in on a part? In other words I want to create a missile to change directions and follow a part based on the position of the part.

Is there any way to make the projectile come out in a straight line from the fire point and not at mouse point? I looked at the API and messed with it in studio a bit and I can’t seem to get it off mouse point as the direction.

You would most likely use CFrame.LookVector

Alright I’ll try that later, kinda dumb but forgot about CFrame I only used Vector3 when I was messing with it. Thanks for the tip.

1 Like

Hi! Love that you have created this and provided all this extra info on how to use it.

I’m having trouble with running FastCaster:Fire() once but receiving two FastCaster.RayHit:Connect(OnRayHit) back.

Edit: Fixed it!
The problem was that I was having the server script in each gun, listen for the SendHitEvent. This event was in ReplicatedStorage, so every server script in every tool would damage the hit player once one of the players sent the SendHitEvent. I fixed it by moving the event inside the tool. First I tried moving the server script out of the tool, but it required that I keep the state of all the clients in a table or something. And I didn’t want the extra complexity.

Original post

This means that my DamagePlayer() function is fired twice on the server, and I don’t want that.
The issue occurs when I run a test server with two players. For one player it fires once. For three players I would imagine it fires 3 times.

I don’t think FastCaster:Fire() should be ran more than once, because it is only ran in the LocalScript of the player who fires at that moment.

LocalScript
local Tool = script.Parent
local Handle = Tool:WaitForChild("Handle")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local EventStorage = game:GetService("ReplicatedStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FireEvent = EventStorage:WaitForChild("FireEvent")
local ClientProjectileEvent = EventStorage:WaitForChild("ClientProjectileEvent")
local Configs = Tool:WaitForChild("Configs")
local constants = require(Configs:WaitForChild("Settings"))

local Mouse = nil
local ExpectingInput = false
local Camera = workspace.CurrentCamera
local FirePointObject = Handle:WaitForChild("GunFirePoint")

local FastCast = require(ReplicatedStorage:WaitForChild("FastCastRedux"))
local OriginalShotCaster = FastCast.new()
local FireSound = Handle:WaitForChild("Fire")
local Debris = game:GetService("Debris")
local LaserBullet = require(Tool:WaitForChild("Bullet"))
local SendHitEvent = EventStorage:WaitForChild("SendHitEvent")


function UpdateMouseIcon()
	if Mouse and not Tool.Parent:IsA("Backpack") then
		Mouse.Icon = "rbxasset://textures/GunCursor.png"
	end
end

function OnEquipped(PlayerMouse)
	Mouse = PlayerMouse
	ExpectingInput = true
	UpdateMouseIcon()
end

function OnUnequipped()
	ExpectingInput = false
	UpdateMouseIcon()
end

Tool.Equipped:Connect(OnEquipped)
Tool.Unequipped:Connect(OnUnequipped)

UserInputService.InputBegan:Connect(function (input, gameHandledEvent)
	if gameHandledEvent or not ExpectingInput then
		--The ExpectingInput value is used to prevent the gun from firing when it shouldn't on the clientside.
		--This will still be checked on the server.
		return
	end
	
	if input.UserInputType == Enum.UserInputType.MouseButton1 and Mouse ~= nil then
		local FireDirection = (Mouse.Hit.p - FirePointObject.WorldPosition).Unit
		FireEvent:FireServer(FireDirection)
		
		local humanoidRootPart = Tool.Parent:WaitForChild("HumanoidRootPart", 1)	-- Add a timeout to this.
		local myMovementSpeed = humanoidRootPart.Velocity							-- To do: It may be better to get this value on the clientside since the server will see this value differently due to ping and such.
		local modifiedBulletSpeed = (FireDirection * constants.BULLET_SPEED) + myMovementSpeed	-- We multiply our direction unit by the bullet speed. This creates a Vector3 version of the bullet's velocity at the given speed. We then add MyMovementSpeed to add our body's motion to the velocity.
		
		FireProjectile(FirePointObject.WorldPosition, FireDirection * constants.BULLET_MAXDIST, modifiedBulletSpeed, nil, Tool.Parent, false, constants.BULLET_GRAVITY, "LaserPistol")
	end
end)

function PlayFireSound(position)
	local NewSound = FireSound:Clone()
	NewSound.Parent = Handle
	NewSound:Play()
	Debris:AddItem(NewSound, NewSound.TimeLength)
end
	
function FireProjectile(Origin, DirectionWithMagnitude, Speed, cosmeticBulletObject, ignoreDescendantsInstance, ignoreWater, bulletAcceleration, Type) 
	
	OriginalShotCaster.IgnoreDescendantsInstance = ignoreDescendantsInstance
	PlayFireSound(ignoreDescendantsInstance:WaitForChild("DebugGun"):WaitForChild("Handle"))
	if Type == "LaserPistol" then
		local NewBullet = LaserBullet:Clone()
		NewBullet.CFrame = CFrame.new(Origin, Origin + DirectionWithMagnitude.Unit)
		NewBullet.Parent = workspace.CurrentCamera
		print("Firing cast in FireProjectile")
		OriginalShotCaster:Fire(Origin, DirectionWithMagnitude, Speed, NewBullet, ignoreDescendantsInstance, ignoreWater, bulletAcceleration)
	end		
end


function OnRayHit(hitPart, hitPoint, normal, material, cosmeticBulletObject)
	print("OnRayHit for OriginalShotCaster")
	SendHitEvent:FireServer(hitPart, hitPoint, normal, material, cosmeticBulletObject)
end

--OriginalShotCaster.LengthChanged:Connect(DrawBulletModule.OnRayUpdated)
--OriginalShotCaster.RayHit:Connect(DrawBulletModule.OnRayHit) --For displaying the hit marker
OriginalShotCaster.RayHit:Connect(OnRayHit) --For taking damage
Server
-- Latest update 24 November 2019 - Upgrade to redux.

local Tool = script.Parent
local Handle = Tool:WaitForChild("Handle")
local EventStorage = game:GetService("ReplicatedStorage")

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FireEvent = EventStorage:WaitForChild("FireEvent")
local FirePointObject = Handle:WaitForChild("GunFirePoint")
local FastCast = require(ReplicatedStorage:WaitForChild("FastCastRedux"))

local ImpactParticle = Handle:WaitForChild("ImpactParticle")
local SendCastEvt = EventStorage:WaitForChild("SendCast")
local Debris = game:GetService("Debris")
local Configs = Tool:WaitForChild("Configs")
local SendHitEvent = EventStorage:WaitForChild("SendHitEvent")

local constants = require(Configs:WaitForChild("Settings"))
					

local CanFire = true								-- Used for a cooldown.

-- Now we set the caster values.
--local Caster = FastCast.new() --Create a new caster object.

-- Bonus points: If you're going to be slinging a ton of bullets in a short period of time, you may see it fit to use PartCache.
-- https://devforum.roblox.com/t/partcache-for-all-your-quick-part-creation-needs/246641 

function Fire(direction)
	-- Called when we want to fire the gun.
	if Tool.Parent:IsA("Backpack") then return end -- Can't fire if it's not equipped.
	-- Note: Above isn't in the event as it will prevent the CanFire value from being set as needed.
	
	-- UPD. 11 JUNE 2019 - Add support for random angles.
	local directionalCF = CFrame.new(Vector3.new(), direction)
	-- Now, we can use CFrame orientation to our advantage.
	-- Overwrite the existing Direction value.
	local direction = (directionalCF * CFrame.fromOrientation(0, 0, constants.RNG:NextNumber(0, constants.TAU)) * CFrame.fromOrientation(math.rad(constants.RNG:NextNumber(constants.MIN_BULLET_SPREAD_ANGLE, constants.MAX_BULLET_SPREAD_ANGLE)), 0, 0)).LookVector
	
	-- UPDATE V6: Proper bullet velocity!
	-- IF YOU DON'T WANT YOUR BULLETS MOVING WITH YOUR CHARACTER, REMOVE THE THREE LINES OF CODE BELOW THIS COMMENT.
	-- Requested by https://www.roblox.com/users/898618/profile/
	-- We need to make sure the bullet inherits the velocity of the gun as it fires, just like in real life.
	local humanoidRootPart = Tool.Parent:WaitForChild("HumanoidRootPart", 1)	-- Add a timeout to this.
	local myMovementSpeed = humanoidRootPart.Velocity							-- To do: It may be better to get this value on the clientside since the server will see this value differently due to ping and such.
	local modifiedBulletSpeed = (direction * constants.BULLET_SPEED) + myMovementSpeed	-- We multiply our direction unit by the bullet speed. This creates a Vector3 version of the bullet's velocity at the given speed. We then add MyMovementSpeed to add our body's motion to the velocity.
	
	-- NOTE: It may be a good idea to make a Folder in your workspace named "CosmeticBullets" (or something of that nature) and use FireWithBlacklist on the descendants of this folder!
	-- Quickly firing bullets in rapid succession can cause the caster to hit other casts' bullets from the same gun (The caster only ignores the bullet of that specific shot, not other bullets).
	-- Do note that if you do this, you will need to remove the Equipped connection that sets IgnoreDescendantsInstance, as this property is not used with FireWithBlacklist
	
	-- Fire the caster
	--Caster:Fire(FirePointObject.WorldPosition, direction * constants.BULLET_MAXDIST, modifiedBulletSpeed, nil, Tool.Parent, false, constants.BULLET_GRAVITY)
	-- Uncomment this later: SendCastEvt:FireAllClients(FirePointObject.WorldPosition, direction * constants.BULLET_MAXDIST, modifiedBulletSpeed, nil, Tool.Parent, false, constants.BULLET_GRAVITY, "LaserPistol")	
end


function DamagePlayer(player, hitPart, hitPoint, normal, material, cosmeticBulletObject)
	-- This function will be connected to the Caster's "RayHit" event.
	-- (removed from server) cosmeticBulletObject:Destroy() -- Destroy the cosmetic bullet.
	if hitPart and hitPart.Parent then -- Test if we hit something
		local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid") -- Is there a humanoid?
		if humanoid then
			print("Player "..player.Name.." ordered Take damage")
			humanoid:TakeDamage(Configs:WaitForChild("ShotDamage").Value) -- Damage. Configs:WaitForChild("ShotDamage").Value script.Parent.Configs:WaitForChild("ShotDamage").Value
		end
	end
end

	
-- Before I make the connections, I will check for proper values. In production scripts that you are writing that you know you will write properly, you should not do this.
-- This is included exclusively as a result of this being an example script, and users may tweak the values incorrectly.
assert(constants.MAX_BULLET_SPREAD_ANGLE >= constants.MIN_BULLET_SPREAD_ANGLE, "Error: MAX_BULLET_SPREAD_ANGLE cannot be less than MIN_BULLET_SPREAD_ANGLE!")
if (constants.MAX_BULLET_SPREAD_ANGLE > 180) then
	warn("Warning: MAX_BULLET_SPREAD_ANGLE is over 180! This will not pose any extra angular randomization. The value has been changed to 180 as a result of this.")
	constants.MAX_BULLET_SPREAD_ANGLE = 180
end

FireEvent.OnServerEvent:Connect(function (clientThatFired, mouseDirection)
	if not CanFire then
		return
	end
	CanFire = false
	Fire(mouseDirection)
	wait(constants.FIRE_DELAY)
	CanFire = true
end)

SendHitEvent.OnServerEvent:Connect(DamagePlayer)
--Caster.RayHit:Connect(OnRayHit)

In my script, FastCaster is called OriginalShotCaster

6 Likes

Sorry for the week-late response! I’ve been busy.

Right now, no, the module does not support changing the direction of the projectile in runtime. It’s a feature I could look into, but generally speaking rockets are slow enough to (more) safely use stock physics, especially if you set the network owner of the rocket to the client that fires it.

I may add this later down the line but right now it’s fairly low priority, I’m afraid.

1 Like

How would I have one server script handle all my fast cast guns? Creating more caster objects?