Optimizing FastCast and PartCache bullets

Hello, i’ve started using fastcast and partcache lately for optimzing my ‘Conscript’ ai which shoots a lot of bullets at once


Edit: Sorry for the insanely loud conscript-shooting jumpscare
However, even with those improvements, the game still lags with large amounts of them, is there any way I can fix this?

Full Code
local Players = game:GetService('Players')
local RunService = game:GetService('RunService')
local PathfindingService = game:GetService('PathfindingService')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local CollectionService = game:GetService('CollectionService')
local RaycastHitbox = require(ReplicatedStorage.RaycastHitboxV4)
local maxDistance = math.huge
local Anims = script.Parent.R6Anims
local FastCast = require(ReplicatedStorage.FastCastRedux)
local PartCache = require(ReplicatedStorage.PartCache)
local Debris = game:GetService('Debris')

local AI = {}
AI.__index = AI

function AI.new(NPC,WalkAnim,AttackType)
	CollectionService:AddTag(NPC,NPC.Name)
	CollectionService:GetInstanceAddedSignal(NPC.Name):Connect(function(NPC)
		task.wait(5)
		local Team = NPC.Parent.Name
		local NPCProperties = setmetatable({},AI)
		local hum = NPC:WaitForChild('Humanoid')
		local humrp = NPC.HumanoidRootPart
		local WalkAnim = hum:LoadAnimation(WalkAnim)
		local RunOnce = false
		local AttackDB = false
		local ReloadDB = false
		local NPCHitbox = nil
		local idleAnim
		
		if NPC:FindFirstChild('Gun') then
			local Caster = FastCast.new()
			
			local behaviour = Caster.newBehavior()
			local raycastParams = RaycastParams.new()
			raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
			raycastParams.FilterDescendantsInstances = {NPC,workspace.Projectiles,workspace.OreSpawningArea}
			
			
			local bulletTemplate = Instance.new('Part')
			bulletTemplate.Size = Vector3.new(0.2,0.2,12)
			bulletTemplate.Material = Enum.Material.Neon
			bulletTemplate.Color = Color3.fromRGB(255, 225, 0)
			bulletTemplate.CanCollide = false
			--bulletTemplate:SetNetworkOwner(nil)
			
			local CachedBullet = PartCache.new(bulletTemplate,100,workspace.Projectiles)
			
			behaviour.RaycastParams = raycastParams
			behaviour.MaxDistance = 1000
			behaviour.AutoIgnoreContainer = false
			behaviour.CosmeticBulletContainer = workspace.Projectiles
			behaviour.CosmeticBulletProvider = CachedBullet
			
			Caster.LengthChanged:Connect(function(cast,lastpoint,direction,length,velocity,bullet)
				if bullet then
					local bulletlength = bullet.Size.Z/2
					local offset = CFrame.new(0,0,-(length - bulletlength))
					bullet.CFrame = CFrame.lookAt(lastpoint,lastpoint + direction):ToWorldSpace(offset)
				end
			end)
			
			Caster.RayHit:Connect(function(cast,result,velocity,bullet)
				
				if result.Instance:FindFirstAncestorWhichIsA('Model'):FindFirstChild('Humanoid') then
					local enemyHum = result.Instance:FindFirstAncestorWhichIsA('Model').Humanoid
					enemyHum:TakeDamage(5)
				end
				
				CachedBullet:ReturnPart(bullet)
			end)
			
			local function Fire(firepointAttachment,direction,velocity)
				Caster:Fire(firepointAttachment.WorldPosition,direction,velocity,behaviour)
			end
			
			NPCProperties.Fire = Fire
		end
		
		-- makes sure that the loadanimation has an animation object
		if NPC:FindFirstChild('idleAnim') then
			idleAnim = hum:LoadAnimation(NPC.idleAnim)
			NPCProperties.IdleAnim = idleAnim
			idleAnim:Play()
		end
		
		NPC.PrimaryPart:SetNetworkOwner(nil)
		
		NPCProperties.NPC = NPC
		NPCProperties.Humanoid = hum
		NPCProperties.humrp = humrp
		NPCProperties.hum = hum
		NPCProperties.NPCHitbox = NPCHitbox
		NPCProperties.AttackDB = AttackDB
		NPCProperties.RunOnce = RunOnce
		NPCProperties.Team = Team
		NPCProperties.WalkAnim = WalkAnim
		NPCProperties.ReloadDB = ReloadDB
		
		hum.Died:Connect(function()
			NPC:BreakJoints()
			Debris:AddItem(NPC,5)
		end)
		
		--Pathfinding
		local CoreOfFactory = workspace.CentreOfFactory

		local function findTarget()
			local team
			local MaxDistance
			if Team == 'Allies' then
				team = workspace.Enemies:GetChildren()
				maxDistance = 200
			else
				team = workspace.Allies:GetChildren()
				maxDistance = math.huge
			end
			local nearestTarget

			for _,enemy in pairs(team) do
				if enemy then
					if enemy.Humanoid.Health > 0 then
						local target = enemy
						local distance = (humrp.Position - target.HumanoidRootPart.Position).Magnitude

						if distance < maxDistance then
							nearestTarget = target
							MaxDistance = distance
						end
					end
				end
			end
			return nearestTarget
		end

		local function agro()
			WalkAnim:Play()
			repeat task.wait(0.05)
				local target = findTarget()
				
				if target and target.HumanoidRootPart then
					local pos1 = humrp.Position - Vector3.new(0,humrp.Position.Y,0)
					local pos2 = target.HumanoidRootPart.Position - Vector3.new(0,target.HumanoidRootPart.Position.Y,0)
					local distance = (pos1 - pos2).Magnitude
					local whenToStop = 4
					
					if AttackType == 'Gun' then
						whenToStop = 100
					end

					if distance <= whenToStop then
						WalkAnim:Stop()
							NPCProperties.target = target
							AI[AttackType](NPCProperties)
					else
						local targetVel = target.PrimaryPart.Velocity
						local leadBy = 7
						local lead
						
						
						if targetVel.Magnitude < 0.1 then
							lead = Vector3.new(0, 0, 0)
						else
							lead = targetVel.Unit * leadBy
						end
						hum:MoveTo(target.PrimaryPart.Position + lead)
					end
				end
			until target == nil or target.Humanoid.Health == 0 or (humrp.Position - target.HumanoidRootPart.Position).Magnitude > maxDistance
		end
		
		local function pathfindAgro()
			
			local path = PathfindingService:CreatePath()
			
			local target = findTarget()

			local success, errorMessage = pcall(function()
				path:ComputeAsync(humrp.Position, target.HumanoidRootPart.Position)
			end)
			
			if idleAnim then
				idleAnim:Play()
			end
			
			WalkAnim:Play()
			if success and path.Status == Enum.PathStatus.Success then
				local waypoints = path:GetWaypoints()
				for _,waypoint in pairs(waypoints) do

					if waypoint.Action == Enum.PathWaypointAction.Jump then
						hum.Jump = true
					end

					if target and (humrp.Position - target.HumanoidRootPart.Position).Magnitude < math.huge then
						AI[AttackType](NPCProperties)
					end

					hum:MoveTo(waypoint.Position)
				end
			end
		end
		
		NPCProperties.pathfindAgro = pathfindAgro

		local function walkToCore()
			if Team == 'Enemies' then
				local path = PathfindingService:CreatePath()

				local success, errorMessage = pcall(function()
					path:ComputeAsync(humrp.Position, CoreOfFactory.Position)
				end)
				WalkAnim:Play()
				if success and path.Status == Enum.PathStatus.Success then
					local waypoints = path:GetWaypoints()
					for _,waypoint in pairs(waypoints) do

						if waypoint.Action == Enum.PathWaypointAction.Jump then
							hum:ChangeState(Enum.HumanoidStateType.Jumping)
						end

						if findTarget() and (humrp.Position - findTarget().HumanoidRootPart.Position).Magnitude < maxDistance then
							WalkAnim:Play()
							agro()
						end

						hum:MoveTo(waypoint.Position)
						hum.MoveToFinished:Wait()
					end
				end
				WalkAnim:Stop()
			else
				if findTarget() and (humrp.Position - findTarget().HumanoidRootPart.Position).Magnitude < maxDistance then
					WalkAnim:Play()
					agro()
				else
					WalkAnim:Stop()
				end
			end
		end

		walkToCore()
	end)
