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

Did you remember to CFrame the arrow on the LengthChanged event?

Caster.LengthChanged:Connect(function (CastOrigin, SegmentOrigin, SegmentDirection, Length, CosmeticBulletObject)
	CosmeticBulletObject.CFrame = CFrame.new(SegmentOrigin, SegmentOrigin + SegmentDirection)
end)

I suggest checking out the example code (https://www.roblox.com/library/2009153526/FastCast-Example-Laser-Gun) for more usage.

Sorry for bumping an old thread but, how come the bullet Hit detection for me doesn’t really work?
here’s the GIF here:

and here’s the script portion:

 function OnRayHit(HitPart, HitPoint, Normal, Material, CosmeticBulletObject)
--This function will be connected to the Caster's "RayHit" event.
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
		Humanoid:TakeDamage(10) --Damage.
	end
	MakeParticleFX(HitPoint, Normal) --Particle FX
end
end

function OnRayUpdated(CastOrigin, SegmentOrigin, SegmentDirection, Length, 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.
local BulletLength = CosmeticBulletObject.Size.Z / 2 --This is used to move the bullet to the right spot based on a CFrame offset
CosmeticBulletObject.CFrame = CFrame.new(SegmentOrigin, SegmentOrigin + SegmentDirection) * CFrame.new(0, 0, -(Length - BulletLength))

end

the hit detection is literally the same as the test weapon you have, but here’s the fire function, if there’s a problem with that.

local function shoot(Player, Direction)
--//Initialize
--if not canShoot(player, true) then return end
--shootSound:Play()

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.

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.

--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 * 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.

--Prepare a new cosmetic bullet
local Bullet = CosmeticBullet:Clone()
Bullet.CFrame = CFrame.new(FirePointObject.WorldPosition, FirePointObject.WorldPosition + Direction)
Bullet.Parent = workspace

Caster:Fire(FirePointObject.WorldPosition, Direction * BULLET_MAXDIST, ModifiedBulletSpeed, Bullet)
--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 * 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.
--Prepare a new cosmetic bullet
local Bullet = CosmeticBullet:Clone()
Bullet.CFrame = CFrame.new(FirePointObject.WorldPosition, FirePointObject.WorldPosition + Direction)
Bullet.Parent = workspace

Caster.LengthChanged:Connect(function (CastOrigin, SegmentOrigin, SegmentDirection, Length, CosmeticBulletObject)
CosmeticBulletObject.CFrame = CFrame.new(SegmentOrigin, SegmentOrigin + SegmentDirection)end)

Caster:Fire(FirePointObject.WorldPosition, Direction * BULLET_MAXDIST, ModifiedBulletSpeed, Bullet)

end

First of all, one major issue I see is that you fire the bullet twice in the fire function, and the connect the LengthChanged event every single time after you fire both shots. It looks almost like you copied and pasted that entire chunk of code so it runs twice. This isn’t proper behavior. The template script I supplied had a correct layout, though for the sake of redundancy, this layout should include the following:

  1. The creation of a caster and optionally a template cosmetic bullet at the start of your script
  2. Some function to handle creating a bullet, e.g. shoot in your case. This function should only clone the cosmetic bullet (if necessary), calculate velocity (if necessary), and fire the caster with the needed parameters. It should not connect any caster events.
  3. Event connections to LengthChanged and RayHit at the bottom of the script or wherever you see fit given your organizational preferences.

To elaborate on #2 and #3 – You should connect to LengthChanged and RayHit outside of the shoot function. Think of it like any other event, like Tool.Activated; you usually connect to this only once at the end of your script. This same ideology also applies to LengthChanged and RayHit – connect to them once and handle the data whenever they fire.

From what I can tell in the behavior of the bullets in the gif, you do not handle RayHit correctly. If you indeed copied the code directly from my template, which you claim you did, the undesired behavior is likely caused by your shoot function and its odd behavior. That, or you didn’t use Caster.RayHit:Connect(OnRayHit)

Try cleaning up the script a bit and ensuring that everything necessary is set up. If you’re still having issues, please send me a message and I’ll help you get it sorted out :grinning:

Thank you so much for helping out! turns out the only thing needed was the Caster.RayHit:Connect(OnRayHit), and I’m sorry about my rookie mistakes, this was my first time using fastcast and I love how it works, I will definitely sort out through my code, but all I can say is that it definitely works. Thank you for responding in the thread!

edit once again, but it seems that my code is indeed, horrible! due to how the bullets were working, it multiplies the bullet every single time it is shot, and it deals more damage! I will look into why i made such a stupid mistake, and will edit my comment as to why this happened, lol.

edit edit: was because i put rayhit and lengthchanged inside the shoot function, sorry my bad.

1 Like

You can probably replace the Signal library in this with this alternative. It’s faster than that Signal library.

local Signal = { }
Signal.__index = Signal
Signal.ClassName = "Signal"

local setmetatable = setmetatable
local running = coroutine.running

function Signal.new() return setmetatable({ }, Signal) end

function Signal:Fire(...)
	for Index = 1, #self do
		local Thread = coroutine.create(self[Index])
		coroutine.resume(Thread, ...)
	end
end

function Signal:Wait()
	local Thread = running()

	local function Yield(...)
		self:Disconnect(Yield)
		coroutine.resume(Thread, ...)
	end

	self[#self + 1] = Yield
	return coroutine.yield()
end

function Signal:Connect(Function)
	self[#self + 1] = Function
end

function Signal:Disconnect(Function)
	local Length = #self

	for Index = 1, Length do
		if Function == self[Index] then
			self[Index] = self[Length]
			self[Length] = nil
			break
		end
	end
end

function Signal:Destroy()
	for Index = 1, #self do
		self[Index] = nil
	end
end

return Signal

It’s the fastest event library there is, using it can make FastCast more like FastestCast :stuck_out_tongue: . It’s around 88% faster than the Signal you have currently.
image

I published a gist of the updated code.

3 Likes

Definitely some interesting work. I’ll be replacing the signal module, you’ll be credited of course.

In terms of your edits to the main module, I think I’ll only implement the changes that implement the new Signal module as soon, caching things like Vector3.new, math.sqrt, etc. will have no benefits in performance. Edit: It seems you’ve replied to this, so you’re aware of it already (Also see the second tweet, https://twitter.com/zeuxcg/status/1120508108445450241). Given that the performance increases from those changes at the moment are relatively negligible (very few use cases would require something faster than what’s already going on – I don’t think anyone needs to fire 500+ bullets per frame), I plan to continue using the older methods in anticipation of the new Lua VM, even if it’s some distance from release.

My friend @Validark made the original Signal module, but mine is faster and has an API that’s more like a BindableEvent. I think he deserves the credit more than me.

this module is working great so far, i needed something to stop the delay happenning for projectiles and this works perfectly. however for some reason it ignores the blacklist and still hits a part on it (local player character). the same goes for just shooting. sometimes the ray gets casted through an object thinking that there’s no object and therefor making the projectile go through the object.

I fixed the delay for projectiles by shooting on the client, and then only updating it for other players, so a for loop, checking if the player name is not the local player name (the one who fired the remote), then use FireClient

This is something I made, alternatively. I had a bullet system that tied to this and fired the impacted event, but you can just replace that with a bindable event if you’re willing to make the bullet system. :update() should be called on the created trajectory every RenderStepped, although it is still tied to time rather than an execution count.

Includes a reflection method for more complex weapons and wallbangs.

function physics.trajectory.new(initial, velocity)
	local self 				= setmetatable({},{__index = physics.trajectory})
	
	-- declaring critical variables
	self.position			= initial
	self.velocity			= velocity
	self.impacted			= event.new("impacted")
	self.tracing			= false
	self.tracetype			= "interface"
	
	self.start				= tick()
	self.t					= self.start
	
	return self
end

function physics.trajectory:update()
	local dt 				= tick() - self.t
	self.t 					= tick()
	
	self.position 			= self.position + (self.velocity * dt)
	self.velocity			= (self.velocity + .5 * Vector3.new(0,-1,0) * dt)
end

function physics.trajectory:reflect(normal)
	self.trajectory			= -2 * self.velocity.unit:Dot(normal.unit) * normal.unit + self.velocity.unit;
end
1 Like

Wish the example gun included a spread/accuracy implementation.

Excellent point. That is a valuable piece of information for people looking to get into FPS/TPS design. I’ll get to adding that as soon as I can :smiley:

Status update: This change is now live! Get the latest version of the example gun.

This is certainly interesting. I may not include such extensions natively (Only specific use cases may require something like bullet ricochet e.g. specific laser weapons. They aren’t prominent enough to add as part of the stock code) but I do most certainly encourage you to publish a module of your own to implement this stuff.

One immediate suggestion I have is adding a module that has FastCast as a child, where your module can automate casts and add extra functionality to bullets through pre-handled sequences.

Awesome, thanks for your work man!!!

1 Like

How would I go about making the end of the projectile the starting position rather then the center as it is in the example, Lets say you have a LONG projectile like a beam when you spawn it at the OUTPUT of weapon it uses the projectiles center rather then the end or the beginning of it. I tried just offsetting it by a vector but that makes it look ugly when shooting sideways.

Append this to the end of setting the cosmetic bullet CFrame in LengthChanged

... * CFrame.new(0, 0, -CosmeticBulletObject.Size.Z/2)

This will move the bullet forward by half it’s size relative to however it’s rotated, resulting in exactly what you want.

2 Likes

Thank you for a quick response, that solved it.

Hey, I’ve been experimenting quite a bit but haven’t managed to get what I’m aiming for. How can I make the bullet only stay on a certain Y axis as it travels, as in I don’t want to fire into the sky, only things on the same “level” as the character.

Wow I finally solved it, I was playing around with the server script but all I had to do was actually experiment in the client.

replaced the default firedirection with
local FireDirection = (Vector3.new(Mouse.Hit.p.X,game.Players.LocalPlayer.Character.PrimaryPart.Position.Y, Mouse.Hit.p.Z) - FirePointObject.WorldPosition)

remember to change the ray’s fire direction based on a unit vector otherwise you’ll end up with wack stuff where the spread changes based on how far away someone is. I’ve seen lots of games on roblox do this and it’s incredibly annoying.