Synchronized client-based zombie NPCs?

I’m working on a game that includes zombie survival. At any given time, there could be as many as 50 zombies in the game. Every few frames, they do a distance check to figure out which player is closest to them, and then MoveTo() is called on the zombie’s humanoid to move them towards that player. Doing this on the server is trivial, but as you can imagine, a lot of data is being sent to the clients as a result.

I’d like to employ a strategy similar to that described in this video by @5uphi:

However, in this video, the NPCs simply have a goal position that is changed every so often (and stored in a folder in ReplicatedStorage,) so the server simply needs to send that new goal position to the client. The NPC objects don’t exist on the server, either. I’m not sure if doing this with zombie NPCs (whose positions need to be synchronized well) is feasible or not. Honestly not sure what the best way to go about this is and would appreciate any advice.

2 Likes

Are you asking if the client can make these distance checks?

You can use RemoteFunctions. They basically just allow you to call a function from either the client or the server. Say you are on the server, and you need data from the client. Well that’s what it does.

Just get the total number of zombies, and distribute their workload across all the players that will handle this logic.

DO NOT USE INVOKECLIENT

RemoteFunctions should only get data from the server. The server should NEVER ask the client for data through a RemoteFunction. If the player leaves without sending the data, the server sits waiting forever.

Then make it so when the player leaves, it cancels the callback.

Not a feature of RemoteFunctions. You would need to do that through RemoteEvents.

I’m sure you could put it in a coroutine, then just suspend it. May not be a feature for RemoteFunctions, but it is for Roblox.

RemoteFunctions are a creation of Roblox. A RemoteFunction is a function call that yields until it gets an answer. You cannot suspend that.

If your NPCs are run on the client, the client would just watch for changes from the server. Either the server sends it to the client via RemoteEvent:FireClient() or it’s replicated through ReplicatedStorage by values (values are pretty dated, I’d recommend using remotes).

The client may also request the data from the server via a RemoteFunction. This is ideal when the client doesn’t have the information yet. Beyond that, the player should receive it through a RemoteEvent.

Okay I did some research. A RemoteFunction doesn’t hang when the client leaves (which would be a horrible design flaw on Roblox’s part anyway), it just returns an error. So pcall() it.

This is new behavior then over the past few years. In the past, Roblox listed a very large warning about invoking the client for this issue.

See videos such as this one from sleitnick that discuss the issues with invoking the client. https://www.youtube.com/watch?v=0H_xcA-0LDE

You can also just do the calculations on the server, because remember that LuaU is super fast. And getting magnitude is a very simple calculation. It may not be worth it to invoke the client.

I’m more so asking what a decent overall strategy for designing this system would be. I’m trying to minimize the amount of data sent between the client and the server. On the server, hold the position values, on the client, render the NPCs and get them to move to those positions (either through MoveTo() or setting the CFrame manually if the diff is too large).

My issue is that the NPCs are supposed to chase players, and since their objects don’t exist on the server, I’m not sure how I could simulate their movement.

Right, and just to be clear, I’m not asking how to communicate data between the client and the server. I’m trying to think of an appropriate design for this system that minimizes data sent between the client and the server.

The zombies don’t exist on the server? Please elaborate.

RemoteEvents are your best option with a RemoteFunction to get the data on first load.
A RemoteEvent allows the server to tell the client that something changed. This means the data is only sent when something changes.
A RemoteFunction would be necessary if the client doesn’t have the data yet, such as joining before the server provides new data.

This was mentioned in the original post. It’s unnecessary replication for the server to handle the NPCs when the server can just handle position data and let the client create the actual NPC.

I see, I had the whole thing wrong then. Couldn’t the server just update some values in ReplicatedStorage? The client then just checks for changes and then updates?

Values work, yes, but they are dated. You cannot send a table through a value without serializing it as a string. You can, however, send a table through remotes.

I was thinking more of a folder with the children being values for each respective zombie but yeah that’s better.

Here’s the problem that I’m dealing with currently:
-Let’s say that we initialize 50 NPC positions on the server, random locations.
-We spawn in the actual NPC objects on the clients at these random positions.
-The server can do distance checks for each of those NPCs to determine who they should target, and then communicate that to the clients. Maybe store this information as well.
-When the clients get this information, they call MoveTo() on the created NPCs.

There are a few problems:
On the server, the NPC positions haven’t changed from their original positions.
Let’s say someone joins the game after all of this has started. They may have a goal, which is communicated to the new client, but that new client will be out of sync with the other clients.

Additionally, let’s say that the distance checks happen every 1 second. The server positions, again, are still what they were originally. Therefore the distance checks won’t accurately reflect who the zombie NPCs might be close to on the clients.

This is the issue I’m trying to solve. Appreciate the help.