end

function AI.Punch(self)
	if self.AttackDB == false then
		self.AttackDB = true
		self.WalkAnim:Stop()
		self.NPC:SetPrimaryPartCFrame(CFrame.new(self.NPC.HumanoidRootPart.Position,Vector3.new(self.target.HumanoidRootPart.Position.X,self.NPC.HumanoidRootPart.Position.Y,self.target.HumanoidRootPart.Position.Z)))
		local AttackAnim = self.hum:LoadAnimation(Anims.Attack)
		local Damage = 15
		local delay = 1
		
		if self.RunOnce == false then
			self.RunOnce = true
			self.NPCHitbox = RaycastHitbox.new(self.NPC['Right Arm'])
			self.NPCHitbox:SetPoints(self.NPC['Right Arm'],{Vector3.new(0,0,1),Vector3.new(-1,1,3),Vector3.new(-3.5,1,3)})
		end

		AttackAnim:Play()
		self.NPCHitbox:HitStart()

		self.NPCHitbox.OnHit:Connect(function(hit,humanoid)
			if humanoid.Parent.Name ~= self.NPC.Name and not humanoid:FindFirstAncestor(self.Team) then
				humanoid:TakeDamage(Damage)
			end
		end)
		coroutine.wrap(function()
			AttackAnim.Stopped:Wait()
			self.NPCHitbox:HitStop()
			task.wait(delay)
			self.AttackDB = false
			self.WalkAnim:Play()
		end)()
	end
