Search Unity

Question Handling a large number of same-importance entities

Discussion in 'NetCode for ECS' started by MhmdAL1111, Dec 12, 2022.

  1. MhmdAL1111

    MhmdAL1111

    Joined:
    Oct 25, 2017
    Posts:
    30
    Hi, I am a beginner with multiplayer games, and I am making a small prototype of a tower defense game.
    I am trying to understand what is the best way to handle a big number (1000+) of entities which move along a path (a square-wave looking path) at varying speeds, which can also be stopped and attacked when a player is near them.

    I tried having them as dynamic interpolated ghosts but after about 500 entities it starts showing weird behavior where the entities will continue in a straight path even though they should turn and follow their defined path, then quickly disappear and reappear on the path again.

    So it seemed this approach won't work. All these entities are of equal importance and I need to simulate them all. Then I tried changing the translation Send Type Optimization on the prefab to "never send" and instead ran the movement system on the client. It works, and the bug described above doesn't happen, but now I wonder how I will handle syncing the initial spawn position, the stopping and attacking the player, etc. when the systems that spawn the entities, handle the attacks, etc. are server-only?

    Is there any built-in way I can sync the position once every X time for this entity? Or, is there any way to force sync the translation whenever it makes sense (for example on every waypoint along the path)?

    Any ideas will be helpful, thank you
     
  2. miniwolf_unity

    miniwolf_unity

    Unity Technologies

    Joined:
    Apr 10, 2018
    Posts:
    138
    It sounds to me like you are sending too much data per tick, which is causing their entities to overshoot and then get moved back. But that is an assumption.
    I made a system to make about 500 entities walk around in a pattern and put the RTT Delay to 1000ms and only then saw the overshooting happen. Are you perhaps synchronizing more data than just the position and rotation?

    For optimization, you could think about something like:
    You only need to know which waypoint and the speed for the enemy to figure out how to move the enemy.
    The waypoints can be stored locally and be the same for all enemies. The speed is something that can be authorized by the server. Then we only need to send a float instead of 3 floats for the position.
     
    NikiWalker likes this.
  3. MhmdAL1111

    MhmdAL1111

    Joined:
    Oct 25, 2017
    Posts:
    30
    Hi, thanks for replying

    Yes you are right, it was way too much data being sent. I was sending Position, Speed, and a few other things.

    Even just sending a few floats, with 1k+ entities, set to dynamic optimization mode, was too much. I have tried out different ideas so far.

    I tried setting optimization mode to static, do not sync position and only sync speed and other things which don't change every frame, and run the movement system on the client given this data. This works okay but for some reason the client always slowly out-runs the server entities. I figured it has to do with float math errors and client/server tick rate difference, but not sure.

    I tried then to implement a custom "sync point" where I force sync the position on every waypoint between server/client. I did this by enabling an enablable component on the entities when they reach a waypoint, which will be handled by a system on the client to update the position. I did not find a way to force sync (when a value is set to not sync by default, like I did with my position) without all this hassle. Is there a way which I missed?

    But this still caused issues where it looks jumpy on the client. Maybe the best way is to figure out why the client out-runs the server and make sure the game is fully deterministic so this doesn't happen.
     
  4. PolarTron

    PolarTron

    Joined:
    Jun 21, 2013
    Posts:
    94
    Use waypoints instead of realtime position updates. A path is a collection of waypoints. Only when the path changes it is synchronized to clients. Instead of 20hz updates you now get a really low update frequency because the path stays the same for many entities over many ticks.

    Code (CSharp):
    1. public struct PathingWaypoint : IBufferElementData
    2. {
    3.     [GhostField]
    4.     public float3 Position;
    5.     [GhostField]
    6.     public int ArrivalTick;
    7. }
    Position can now be inferred by using spline interpolation logic.
     
    UniqueCode likes this.