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

Hey, thanks for replying!
I recently checked out your solution while back and I have tried implementing it, unfortunately I don’t believe I was setting it up properly. I will give it another shot and see if I can get it working properly!

Also, I looked towards games like CS:GO and tried implementing their lag compensation methods, it didn’t really turn out well and made server hit reg slightly better but still inconsistent.

Best regards,
Scythe

1 Like

I ended up fixing this issue by overriding the filter list in the CanRayPierce function, not OnRayPierced. Thanks for ADRENALXNE’s help, he had a similar post in this thread and found this solution.

This API member does not exist because it’s not so simple.

Imagine you do a hitreg with this hypothetical FastCast:Once() and you detect a hit on the person you’re aiming at. Cool, all good so far. But what happens when you fire the bullet that has travel time and say, some dude happens along and walks into the path of the bullet before it hits the guy you were aiming it? Now your pre-calculated result is flat out wrong.

This is why the module comes with the hit simulation it does - because you should be simulating the bullet, travel time and all.

1 Like

Ahh, I understand. Yeah I can see the complexity behind that and the issues that could arise. I appreciate you reading my reply and considering! I do really enjoy this module quite a lot, it’s been a major staple in many of my projects. Thanks a lot!

Is possible to make a projectile’s motion? I was looking on the dev forum and everything I didnt found I am trying to do something like this.

and I am doing something like:

caster.LengthChanged:Connect(function(cast, lastpoint, dir, length)
		local offset = CFrame.new(0, 0, -length)
		bullet.CFrame = CFrame.lookAt(lastpoint, lastpoint + dir):ToWorldSpace(offset)
	end)

How do you make the bullet render immediately when the cast starts instead of when the cast first updates, since if you have it render when it updates, it’ll start rendering at various distances in front of the firepoint rather than actually at the firepoint. It really breaks immersion and I can’t seem to fix it, I’ve tried moving the bullet position to the firepoint after the first render as well, but it just doesn’t do anything.

you need to increase the downward acceleration, its in the castbehavior i believe

1 Like

How can I make bullets pierce through the accessories?

How do I make both projectiles terminate when they hit each other? Right now only one random one gets terminated (usually the one that’s most recently fired).

EDIT: I’ve tried getting the bullet that it hit in OnRayHit, and returning it to PartCache in OnCastTerminating, but it didn’t work. The recently fired bullet gets returned, but it errors for the hit bullet saying that it’s not in-use (though it very obviously is as I tried doing table.find on the InUse table with the hit bullet, and it found it perfectly fine every time)

EDIT 2: Solved:) If anyone runs into this in the future, I keep all ActiveCasts in a table, storing them in the table on fire, having the projectile instance as the key, the ActiveCast as the value, and then when I determine that it hits a projectileI find the ActiveCast associated with the projectile instance from the table, and run :Terminate() on it.

6 Likes

Heya there!

There has been just one issue that I seemingly cannot solve.

Sometimes, the bullet ray shoots at the sky (past the zombie)? I’m guessing this is because of the muzzle being inside a zombie head. The video below showcases this: red part is muzzle position, green part is mouse position.

Here’s the module code that I use for the gun.

local setup = {}
setup.__index = setup

local PS = game:GetService("Players")
local RS = game:GetService("ReplicatedStorage")
local DBS = game:GetService("Debris")

local caster = require(script.Parent.Caster)
local partCache = require(script.Parent.Cache)
local damageDisplay = require(script.Parent.Parent.DamageDisplay)
local fx = RS.Events.FX
local effect = {
	blood = "Blood",
	hit = "Hit"
}

type params = {[string]: any}

local function VisualizeRay(dir: Vector3, origin: Vector3)
	local mid = origin + dir / 2

	local part = Instance.new("Part") do
		part.Parent = workspace
		part.Anchored = true
		part.CFrame = CFrame.new(mid, origin)
		part.Size = Vector3.new(.1, .1, dir.Magnitude)
		part.Material = Enum.Material.Neon
		part.BrickColor = BrickColor.Red()
		part.CanCollide = false
		part.CanQuery = false
		part.CanTouch = false

		task.delay(.2, function()
			part:Destroy()
		end)
	end
end

local function VisualizePos(pos: Vector3, color: BrickColor)
	local part = Instance.new("Part") do
		part.Parent = workspace
		part.Anchored = true
		part.Position = pos
		part.Size = Vector3.new(.5,.5,.5)
		part.Material = Enum.Material.Neon
		part.BrickColor = color
		part.CanCollide = false
		part.CanQuery = false
		part.CanTouch = false

		--task.delay(.2, function()
		--	part:Destroy()
		--end)
	end