end

function AI.Sword(self)
	if self.AttackDB == false then
		self.AttackDB = true
		self.IdleAnim:Stop()
		self.WalkAnim:Stop()
		self.NPC:SetPrimaryPartCFrame(CFrame.new(self.NPC.HumanoidRootPart.Position,Vector3.new(self.target.HumanoidRootPart.Position.X,self.NPC.HumanoidRootPart.Position.Y,self.target.HumanoidRootPart.Position.Z)))
		local AttackAnim = self.hum:LoadAnimation(Anims.Sword)
		local Damage = 15
		local delay = 1

		if self.RunOnce == false then
			self.RunOnce = true
			self.NPCHitbox = RaycastHitbox.new(self.NPC['Right Arm'])
			self.NPCHitbox:SetPoints(self.NPC['Right Arm'],{Vector3.new(0,-1,-1),Vector3.new(0,-2,-2),Vector3.new(0,-1,-3),Vector3.new(0,-2,-4),
				Vector3.new(0,-1,1),Vector3.new(0,-2,0),Vector3.new(0,-1,1)})
		end

		AttackAnim:Play()
		self.NPCHitbox:HitStart()

		self.NPCHitbox.OnHit:Connect(function(hit,humanoid)
			if humanoid.Parent.Name ~= self.NPC.Name and not humanoid:FindFirstAncestor(self.Team) then
				humanoid:TakeDamage(Damage)
			end
		end)
		coroutine.wrap(function()
			AttackAnim.Stopped:Wait()
			self.IdleAnim:Play()
			self.WalkAnim:Play()
			self.NPCHitbox:HitStop()
			task.wait(delay)
			self.AttackDB = false
		end)()
	end
end

