How to make sure that enemies will sync in my tds game?

Alright, so, I’m making a TDS game.
However, I wanted to handle spawning on the client to prevent server lag (event is fired on all clients);

function spawn_enemy(data)
	--
	local model = data.model:Clone()
	local humanoid = model.Humanoid
	local animator = humanoid.Animator
	--
	local attributes = model.attributes
	local health_data = model.health_data
	local resistances = model.resistances
	local stats = model.stats
	--
	local map = _G.map
	model.Parent = map
	--	
	local thread
	local walk_anim = animator:LoadAnimation(model.walk)
	walk_anim:Play()
	--
	model.Parent = _G.map.enemies
	_G.follow_path({
		["model"] = model; 
		["nodes_folder"] = _G.map.nodes_folder
	}) -- they follow the nodes
	--
end
spawn_enemy_event.OnClientEvent:Connect(spawn_enemy)

An issue is latency.
For some clients, the enemy will spawn later/earlier than other clients, making it inconsistent.
And if someone is really laggy, an enemy might make it to their base before they even realize that it’s spawned yet.

How would I achieve this? How would I sync the enemy position with all clients?

I believe this would fall under “rollback” networking although I don’t actually understand the buzzword. To sync properly, you have the server notify each client to spawn the enemy along with a timestamp of when the enemy was created on the server. The client then receives this and jumps the enemy ahead to account for the delay.

E.g Server says to client “I spawned the boss troop at time t = 5s”
Client receives the remote event at time t = 7s to spawn the troop
Client sees the troop was spawned at t=5 as this is contained in the message
Client calculates a 2s delay and rather than spawning the troop from the start, spawn it 2s ahead from the starting point to account for the delay.

This reduces slight lag and ensures the troops are synced however “if someone is really laggy, an enemy might make it to their base before they even realize that it’s spawned yet” has no real solution as you cannot stop latency only account (to a certain extent) for it.

2 Likes

If your enemies are travelling at a linear speed (constant studs/s), this can be achieved pretty easily by using a time function.

How It Could Work

If you have enemies that travel at a constant linear speed between points, you can determine the
position of the enemy easily solely based on time. Here is an example of this with an array of positions (nodes):

function getPositionFromT(t: number, points: table, speed: number)
	local baseT = 0
	for i,point in ipairs(points) do
		local previous = points[i-1]
		if not previous then continue end
		local distance = (point - previous).Magnitude
		if t - baseT < distance/speed then
			local delta = t - baseT
			return previous + (point-previous).Unit*speed*delta
		else
			baseT += distance/speed
		end
	end
	return points[#points]
end

Basically, this code goes through each point and determines if the point was already passed based on t. If it was passed, then continue to the next point. If it isn’t passed yet, the position is returned between the point and previous based on the relevant time and speed.

Now that you can get the position from any time, you can now determine the instantaneous position of an enemy based on when it was spawned by the server.

When a server spawns an enemy and tells the client, it can also tell the client the workspace:GetServerTimeNow() value when the enemy was spawned. Now, when the client updates the positions of the enemies (presumably every RenderStepped), t would equal to workspace:GetServerTimeNow() - spawnTime.

Since this system is time-reliant, no matter how laggy the client is, the position of the enemies when rendered will always be the same on every update.

Let me know if you have any questions!

2 Likes

‎ ‎ ‎ thank‎ ‎ :slightly_smiling_face: ‎ ‎ ‎ ‎ ‎ ‎

2 Likes