Moving npcs on the client issue

Soooo

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:

  1. The enemies stuck all together, even if a new one spawns it instantly teleports to the others and move inside each other

  2. 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)

Example of my issue

Thanks for any help already, I appreciate it

1 Like

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.

im looping on the client already, the only reason im storing pos of each enemy on the server is to prevent exploits

It kinda works now, the only issue there currently is is that the last enemy of a group gets stuck once the first one enters the exit.

(those are 2 groups of enemies)

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

You could either move it down, inside of for i=1, amountOfSpawns do, or put it inside of the task.spawn.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.