I’ve used to have my enemies system simply use the MoveTo function on the humanoid but after some testing I realized the game gets quite a high Recv when alot of enemies are spawned in so after some looking around I came across this post which seems to have solved the issue, I’m trying to use the code provided in it but instead this happens:
Not only that I don’t know what seems to be the issue, this has a lot of complications for example when stunning the enemy how would that work?
here’s the client code I used:
--- Some client stuff
local Precision = 10^2
local tableOfPaths = game.ReplicatedStorage.RequestPaths:InvokeServer()
local tableOfEnemies = {}
function Decompile(EnemyData)
return {
["EnemyId"] = EnemyData[1],
["EnemyPosition"] = EnemyData[2],
["EnemyRotation"] = EnemyData[3],
["Node"] = EnemyData[4],
["Speed"] = EnemyData[5],
["Path"] = tableOfPaths[EnemyData[6]],
["SyncTime"] = workspace.DistributedGameTime - EnemyData[7],
["Done"] = false,
}
end
game.ReplicatedStorage.SpawnedEnemy.OnClientEvent:Connect(function(EnemyInfo)
local ClientTableEnemyInfo = Decompile(EnemyInfo)
local key = ClientTableEnemyInfo["EnemyId"]
tableOfEnemies[key] = ClientTableEnemyInfo
local Precision = 10^2
local CFrameNoOrientation = CFrame.new(ClientTableEnemyInfo["EnemyPosition"].X / Precision, ClientTableEnemyInfo["EnemyPosition"].Y / Precision, ClientTableEnemyInfo["EnemyPosition"].Z / Precision)
local CFrameWithOrientation = CFrameNoOrientation * CFrame.Angles(0, ClientTableEnemyInfo["EnemyRotation"].Y / Precision, 0)
local cloneClientSide = game.ReplicatedStorage.Enemies.Models.Test:Clone()
cloneClientSide.Name = ClientTableEnemyInfo["EnemyId"]
cloneClientSide.Parent = game.Workspace.Enemies
cloneClientSide:PivotTo(CFrameWithOrientation)
end)
function HandleMovement()
for i,Enemy in pairs(tableOfEnemies) do
local clientTarget = game.Workspace.Enemies[Enemy["EnemyId"]]
if Enemy.Done then
tableOfEnemies[i] = nil
game.Debris:AddItem(clientTarget, 0)
else
clientTarget.CFrame = clientTarget.CFrame:ToWorldSpace(CFrame.new(0, 0, -0.1 * Enemy.Speed))
local magnitude = (clientTarget.CFrame.Position - Enemy.Path[Enemy.Node + 1].Position).Magnitude
if magnitude <= 1 then
Enemy.Node += 1
if Enemy.Node == #Enemy.Path then
Enemy.Done = true
else
if Enemy.SyncTime then
clientTarget.CFrame = CFrame.new(Enemy.Path[Enemy.Node].Position, Enemy.Path[Enemy.Node + 1].Position + (Enemy.Path[Enemy.Node + 1].Position * Enemy.SyncTime))
Enemy.SyncTime = nil
else
clientTarget.CFrame = CFrame.new(Enemy.Path[Enemy.Node].Position, Enemy.Path[Enemy.Node + 1].Position)
end
end
end
end
end
end
while true do
task.wait(0.01)
HandleMovement()
end
and this is the server code:
local SpawnedEnemy = game.ReplicatedStorage:WaitForChild("SpawnedEnemy")
--- Spline Calculations
-- example for p0 with tension
--local dot = game.ServerStorage.dot
local tableOfPaths = {}
function DrawPath()
local NodesTable = {}
local tensionMain = math.random(1, 100)/100 -- math.random(90, 100)/100
local counter = 0
-- To DO: the point calculation is already in this table, just run DrawPath() for each NPC individually and then have them recalculate where to go
-- this will cause them to be scattered and different
local Points = {
}
for i=1, #workspace.Map.Waypoints:GetChildren(), 1 do
Points[i] = workspace.Map.Waypoints[i].Position
end
-- print("Tension is: " .. tensionMain)
local NumPoints = #Points
local LastPoint = Points[1]
for i = 1, NumPoints - 1 do
local p0 = Points[i - 1] or Points[1]
local p1 = Points[i]
local p2 = Points[i + 1]
local p3 = Points[i + 2] or Points[NumPoints]
-- print(i)
for j = 0, 1, 0.05 do
local tension = tensionMain
local t = j
local t2 = t * t
local t3 = t2 * t
local p =
(
(-tension * t + 2 * tension * t2 - tension * t3) * p0 +
(2 + (tension - 6) * t2 + (4 - tension) * t3) * p1 +
(tension * t - 2 * (tension - 3) * t2 + (tension - 4) * t3) * p2 +
(-tension * t2 + tension * t3) * p3
) * 0.5
--[[
-- EnemyPosVal Object Purposes + Visual Demonstration of Points
local visual_dot = dot:Clone()
visual_dot.Parent = game.Workspace.Nodes
visual_dot.Position = p
-- counter = counter + 1
visual_dot.Name = counter
]]
counter = counter + 1
NodesTable[counter] = CFrame.new(p)
--local visual_dot = Instance.new("Part")
--visual_dot.Size = Vector3.new(1,1,1)
--visual_dot.CanCollide = false
--visual_dot.Anchored = true
--visual_dot.Parent = game.Workspace.Nodes
--visual_dot.Position = p
--visual_dot.Name = counter
end
end
return NodesTable
end
-- Pre-Process Generate a Bunch of Paths so we don't have to generate new ones later on
for i = 1, 100 do
tableOfPaths[i] = DrawPath()
end
function RequestPath()
return tableOfPaths
end
game.ReplicatedStorage.RequestPaths.OnServerInvoke = RequestPath
task.wait(5)
-- NPC Spawning Table Putting Function
local ServerTableOfEnemies = {}
local EnemyId = "1";
function SpawnNPC()
local Precision = 10^2
local EnemyInfo = {
["EnemyId"] = EnemyId;
["Node"] = "1";
["Speed"] = 0.1;
["Path"] = math.random(1,100);
["CFrame"] = CFrame.lookAt(workspace.Map.Start.Spawn.Position, workspace.Map.Waypoints["1"].Position);
["Done"] = false;
}
ServerTableOfEnemies[EnemyInfo.EnemyId] = EnemyInfo;
EnemyId = EnemyId + 1;
-- print(EnemyId)
local ClientEnemyInfo = {
[1] = EnemyInfo.EnemyId;
[2] = Vector3int16.new(EnemyInfo.CFrame.X * Precision, EnemyInfo.CFrame.Y * Precision, EnemyInfo.CFrame.Z * Precision);
[3] = Vector2int16.new(0, 0);
[4] = EnemyInfo.Node; -- Node
[5] = 4; -- Speed
[6] = EnemyInfo.Path; -- Path
[7] = workspace.DistributedGameTime, -- Time in server, used to sync
}
SpawnedEnemy:FireAllClients(ClientEnemyInfo)
end
-- Actual Movement Handling
local ClientTableOfEnemies = {}
function HandleMovement()
for i,Enemy in pairs(ServerTableOfEnemies) do
if (Enemy.Done == true) then
-- in the future fire some event to notify clients about deleting the NPC since its dead or reached the end
-- make another check about the NPC having 0 health or less in the future
-- print("Done")
else
Enemy.CFrame = Enemy.CFrame:ToWorldSpace(CFrame.new(0,0,-0.1 * Enemy.Speed))
local magnitude = (Enemy.CFrame.Position - tableOfPaths[Enemy.Path][Enemy.Node + 1].Position).Magnitude
-- print(Enemy.Node)
if (Enemy.CFrame.Position - tableOfPaths[Enemy.Path][Enemy.Node + 1].Position).Magnitude <= 1 then
Enemy.Node += 1
if Enemy.Node == #tableOfPaths[Enemy.Path] then
Enemy.Done = true
else
Enemy.CFrame = CFrame.new(tableOfPaths[Enemy.Path][Enemy.Node].Position, tableOfPaths[Enemy.Path][Enemy.Node + 1].Position)
end
end
local Precision = 10^2
local EulerAnglesThingy = Vector3.new(Enemy.CFrame:ToEulerAnglesXYZ())
local ClientEnemyInfo = {
[1] = Enemy.EnemyId; -- EnemyId
[2] = Vector3int16.new(Enemy.CFrame.Position.X * Precision, Enemy.CFrame.Position.Y * Precision, Enemy.CFrame.Position.Z * Precision); -- Position
[3] = Vector2int16.new(0, EulerAnglesThingy.Y * Precision); -- Rotation
[4] = Enemy.Node; -- Node
}
ClientTableOfEnemies[i] = ClientEnemyInfo
end
end
end
-- Main Routine
local enemyCount = 50;
wait(5)
task.spawn(function()
for _ = 1, enemyCount do
task.wait(0.25) -- Spawn delay between each unit
SpawnNPC()
end
end)
while true do
task.wait(0.01)
HandleMovement()
end