function AI.Gun(self)
	if self.AttackDB == false then
		self.AttackDB = true
		self.IdleAnim:Stop()
		self.WalkAnim:Stop()
		self.NPC:SetPrimaryPartCFrame(CFrame.new(self.NPC.HumanoidRootPart.Position,Vector3.new(self.target.HumanoidRootPart.Position.X,self.NPC.HumanoidRootPart.Position.Y,self.target.HumanoidRootPart.Position.Z)))
		local AttackAnim = self.hum:LoadAnimation(self.NPC.attackAnim)
		local ReloadAnim = self.hum:LoadAnimation(self.NPC.reloadAnim)
		local Damage = self.NPC.Gun.Damage.Value
		local fireRate = self.NPC.Gun.fireRate.Value
		local MagSize = self.NPC.Gun.MagSize
		local FullMagSize = self.NPC.Gun.FullMagSize.Value
		local firePoint = self.NPC.Gun.FirePoint
		local attackSound = self.NPC.Gun:WaitForChild('Handle').attackSound
		local reloadSound = self.NPC.Gun:WaitForChild('Handle').reloadSound

		local direction = self.target.HumanoidRootPart.Position - firePoint.Position
		local raycastParams = RaycastParams.new()
		local result = workspace:Raycast(firePoint.Position,direction,raycastParams)

		if result and result.Instance:FindFirstAncestor(self.target.Name) then
			if MagSize.Value > 0 then
				MagSize.Value -= 1
				AttackAnim:Play()
				attackSound:Play()
				AttackAnim:AdjustSpeed(2)
				
				self.Fire(firePoint.Attachment,direction,750)
				
			else
				if self.ReloadDB == false then
					self.ReloadDB = true
					reloadSound:Play()
					ReloadAnim:Play()
					coroutine.wrap(function()
						ReloadAnim.Stopped:Wait()
						self.IdleAnim:Play()
					end)()
					reloadSound.Ended:Wait()
					MagSize.Value = FullMagSize
					self.ReloadDB = false
				end
			end
		else
			self.pathfindAgro()
		end

		coroutine.wrap(function()
			self.IdleAnim:Play()
			task.wait(fireRate)
			self.AttackDB = false
		end)()
		end
end


return AI

Relevant code:

		if NPC:FindFirstChild('Gun') then
			local Caster = FastCast.new()
			
			local behaviour = Caster.newBehavior()
			local raycastParams = RaycastParams.new()
			raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
			raycastParams.FilterDescendantsInstances = {NPC,workspace.Projectiles,workspace.OreSpawningArea}
			
			
			local bulletTemplate = Instance.new('Part')
			bulletTemplate.Size = Vector3.new(0.2,0.2,12)
			bulletTemplate.Material = Enum.Material.Neon
			bulletTemplate.Color = Color3.fromRGB(255, 225, 0)
			bulletTemplate.CanCollide = false
			--bulletTemplate:SetNetworkOwner(nil)
			
			local CachedBullet = PartCache.new(bulletTemplate,100,workspace.Projectiles)
			
			behaviour.RaycastParams = raycastParams
			behaviour.MaxDistance = 1000
			behaviour.AutoIgnoreContainer = false
			behaviour.CosmeticBulletContainer = workspace.Projectiles
			behaviour.CosmeticBulletProvider = CachedBullet
			
			Caster.LengthChanged:Connect(function(cast,lastpoint,direction,length,velocity,bullet)
				if bullet then
					local bulletlength = bullet.Size.Z/2
					local offset = CFrame.new(0,0,-(length - bulletlength))
					bullet.CFrame = CFrame.lookAt(lastpoint,lastpoint + direction):ToWorldSpace(offset)
				end
			end)
			
			Caster.RayHit:Connect(function(cast,result,velocity,bullet)
				
				if result.Instance:FindFirstAncestorWhichIsA('Model'):FindFirstChild('Humanoid') then
					local enemyHum = result.Instance:FindFirstAncestorWhichIsA('Model').Humanoid
					enemyHum:TakeDamage(5)
				end
				
				CachedBullet:ReturnPart(bullet)
			end)
			
			local function Fire(firepointAttachment,direction,velocity)
				Caster:Fire(firepointAttachment.WorldPosition,direction,velocity,behaviour)
			end
			
			NPCProperties.Fire = Fire
		end
