What do I want to achieve?
I am making a td game and my plan was to create enemies on the server as pure data and create a model for each enemy on the client. Those 2 cycles would run parallel to each other bc it’s not good for recv to send the enemies pos from the server to client every frame.
What is my problem?
Well, I’m currently at the part where I create the enemy models on the client. It all works well like moving and creating the model but there are some problems:
The enemies stuck all together, even if a new one spawns it instantly teleports to the others and move inside each other
The loop which moves the enemies somehow already runs before an enemy even exists (which should not be possible since I send a remote to the client telling it to spawn and move the enemy model). This results in the enemy spawning out of nowhere suddenly and not from the first waypoint where I want it to be.
How have I tried?
I send a remote for each group of enemies in a wave from the server to the client (a local script in starter player scripts) which then creates the enemy model referenced from a table having all the enemy data (like its corresponding model) inside it. Then I use a optimized leap movement module to move them
What may be the problem?
It could be that the script takes the newEnemy variable for each and every enemy and not for each enemy on its own, that may be why they all stuck together
Code
local function receiveData(stream, id, amount, succession, startTime)
--// Received Data
local enemyID = buffer.readu16(stream, 0)
local amountOfSpawns = buffer.readu16(stream, 2)
local successionOfSpawns = buffer.readu16(stream, 4)
print(enemyID, amountOfSpawns, successionOfSpawns)
local Positions = {}
for waypoint = 1, #workspace.Main.Map.Waypoints:GetChildren(), 1 do
table.insert(Positions, workspace.Main.Map.Waypoints[waypoint].Position)
end
local movementLoop
for i=1, amountOfSpawns do
task.wait(successionOfSpawns)
warn("loop")
local detectedEnemy = enemyList[enemyID]
local speed = detectedEnemy.Speed
local height = detectedEnemy.height
local newEnemy = detectedEnemy.model:Clone()
newEnemy.Parent = clientModels
animateEnemyEvent:Fire("walk", newEnemy, enemyID)
local NewPath = bezierPath.new(Positions,3) -- create new path for moduel to use
local totalTime = NewPath:GetPathLength() / speed
task.spawn(function() -- function to make enemies move
movementLoop = runService.Heartbeat:Connect(function()
local t = (workspace:GetServerTimeNow() - startTime) / (totalTime)
if newEnemy.PrimaryPart then
newEnemy:SetPrimaryPartCFrame(NewPath:CalculateUniformCFrame(t) + Vector3.new(0,height,0))
end
if t >= 1 then -- remove enemy and loop once last waypoint is reached
newEnemy:Destroy()
movementLoop:Disconnect()
end
end)
end)
end
end
(this function is being triggered by a remote event)
Your clumping problem may be related to the startTime being passed by recieveData.
I am basing this on the assumption that startTime is supposed to be when the Enemy spawned in. If its based on when the wave starts however, I’d suggest initializing a seperate variable.
What’s likely happening is that the startTime is a solid 5 seconds ago when the next enemy spawns in, meaning startTime is not when the 2nd Enemy Spawned, but the 1st enemy, so its timing is based on the 1st enemy. What you’d want is for each enemy to have their own timing.
You could do the following instead. Initialize the startTime inside of the task.spawn. An example of this could be:
task.spawn(function() -- function to make enemies move
local startTime = workspace:GetServerTimeNow()
movementLoop = runService.Heartbeat:Connect(function()
local t = (workspace:GetServerTimeNow() - startTime) / (totalTime)
if newEnemy.PrimaryPart then
newEnemy:SetPrimaryPartCFrame(NewPath:CalculateUniformCFrame(t) + Vector3.new(0,height,0))
end
if t >= 1 then -- remove enemy and loop once last waypoint is reached
newEnemy:Destroy()
movementLoop:Disconnect()
end
end)
end)
This also means that receiveData, does not need a startTime passed as a parameter.
oh ok, I did the start time thingy bc a guy who usually does those type of systems said it was better to make the server and the client sync better. (The server does the same but it doesn’t have a model, it only gets pure position values)
That is true, but I believe its would likely would be more optimal for other implementations of the system. The way that you have set it up involves spawning all of the NPCs on the Client Side in a loop, rather than server spawning each individual NPC, with the server having the loop instead.
The issue is cased by movementLoop, the connection to the Heartbeat event, kept getting overwritten by each new enemy, and so in the end it only Disconnects the last Heartbeat event, being the last NPC.
Instead you can move local movementLoop inside of the for loop, so that its initalized for each individual NPC.
What loop do I move it in? (for or runService). I fell like it would not make sense for the loop to be In the run service loop, correct me if im wrong. Also imma go off now so I’ll see answers late. Thanks