How would i go about rendering hostile mobs to specific clients?

So in my game there’s basically a system where players can queue and create rooms with other players. Once a room is created they get sent to the room (on the same server) and they have to fight hoards of enemies. These enemies are really simple, they find the nearest target and chase/attack them depending on the distance.

I want the enemies to be as optimized as possible, because worst case scenario a server could have 6 rooms with 40 enemies each, which leads to 240 enemies being moved on the server.

What i decided to go with is having the enemies only be rendered on the clients inside the room. Which leads me to this post, I’m not too sure how to go about this.

One idea i might go with is:

  1. Creating a hitbox part on the server
  2. Rendering/Loading the NPC on the client and welding it to the part.
  3. Setting the network ownership of the part to the target client.
  4. Handling the parts movement (chasing the player) on the server(?)

My thought process for this is basically, if the npc’s model and network ownership is on the client then that might take SOME strain off the server, while keeping everything in sync for each player.

I know this process is extremely flawed. For example exploiters can move the npcs where ever they want due to the network ownership being on the client. But hey, that’s the reason i’m making this post.

I’m not asking to be spoon fed, just a nod in the right direction is enough. So please send some suggestions if you have any!

1 Like

Personally I would go about this by calculating on the server, rendering on the client.

Calculate anything critical on the server (damage, spawns, etc) and leave EVERYTHING else on the client. The number 1 rule of optimization is to only use server space for needed game features. Basically think about the bare minimum your game would need to run and handle only that on the server.

Let’s say you have a zombie enemy that is currently 100% handled on the server. You could improve it by first off handling animations client sided. This especially applies if you are using some form of procedural animation. Next you could handle the hitboxes client sided, just remember to include logic checks.

Let’s say you have a system of 100 entities. I personally would design it in the following way:

Server broadcasts to clients that enemies have been spawned.

Clients render and animate enemies, then start listening for hitbox collisions

Clients send info about enemies positioning semi regularly to the server. These positions can be cross checked against other players as the odds of getting a full party of exploiters is rare.

Any time a hitbox is triggered the client broadcasts it to the server. Server uses enemy positioning stated above combined with known info about the player and enemy (player/enemy health, player damage, etc) in order to run logic checks.

Once server validates info it relays it to the other clients and updates any server values accordingly.

Notice how in this example the only thing the server is doing is running logic checks and acting as a client middleman.

Quick warning: remote events are not instantaneous. There is no way around this, if you wish to offload computations on the client you will need to deal with a small bit of lag. This can be crippling depending on what game your making, so just think before you implement and see what you can get away with in terms of client-server lag.

3 Likes

I love the idea of comparing and crosschecking the positions thats smart.

Quick question tho, where would the position syncing come in to play? I want the mob to be in the same spot for all clients rendering it. If each client handles their own individual positioning then it might get out of sync wouldn’t it? Correct me if i misread

You’re good, I forgot to mention that part. There are three ways you could handle this.

You could broadcast positions/targets when enemy’s are spawned. This is easy to implement and should sync perfectly, the only issue is you won’t be able to change targets while an enemy is spawned unless you broadcast a new position from the server which I doubt will happen much.

You could calculate positions on the server and then relay them to the clients. Most of the lag will come from actually loading/moving the parts so if you keep the entities unloaded on the server it should be fine.

You could use the same calculation algorithm for every client. This option might be a little iffy due to ping and connection differences between players but in theory if you use the exact same pathfinding algorithm across all clients the entities should pathfind the exact same way across all the clients.

Personally I would go with number 2, maybe a mix of 2 and 3 if I was feeling ambitious. If you calculated waypoints on the server every few seconds and then did the pathfinding client side between waypoints that would probably be the most optimized system, it might be a little hard to sync between especially laggy players though.

1 Like

Old post but this is a good topic.

I wanted to include that you should use “client prediction” techniques with these calculations. That way if client lags or has high ping the position does not deviate across all clients.

The caveat is monsters may rubberband in high ping events- which is what most mmo games would do anyway.

1 Like

Now that I think about it. I came up with another solution. Im curious to hear your opinion. This would be a hybrid model (Headless server-authoritative logic and client-side rendering)

This approach that comes to mind takes advantage of Roblox’s native replication. The idea is this using headless part references (position/rotation replicates) and Values (which also replicates). Client then uses these to update animation and behavior.

  • Server: Server uses a part which represents a headless part mob only containing a part and attributes/Values to hold important state data. The part has collisions/shadows and is anchored to keep memory costs low and disabling physics as the server should typically calculate the movement. The server Mob AI can move the headless part and updates state (using things like StringValue/NumberValue for health, walksped and other stats to maintain server). I found this preferably than humanoids for lower overhead. Also String/NumberValue support native replication. it has lesss control of how roblox handles it under the hood but you wouldnt have to worry about failed position requests and retries which which lead to desync or worse packetloss (I didnt do extensive testing for this claim but theres a lot in the networking backend that roblox handles I am not aware of)

  • Client: The headless part is replicated to all clients. The only thing a local client needs to do is clone the actual mesh model of the mob at the pivot position of that part. Client would makes the server part transparent (makes debugging easier where you can switch to server and see the headlesspart move, then switch to client to see the actual mob). Client would then have localscripts that listen onto the StringValue or NumberValues changes play animations or update state.

I think this works well because we dont really need to calculate and sending positions over the network which may desync anyways. And if done incorrectly could flood your network message queue. And avoid manually retrying upon failed requests.

The only thing Im unsure about this solution is how to utilize spatial partitioning for spawning and despawning. Perhaps when a mob is spawned, the server does a magnitude position check if player is nearby to render it, if so fire an event. As for despawning possibly do a magnitude dist check of all mobs nearby. Reduce these checks to 1 or 2 seconds preferably depending on the number of mobs.

Potential last bit of optimization is to object pool the mobs in a table- as you said loading and unloading is very expensive. If mobs are less than 50 object pooling wont be as necessary than say 200 mobs on screen.

What are your thoughts on this?

Edit:
Maybe for despawning mobs: Track with a timestamp like “LastPlayerNearbyTime”. Then server can check once in a while if no nearby players and the time threshold has been a while since player was nearby, despawn the mob.