function AI.Gun(self)
	if self.AttackDB == false then
		self.AttackDB = true
		self.IdleAnim:Stop()
		self.WalkAnim:Stop()
		self.NPC:SetPrimaryPartCFrame(CFrame.new(self.NPC.HumanoidRootPart.Position,Vector3.new(self.target.HumanoidRootPart.Position.X,self.NPC.HumanoidRootPart.Position.Y,self.target.HumanoidRootPart.Position.Z)))
		local AttackAnim = self.hum:LoadAnimation(self.NPC.attackAnim)
		local ReloadAnim = self.hum:LoadAnimation(self.NPC.reloadAnim)
		local Damage = self.NPC.Gun.Damage.Value
		local fireRate = self.NPC.Gun.fireRate.Value
		local MagSize = self.NPC.Gun.MagSize
		local FullMagSize = self.NPC.Gun.FullMagSize.Value
		local firePoint = self.NPC.Gun.FirePoint
		local attackSound = self.NPC.Gun:WaitForChild('Handle').attackSound
		local reloadSound = self.NPC.Gun:WaitForChild('Handle').reloadSound

		local direction = self.target.HumanoidRootPart.Position - firePoint.Position
		local raycastParams = RaycastParams.new()
		local result = workspace:Raycast(firePoint.Position,direction,raycastParams)

		if result and result.Instance:FindFirstAncestor(self.target.Name) then
			if MagSize.Value > 0 then
				MagSize.Value -= 1
				AttackAnim:Play()
				attackSound:Play()
				AttackAnim:AdjustSpeed(2)
				
				self.Fire(firePoint.Attachment,direction,750)
				
			else
				if self.ReloadDB == false then
					self.ReloadDB = true
					reloadSound:Play()
					ReloadAnim:Play()
					coroutine.wrap(function()
						ReloadAnim.Stopped:Wait()
						self.IdleAnim:Play()
					end)()
					reloadSound.Ended:Wait()
					MagSize.Value = FullMagSize
					self.ReloadDB = false
				end
			end
		else
			self.pathfindAgro()
		end

		coroutine.wrap(function()
			self.IdleAnim:Play()
			task.wait(fireRate)
			self.AttackDB = false
		end)()
		end
end

I previously used normal instancing projectiles with bodyvelocity, but that proved so inefficient that my game lagged with only 4 of these units spawned in.

I’ve impleneted partcache and fastcast as an improvement, it did seem to speed up the game a bit but I still can’t run it smoothly with a lot of units shooting at once.

I want to remove the lag from this code if I can, and it does sound ambitious.
Please help, I kinda need to optimize this script quite soon or else I won’t have anything to do for the next week

4 Likes

There’s not really much you can do to optimize it I believe.

also it’s not AI

1 Like

I have question about the AI and gun usage
Is the bullet hitscan? – if so you dont need fastcast
Are you rendering bullets on the client? – usually render stuff on the client and not on the server for better performance
You also dont have a Caster.CastEnd or whatever the destroy part of the ray is so you are only returning the part IF it hit also

if result.Instance:FindFirstAncestorWhichIsA('Model'):FindFirstChild('Humanoid') then
        local enemyHum =  result.Instance:FindFirstAncestorWhichIsA('Model').Humanoid
	enemyHum:TakeDamage(5)
end
				

can be

local enemyHum = result.Instance:FindFirstAncestorWhichIsA('Model'):FindFirstChild('Humanoid')
if enemyHum then
	enemyHum:TakeDamage(5)
end

the edit was cause i accidentaly submitted it too early

3 Likes

Render the bullets on every client not the server do that by using FireAllClients on an RemoteEvent.

Remember to protect your RemoteEvents!

Also one quick thing to note please use Animator:LoadAnimation and PivotTo instead it will most likely improve performance with your code!

4 Likes

Sorry about that, the script is included in an AI named ‘Conscript’, that is why I mentioned it.

