Hello, recently i’ve been experiencing severe issues with rendering a large quantity of bullets (hundred or fifty a second) in my AI script, as its been causing a severe amount of lag.
I am using FastCast and PartCache to optimize everything, yet it still lags.
This is a direct continuation from my previous post which discusses this issue further, and i’ve been recommended to ‘render the bullets on the client not the server’, but I have no idea how to do this, here is the script that i’ve done so far on a local script in replicatedstorage:
-- 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)
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,31,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)
local enemyHum = result.Instance:FindFirstAncestorWhichIsA('Model'):FindFirstChild('Humanoid')
if enemyHum then
enemyHum:TakeDamage(5)
end
CachedBullet:ReturnPart(bullet)
end)
local function Fire(firepointAttachment,direction,velocity)
Caster:Fire(firepointAttachment.WorldPosition,direction,velocity,behaviour)
end
-- I have no idea what to put here, I need to be able to somehow do the Fire() function from the server
-- should I look into things like remote functions or something?
end)
-- module/server script
if NPC:FindFirstChild('Gun') then
ReplicatedStorage.Events.FastCastClient:FireAllClients(NPC)
NPCProperties.Fire = Fire -- I need the Fire function from the client to be here, but I don't know how to achieve that
end
full script, I don't know if it will be necessary but just incase
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
ReplicatedStorage.Events.FastCastClient:FireAllClients(NPC)
NPCProperties.Fire = Fire -- I need the Fire function from the client to be here, but I don't know how to achieve that
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
please help