end

local function LoadBulletHole(parent: BasePart, pos: Vector3, normal: Vector3, hum: Humanoid)
	local hole = Instance.new("Part") do
		hole.CanCollide = false
		hole.CanQuery = false
		hole.CanTouch = false
		hole.Transparency = 1
		hole.Size = Vector3.new(.5, .5, .05)
		hole.Parent = parent
		hole.CFrame = CFrame.lookAt(pos, pos + normal)
	end

	local image = if hum then script.BloodHole:Clone() else script.PartHole:Clone() do
		image.Parent = hole
	end
	
	local weld = Instance.new("WeldConstraint") do
		weld.Parent = parent
		weld.Part0 = parent
		weld.Part1 = hole
	end

	DBS:AddItem(hole, 15)
end

function setup.new(params)
	local self = {}
	
	self.Tool = params.Tool
	self.Owner = if self.Tool.Parent:IsA("Backpack") then self.Tool.Parent.Parent else PS:GetPlayerFromCharacter(self.Tool.Parent)
	self.Character = self.Owner.Character or self.Owner.CharactedAdded:Wait()
	self.Damage = params.Damage
	self.AttackType = params.AttackType
	self.Bullet = Instance.new("Part")
	self.Multiplier = params.Multiplier
	self.DistanceBeforeMultiply = params.DistanceBeforeMultiply
	self.MinSpread = params.MinSpread
	self.MaxSpread = params.MaxSpread
	
	for i,v in pairs(params.BulletInfo) do
		if i == "Trail" and v:IsA("Trail") then
			local at0 = Instance.new("Attachment") do
				at0.Parent = self.Bullet
				at0.WorldPosition = (self.Bullet.CFrame * CFrame.new(0, 0, (self.Bullet.Size.Z / 2))).Position
			end
			local at1 = Instance.new("Attachment") do
				at1.Parent = self.Bullet
				at1.WorldPosition = (self.Bullet.CFrame * CFrame.new(0, 0, -(self.Bullet.Size.Z / 2))).Position
			end
			
			local clone = v:Clone() do
				clone.Parent = self.Bullet
				clone.Enabled = false
				clone.Attachment0 = at0
				clone.Attachment1 = at1
			end
		else
			if self.Bullet[i] then
				self.Bullet[i] = v
			end
		end
	end
		
	self.MaxDistance = params.MaxDistance
	self.Speed = params.Speed
	self.Gravity = params.Gravity
	self.RaycastParameters = params.RaycastParameters
	self.BulletParent = params.BulletParent
	self.PartCache = partCache.new(self.Bullet, 100, self.BulletParent)
	self.Hitbox = caster.new()
	self.Behavior = caster.newBehavior() do
		self.Behavior.RaycastParams = self.RaycastParameters
		self.Behavior.HighFidelityBehavior = caster.HighFidelityBehavior.Default
		self.Behavior.MaxDistance = self.MaxDistance
		self.Behavior.Acceleration = self.Gravity
		self.Behavior.CosmeticBulletContainer = self.BulletParent
		self.Behavior.CosmeticBulletProvider = self.PartCache
		self.Behavior.AutoIgnoreContainer = false
	end
	
	return setmetatable(self, setup)
end