The bullet isn’t really hitscan, my AI.Gun script casts one raycast (without fastcast) to check if a player is in sight, and if it isn’t, it fires an inefficient pathfinding script to get closer to the player, then the local function ‘Fire’ is fired which does FastCast:Fire() which fires a partcache bullet at the player.

I am not rendering bullets on the client, would it make a difference as the ai will always be visible by the player pretty much and it would be a pain to make it client based, I would probably have to spend atleast a whole day debugging the mess that it would create.

thanks for the Caster.CastEnd tip though! I will implement that in right now.

animator:LoadAnimation()? Is that located in the humanoid? Thanks for the tip, but again, I am skeptical about migrating this whole script to the client, would that make a big impact?

Yes it is located in the humanoid. And yes it would make a big impact!

1 Like

Hi, sorry for not marking your post as the answer, I am just waiting for a response on this dev forum post to see if it reduces the lag, I will mark your post as the answer once I get an answer on this post (god that was confusing to say), thanks again.

1 Like

Sorry, but it didn’t work, I tried rendering fastcast bullets and doing everything on the client, but that just made it much worse, here is a video showing the performance:


I spent several days working on this, I am tired, I don’t know what to do, I made a topic about this as you saw and it didn’t get any replies for a week, here is the script:

--edit: this is a local script
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local Remotes = ReplicatedStorage.Events
local Event = Remotes[script.Name]
local FastCast = require(ReplicatedStorage.FastCastRedux)
local PartCache = require(ReplicatedStorage.PartCache)

ReplicatedStorage.Events.FastCastClient.OnClientEvent:Connect(function(NPC,Type,attachment,direction,velocity)
	if Type == 'Constructor' then
		local Caster = FastCast.new()

		local behaviour = Caster.newBehavior()
		local raycastParams = RaycastParams.new()
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		raycastParams.FilterDescendantsInstances = {NPC,workspace.Projectiles,workspace.OreSpawningArea}


		local bulletTemplate = Instance.new('Part')
		bulletTemplate.Size = Vector3.new(0.2,0.2,12)
		bulletTemplate.Material = Enum.Material.Neon
		bulletTemplate.Color = Color3.fromRGB(255, 225, 0)
		bulletTemplate.CanCollide = false

		local CachedBullet = PartCache.new(bulletTemplate,100,workspace.Projectiles)

		behaviour.RaycastParams = raycastParams
		behaviour.MaxDistance = 300
		behaviour.AutoIgnoreContainer = false
		behaviour.CosmeticBulletContainer = workspace.Projectiles
		behaviour.CosmeticBulletProvider = CachedBullet

		Caster.LengthChanged:Connect(function(cast,lastpoint,direction,length,velocity,bullet)
			if bullet then
				local bulletlength = bullet.Size.Z/2
				local offset = CFrame.new(0,0,-(length - bulletlength))
				bullet.CFrame = CFrame.lookAt(lastpoint,lastpoint + direction):ToWorldSpace(offset)
			end
		end)
		
		Caster.CastTerminating:Connect(function(cast)
			local cosmeticBulletObject = cast.RayInfo.CosmeticBulletObject
			if cosmeticBulletObject ~= nil then
				CachedBullet:ReturnPart(cosmeticBulletObject)
			end
		end)

		Caster.RayHit:Connect(function(cast,result,velocity,bullet)

			local enemyHum = result.Instance:FindFirstAncestorWhichIsA('Model'):FindFirstChild('Humanoid')
			if enemyHum and result.Instance:FindFirstAncestorWhichIsA('Folder').Name == 'Allies' then
				ReplicatedStorage.Events.FastCastClient:FireServer(enemyHum,5)
			end
		end)

		function Fire(firepointAttachment,direction,velocity)
			Caster:Fire(firepointAttachment.WorldPosition,direction,velocity,behaviour)
		end

	elseif Type == 'Fire' then
		Fire(attachment,direction,velocity)
	end
end)

