Need help understanding how to implement efficient client-side pathfinding for npcs

quick TL;DR:
I am using an OOP approach to handle the behavior of enemy npcs. In each NPC object, a state is stored which defines that particular NPC’s behavior. When a player is close enough, the npc’s state changes from “Idle” to “Pursuit”. When the NPC is in “Pursuit”, the NPC will use pathfinding service and humanoid:MoveTo() to chase the player. If a player leaves the range of the npc, it will go back to the “Idle” state. I can get this roughly working when everything is server side, but I want to compute the pathfinding on the client side of any players that close enough to see the npc. Pathfinding and using Humanoid:MoveTo() on the server is way too encumbering and laggy.

What I am looking for: I want to know an effective way to handle the pathfinding and humanoid:MoveTo() on the client. How do i do this so that when an npc chases a player, the npc’s position is updated for all players that can see the npc? I don’t just want to move the enemy npc on one client. I figure it has something to do with setting the NetworkOwner of the npc model, because the server needs to know when the enemy gets close enough to attack the player. My initial reaction is to use lots of remote events to update the server on the npc’s position, but I don’t know how I can ensure things don’t get wonky.

Example of what I have now:

I would try using a RemoteFunction to return the path and then move the NPC. Would that work?

Trying to use remote events in this situation may be more trouble than it’s worth. The only way I can think of doing this would be picking a single player to do the pathfinding calculations locally, fire a remote to the server, which then would run code to follow that path (This also would create a vulnerability where clients could pass garbage pathfinding data to the server).

The biggest problem with that approach, besides the security vulnerability, is that the use of Remote Events would cause replication lag which would probably be worse than what you are already experiencing.

I can only guess because I have no code to look at, but I assume your “Pursuit” loop is something similar to the following pseudocode:

for i, waypoint in pairs(waypoints) do
	if playerHasMoved() then
		recalculatePath()
		break
	else
		agentHumanoid:MoveTo(waypoint.Position)
		agentHumanoid.MoveToFinished:Wait()
	end
end

If this is the case, the problem likely lies in the MoveToFinished:Wait() line (or any similar waiting method). I would suggest running the :MoveTo() and MoveToFinished:Wait() in a coroutine that can be stopped whenever the agent has to recalculate its path.

You can also do what Roblox did in their “Character Pathfinding” documentation example. They avoided using a loop altogether and opted to handle agent movement exclusively via connections.

In my experience, I find the pathfinding system to be very performant, capable of handling path recalculations each frame. Whenever I had a problem similar to yours, it came down to me fixing my waiting logic.

I hope this helps and you can get that NPC moving buttery smooth! :smile:

Okay, I’ve rewritten my script a little. Now, when a player enters the chase range of the npc, that player becomes the network owner of the npc model. Then, in a localscript, pathfinding and humanoid:moveto() are performed. If the player leaves the chase range of the npc, the server becomes the network owner again. It looks way smoother, but I am unsure of the implications of this setup.

Giving the player ownership of a gameplay critical object does have some security concerns. A malicious client can move that NPC wherever it wants and the change will be replicated to the server. It depends on your use case whether this is a problem or not. In a multiplayer game this means a malicious client can get the NPC permanently stuck somewhere and ruin the experience. If it is a single-player game, and you don’t care about clients exploiting their ownership, then this solution should be fine.

In short: do whatever works for you and your particular needs, but keep in mind that this method does have security issues in most game formats.

1 Like

yeah, i see what you’re saying. I was thinking I could just perform security checks on the server to make sure that the npc behavior is working as intended and there isn’t any foul play on the client’s end. Do you think this would suffice as far as making sure there is no exploiting/hacking? Because the only other alternative I can think of is to fire remote events to all clients within a certain range to run the tweening/animating on the client whenever the npc decides to chase someone. I really do not think I can afford to perfrom these calculations on the server, as even with just one npc it’s stuttery/laggy.

Are you saying create the path on the server, then send that path info to the client via a RemoteFunction?

I think I misunderstood the post. From what I understood, you are tying to make enemies only appear on the client, but want to get the path from a script on the server. So I just thought you might want to use a RemoteFunction to return that path from server.