function setup:Fire(mousePos, endPos)
	local distBeforeMult = self.DistanceBeforeMultiply
	local player = self.Owner
	local hitbox = self.Hitbox
	local speed = self.Speed
	local damage = self.Damage
	local gravity = self.Gravity
	local multiplier = self.Multiplier
	local behavior = self.Behavior
	local cache = self.PartCache
	local params = self.RaycastParameters
	local attackType = self.AttackType
	local maxSpread = self.MaxSpread
	local minSpread = self.MinSpread
	local direction = (mousePos - endPos).Unit
	local debounce = {}
	local connection = {}
	
	VisualizePos(endPos, BrickColor.Red())
	VisualizePos(mousePos, BrickColor.Green())
	connection[1] = hitbox.LengthChanged:Connect(function(caster, origin, direction, length, velocity, bullet)
		if not bullet then
			return
		end
		
		if bullet:FindFirstChildWhichIsA("Trail") then
			bullet:FindFirstChildWhichIsA("Trail").Enabled = true
		end
		
		local bulletLength = bullet.Size.Z / 2
		local baseCFrame = CFrame.new(origin, origin + direction)
		
		bullet.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength))
	end)
	connection[2] = hitbox.RayHit:Connect(function(caster, ray, velocity, bullet)
		local hitPart = ray.Instance
		local hitPoint = ray.Position
		local normal = ray.Normal

		local model = ray.Instance:FindFirstAncestorOfClass("Model")
		local hum = model and model:FindFirstChildOfClass("Humanoid")
		
		fx:FireAllClients(if hum then effect.blood else effect.hit, hitPart, hitPoint)
		LoadBulletHole(hitPart, hitPoint, ray.Normal, hum)
		
		if hum and not PS:GetPlayerFromCharacter(model) then
			if debounce[hum] then
				return
			end	
			
			--if attackType == "Destroying" then
			--	local blacklist = {"Torso", "HumanoidRootPart"}
			--	local rand = Random.new():NextInteger(1,65)
				
			--	if rand <= 25 and hitPart and not table.find(blacklist, hitPart.Name) then
			--		hitPart.Transparency = 1

			--		task.delay(.4, function()
			--			hitPart:Destroy()
			--		end)
			--	end
			--end
			
			local currDamage = if multiplier[hitPart.Name] then damage * multiplier[hitPart.Name] else damage
			
			if player:DistanceFromCharacter(hitPoint) < distBeforeMult then
				currDamage *= 1.3
			end

			debounce[hum] = true
			hum:TakeDamage(currDamage)
			damageDisplay.new(currDamage, hitPart)
		end
	end)
	connection[3] = hitbox.CastTerminating:Connect(function(caster)
		local cosmeticBullet = caster.RayInfo.CosmeticBulletObject
		
		if cosmeticBullet then
			if behavior.CosmeticBulletProvider then
				pcall(function()
					if cosmeticBullet:FindFirstChildWhichIsA("Trail") then
						cosmeticBullet:FindFirstChildWhichIsA("Trail").Enabled = false
					end
					
					behavior.CosmeticBulletProvider:ReturnPart(cosmeticBullet)
					
					for i,v in ipairs(connection) do
						v:Disconnect()
					end
				end)
			end
		end
	end)
	
	pcall(function()	
		local dirCF = CFrame.lookAt(Vector3.new(), direction)
		local spreadDir = CFrame.fromOrientation(0, 0, math.random(0, math.pi * 2))
		local spreadAngle = CFrame.fromOrientation(math.rad(math.random(minSpread, maxSpread)), 0, 0)
		local direction = (dirCF * spreadDir * spreadAngle).LookVector
		local modifSpeed = (direction * speed)
		
		hitbox:Fire(endPos, direction, modifSpeed, behavior)
	end)
end

return setup

I’m not really sure how I can solve this. Any help is appreciated!

EDIT: I think I solved it. I just moved the muzzle point backwards by a few studs (it’s now practically in the player’s shoulder).

1 Like

Hello, The module fastcast is really incredible but i’m having some maths problem.
Id like to use it to simulate a ball physics but i have 2 problems first of all i can’t quite understand how i could simulate real bounce from the ball and how i could change the velocity to make a curve to the right
https://gyazo.com/12d664dba0bc03494da717901cb21b1f
like this for exemple
if you have any tips i’m taking theù

Hi,
Does anyone know if for a bullet to hit / detect a part has to have transparency on?

I have a part with a decal, and the part is transparent, but it does not detect hits on the part. Meaning the bullets go through it.

If I turn transparency on then it hits it.

Is there anyway around this?

I have also checked that CanCollide and CanQuery are checked = set to True and the HumanoidRootPart, which is big enough to hit, and has Transparent = 1 On

Thanks

Would recommend adding another property to the Fire function to include userdata from the start.
I added it to my copy of fastcast and it’s very useful.

2 Questions,

How do I make the direction it shoots set to the way the barrel part is pointing. (Instead of shooting where the mouse is pointing.)

And also, how do I make multiple barrels?

this is probably a dumb question but is this module safe to be required in every tool without heavy performance issues?

How can I blacklist all no collide able parts?

An easy way is to set them all under the same collision group. That way when you set your raycast params, set the collision group to one that doesn’t collide with that one.
https://developer.roblox.com/en-us/api-reference/datatype/RaycastParams

Alternatively, if it suits you better, group them all under the same model and blacklist the whole model.

how to can make a curve attack with fast cast?

Anyone have any advice for optimising networking with fastcast? I’m using it for my 3rd person gun scripts and the communication between client-server is taking up a lot of data, more than that is acceptable. Anyone have some solutions?

That’s weird, how come? What data are you handing over to the server? A resource I’d refer to regardless: BridgeNet | Insanely optimized, easy-to-use networking library full of utilities, now with roblox-ts! | v1.9.8-beta