I am stuck, I have no clue what i’m doing wrong, I tried doing partcache on the server and sending it to the client, it didn’t work, I tried doing things on the server and only doing rendering parts on the client, it didn’t work, I have no clue what to do, please advise me on things I can do to get an answer on this issue, how to write a dev forum post better so that it gets a reply, I just want to get this thing done

1 Like

Hmmm… I am making a Gore System in the meantime however afterwards I will focus on one of my most important projects which is titled: RenderingService you can render objects in a VPF and in theory it should reduce lag greatly objects such as: Bullets, Bulletholes, Blood, etc.

2 Likes

You’re creating a new caster for everytime the client makes the bullet, there should only be one caster. In the FastCast API, it only states that it should act as the entire gun or projectile system and not a bullet

That is why I send an event with the ‘Type’ of constructor every time an npc is created, to build the caster once and then I send the ‘Type’ of Fire whenever I want the npc to fire, it sounds confusing but I am not making a new caster every time an npc fires, here is a picture showing the results of what happens when I add ‘print(new caster created!)’ on top of the ‘if Type == ‘Constructor’ then’ and spawn 20 npcs:
image
would making a single caster for 20 or more npcs work?

1 Like

How I do it is i create a server side module which does all the calculation and damage and have the client do all the visuals and stuff

Server side

local module = {}
local fastcast = FASTCAST
local caster = fastcast.new()
local castBehaviour = fastcast.newBehaviour()
local projectileVisualizer = YOUR REMOTE EVENT

function module.fire(origin,direction,velocity,acceleration,distance,raycastparams,projectile)
     castBehaviour.Acceleration = acceleration
     castBehaviour.MaxDistance = distance
     castBehaviour.RaycastParams = raycastparams
     caster:Fire(origin,direction,velocity,castBehaviour) 
     projectileVisualizer:FireAllClients(origin,direction,velocity,acceleration,distance,raycastparams,projectile)
end
caster.RayHit:Connect(function(...)
        do your damage stuff here
end

return module
local fastcast = FASTCAST
local caster = fastcast.new()
local castBehaviour = fastcast.newBehaviour()
local projectileVisualizer = YOUR REMOTE EVENT

projectileVisualizer.OnClientEvent:Connect(function(origin,direction,velocity,acceleration,distance,raycastparams,projectile)
     castBehaviour.CosmeticBulletTemplate = projectile
     castBehaviour.CosmeticBulletContainer = workspace
     castBehaviour.Acceleration = acceleration
     castBehaviour.MaxDistance = distance
     castBehaviour.RaycastParams = raycastparams
     caster:Fire(origin,direction,velocity,castBehaviour) 
end
caster.LengthChanged:Connect(function(cast,lastpoint,direction,displacement,vel,part)
    part.CFrame = CFrame.new(lastpoint,lastpoint + (dir*displacement))
end)

Honestly, theres probably much more efficient ways to do this but this has always worked for me. Doing all the damage calculations on the server also makes it more secure

1 Like

Honestly I don’t know what to do anymore


This was meant to be a very simple projectile system, how do the games like ‘The zombies onslaught’ manage all of those projectiles that they create per second?
its even worse now, with it reaching up to 8% but its logical to handle the rayhit on the server instead of the client, I am completely stuck.

edit: now I am casting 3 rays at once instead of a single one on the client

edit: the script itself isn’t lagging really, it spikes massively when the player respawns/gets targetted by the ai by about 4%, it seems to sit at a stable 2% when the player isn’t targetted.

edit: not really I am going insane I have no clue what to do

Yeah I somehow need to handle like 600 or 700 raycasts (and projectiles (in this case using fastcast and partcache)) every second, and I know it is possible, but I am at a loss, and this topic died

I’m gonna start working on the module now!

1 Like

did you end up finishing that module? just checking lol

1 Like

You will definitely need to use parallel lua for that amount of raycasts (every frame?). You might be able to get all those to run at 60 fps on most devices, but, why do you need that many raycasts?

1 Like

No, I never did finish it, I abandoned the project like a year ago after I couldnt fix the performance issues sadly.