0
\$\begingroup\$

I'm trying to synchronize 3-4 thousand game objects' positions and rotations. I need performance for my Unity game to remain relatively high (30+fps) and to keep the amount of data sent as low as possible. Therefore I need a solution for sending large amounts of data from the server to the clients with as little data and overhead as possible. I don't want to be paying massive server costs and making the server lag due to too much traffic being sent.

To explain what I'm trying to do: I'm trying to synchronize the destruction of a building across clients. Also, important note, I only need to send data to the clients, I don't need to send any data back to the server (I assumed this would be simple for the server as all it had to do was mass blast all clients with the current game object transform data).

I'm aware that there are tricks to keep traffic low or reduce the need to sync as often such as "client prediction and server side reconciliation", which I plan to look into at a later point (although this could add overhead that perhaps I don't need).

I'm aware that there have been solutions in certain games (I believe) that involve using multiple servers to sync building destruction, but I'd appreciate a single server solution if possible.

I'm also aware that there are probably solutions for avoiding syncing unnecessary game objects that a client may not see such as NetworkProximityChecker, but assuming that all game objects that are being synced are visible and active, I need a solution for handling that (in the worst case scenario that clients can see all the game objects being synced at some point).

Things I've thought about doing are avoiding using NetworkTransforms or even NetworkBehaviours altogether and sending simple NetworkBase/NetworkMessage (I believe it's called) messages to the clients (to reduce data sent). Or, go even more basic and use non-Unity libraries to send data to clients (again to avoid overhead and too much data being sent that might create too much large traffic).

There may not be a solution to this that works well - there is no getting around the simple fact that Vector3s and Quaternions must be constantly sent to each client, and there isn't a way to avoid that massive amounts of data being sent.

I'm using Unity Mirror, although I could and probably will switch to Unity's netcode.

So far, I've tried using Unity Mirror's NetworkIdentity and NetworkTransforms to sync all the necessary object's transforms. With two players the performance wasn't too bad, but not great. With 4 players performance was pretty horrible, with fps around the mid teens.

I was expecting this considering how much data was being sent.

update. game details:

to give more details about the game; players will be walking around a map of destructible buildings that will cause obstacles for them. therefore i need all the broken building parts to be synced. i could only sync parts that're close to players, but in the rare event that there is a lot of destruction all occurring in close proximity to all players, this plan falls apart. I want to ensure the game can handle this uncommon occurrence.

i know that relative to other games sending a ton of data won't be cheap, but cutting the amount of data sent by half could mean reducing cost for running the game, increasing performance (fps) by a lot, and reducing network traffic by a lot. Even small efficiencies i think could come close to making or breaking the game. For example, a 15 - 20 fps game isn't really playable in my opinion but a 28 - 30 fps is. If I could increase performance during these worst case situations of massive destruction even that relatively small amount, that'd be enough.

\$\endgroup\$
1
  • \$\begingroup\$ Comments have been moved to chat; please do not continue the discussion here. Before posting a comment below this one, please review the purposes of comments. Comments that do not request clarification or suggest improvements usually belong as an answer, on Game Development Meta, or in Game Development Chat. Comments continuing discussion may be removed. \$\endgroup\$ Commented Jan 2 at 18:08

2 Answers 2

1
\$\begingroup\$

Let's ignore your existing Unity infrastructural concerns to suggest something else entirely:

Don't:

  1. Use client-server for this, it is not the correct tool for the job. You will probably be stuck with endless re-syncing across (many) thousands of units and multiple clients.
  2. Use floating point math, you will never entirely get rid of desyncs if you do. The only exception to this is using streflop, see below.
  3. Use any old SIMD or multi-threading approach you like, without first verifying that it is deterministic; and do not assume that Unity's underlying mechanisms of these (or networking) are deterministic. Compilers and processors can freely reorder operations, except in environments of highly controlled parallelism.

Do:

  1. Use a low level networking library like ENet CSharp to write a deterministic peer-to-peer lockstep engine (using turns). Have a sync period before gameplay, to get all units transferred and synced.
  2. Ensure all your math / arithmetic, including trig, is integer-based, using a library like libfixmath / FixMath.net plus compile-time defined integer-based trig and sqrt lookup tables if necessary., OR use streflop for safe floating point.
  3. Use only known-deterministic SIMD or multithreading approaches (verify before investing time). For example, OpenMP does not by default deterministically order its operations.
  4. Order your processing of commands by incoming turn number and entity index / id to ensure identical processing across all peers. Ensure that the rules for spawning new entities and removing old ones are, equally, identical, and thus happen at exactly the same time within exactly the same turn, for every peer, thereby assuring deterministic id assignments and deassignments.

P2P games that have done this successfully: Age of Empires (the canon article on P2P locsktep in games), the Total War series, and Total Annihilation: Spring, which used streflop (built for the game) to equalise floating point operations across all supported platforms (could not have been an easy task, only possible through strict control of compiler options).

Conclusion

Client server simply isn't suited that well to constantly sending data for thousands of units to ensure simulations are checksum-equal every frame or turn. It can be done but probably not by an individual dev.

I believe some larger studios have written P2P lockstep like these using floating point math, but it has taken them, with large teams, excessive man hours to hunt down and eliminate floating point bugs across all supported platforms. For this reason, just switch to using fixed point early on, and ensure your parallelism is deterministic, too.

\$\endgroup\$
0
\$\begingroup\$

Instead of syncing every broken piece, make destruction fully deterministic so that all clients can reproduce the destruction identically with minimal data.

If I push on a log from the right side, I don't need to sync the position for me to know, that it will fall to the left.

Acording to google "Unity Physics is a deterministic rigid body dynamics system and spatial query system written from the ground up using the Unity data oriented tech stack."

Instead of syncing every fragment, just send a single destruction command:

-Explosion at position A and time B with strength X -push at position A and time B with Strength C

If the destruction is not based on any randomnes the pieces will land on the same positions. You can "Hash" the results and send a checksum to see if further syncing is necessary if you dont trust the determinist nature of your code.

This will even work in batches: check

\$\endgroup\$
1

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.