NPC can't catch player, due to latency? How do I work around it?

I have been stuck on this problem for a while now. My NPCs that are meant to chase players cannot actually catch up to them. I have half a mind to just make their attacks hit from their current following distance.

You can see this behaviour in the video below.

As you can see, when I stop, the zombie is able to catch up. However, when I move, the zombie lags behind constantly, despite having a faster walkspeed. I’m 99% sure this due to latency - my character’s position on the server is obviously going to lag behind, which is what my NPC is targetting. (Curiously, I don’t have replication lag enabled on studio, but at least I’m not going to get trolled on live testing.)

My NPCS use Humanoid:MoveTo() and a heartbeat loop to catch up to players. I’ve also messed around with linear velocity but that has not borne fruit yet. I’ll continue messing with that in the meanwhile.

I’m certain that it’s possible to prevent this happening - I’ve played zombie games on Roblox and I do recall that this was not an issue on some of them. How do the games do it? I would like the general idea to be explained. Thanks!

4 Likes

Try setting the network owner of all parts in the NPC to the server (or client, however this is more prone to exploiting):

https://developer.roblox.com/en-us/api-reference/function/BasePart/SetNetworkOwner

1 Like

I’ve already done that, thanks for recommending anyhow.

1 Like

Instead of heart beat make it while wait(0.1)

1 Like

That’d introduce an increased delay of 1/60 seconds to 0.1 seconds.

3 Likes

Couldn’t you just increase the speed of the NPC? That or you could move the NPC to some number of studs in front of the character’s facing direction.

NPC:MoveTo(Character:GetPivot().Position + Character:GetPivot().CFrame.LookVector * 5) --Move NPC to 5 studs in front of the character.

If you are using just MoveTo(plr.HumanoidRootPart.Position) try making it MoveTo(plr.HumanoidRootPart.Position,plr.HumanoidRootPart)

1 Like

I’ll try this solution out. I’ll let you know how it turns out, though I did try offsetting the position in the past.

Also try NPC:MoveTo(target.Position,target)

2 Likes

You could take the distance between the NPC and the player. Then, you could use some offseting + dividing + clamping logic to make the NPC slightly faster the closer it is after a certain distance. Or you could simply make them slightly faster (20 instead of 16, for example).

The current behavior of your NPC is kind of how it works in other games (and in real life) in the first place. If you have a game with monsters appearing at night, one can choose to run away when they’re visible, and another can choose to fight with them (to get rewarded, perhaps). If you want the objective to be fighting, then they basically are supposed to be faster than the player. Missed a single sentence and my reply is useless now

2 Likes

No? Humanoid:MoveTo() won’t instantly teleport the NPC, it will be walking to the position of the player 0.1 seconds ago (could be a different time depending on the wait). Even task.wait(1) would work well in that case, the player can’t move too far away in 1 second.

1 Like

If you need it done on the server, you could try some prediction. Work out where the character will be in the future (with a time as something like the ping difference) and move the NPC to there.

Otherwise, you could set the NPCs network owner to the client (when the server thinks it should be targeting it) and handle the actual movement entirely on the client. Prone to exploiters I guess, but not a huge deal I don’t think.

2 Likes

Alright, I believe I have found a solution. Just like with cosmetic projectiles, I will use cosmetic NPCs.

Basically, the client will be notified when a new NPC needs to be spawned. It’ll clone one, while the server will clone one as well. Both will have the same ID so they are linked together. Since the server is entirely in the past, the player’s position will also be in the past. This means the NPC can accurately determine if it had caught up to the player recently, serverside. Then you can send a remote to the client so the NPC can do what it needs to do.

Obviously, you’d probably want the server NPC to be transparent and put it in a collision group.

I can already think of an edge case or two where this wouldn’t work. For instance, if the client zombie and server zombie get desycned, or if the player is exceptionally laggy. Though, I believe that I could implement desync checks to keep that in check. Not too sure about the laggy players though. If you have any warnings or tips on this solution, let me know. I’ll mark this as the solution in the meanwhile.

1 Like

That’s a clever solution. It doesn’t sound totally fun though, getting caught by ghost NPCs!
How well might some simple physics based prediction work, I wonder? If you predict where the player will be about 300ms into the future, it’ll potentially be good at catching players and compensate for up to 300ms of ping.
You could perhaps get it done with this module:

If you know how fast the player and the zombie can move, you can create an intercept path entirely on the server, that might behave consistently regardless of ping. Just a thought!

Otherwise, I was wondering if an approach similar to what you suggested may work; on the client, when the zombie is within sufficient range (which will differ up to a maximum ping…) you could try tweening it over for a strike, then remote back to the server the position it should try to finish at.

1 Like