Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question How to optimally sync `IBufferElementData`, how to set it up, alternatives?

Discussion in 'NetCode for ECS' started by EmmaPrats, Jan 5, 2023.

  1. EmmaPrats

    EmmaPrats

    Joined:
    Apr 19, 2019
    Posts:
    8
    I'm trying to add server-authoritative multiplayer to a small game I made with DOTS, and I have some questions...

    I'm using:
    • Entities 1.0.0-exp.12
    • Netcode for Entities 1.0.0-exp.13
    • Unity Transport 2.0.0-exp.6
    • Unity 2022.2.1
    (I want to keep updating to the latest versions for the foreseeable future.)

    The game has a large number of Agents that follow the closest Player.

    Since ECS-based Navigation is far away in the roadmap, and the game's level is a simple square, I divided it into a grid and implemented a basic A* algorithm.

    Each Agent has a
    WaypointData
    buffer:

    Code (CSharp):
    1. [InternalBufferCapacity(20)]
    2. public struct WaypointData : IBufferElementData
    3. {
    4.     public int2 Position;
    5. }
    The
    AStarSystem
    populates these buffers with the correct path toward each Agent's target, and the
    AgentMovementSystem
    moves the agents along their paths.

    For the multiplayer version, I want:
    • The
      AStarSystem
      to run only in the Server.
      • So I will add
        [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
        .
    • The
      AgentMovementSystem
      runs in both the client and the server, because movement is client-predicted.

    Q1: Is the attribute
    [UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
    the correct one to add to
    AgentMovementSystem
    ?


    Q2: Should I add
    .WithAll<Simulate>()
    to
    AgentMovementSystem
    's entity query? What does this
    Simulate
    tag mean exactly?


    Q3: Given the following requirements, how do I set up the ghost/serialization for the Agents (and specifically for the
    WaypointData
    buffer)?

    • The Agents are NPCs, so they are NOT owned by any Player.
    • The
      WaypointData
      buffer will be written by the server and read by the clients.
    • The clients should not try to predict or interpolate the
      WaypointData
      buffer.
    • I only want to sync the
      WaypointData
      buffer when there are changes, which will NOT happen many times per second.
    • The
      WaypointData
      buffer will have changes when the Agent's target moves from one cell to another. If there are many Agents following that same target, they will all try to sync at the same time.
    Code (CSharp):
    1. [GhostComponent]
    2. [InternalBufferCapacity(20)]
    3. public struct WaypointData : IBufferElementData
    4. {
    5.     [GhostField] public int2 Position;
    6. }
    So I found this: https://forum.unity.com/threads/synchronize-ibufferelementdata-or-any-other-array-of-data.1367526/

    Q4: Do you recommend to not have the
    WaypointData
    buffer be a
    GhostComponent
    , and simply send a message from the server to all clients containing all the changed buffers to all clients?


    Q5: Am I right to assume that, for this purpose, I can identify each Agent by their Ghost ID?

    Q6: Can you give me an example of how to send a message from the server to all clients through TCP communication from within an ECS system?

    Q7: If I don't add
    GhostComponent
    to an
    IBufferElementData
    component, it is NOT synchronized by default, right?


    Thank you for your time!
     
  2. NikiWalker

    NikiWalker

    Unity Technologies

    Joined:
    May 18, 2021
    Posts:
    306
    Hey EmmaPrats!

    Q1: Correct.

    Q2: Yes, correct. The Simulate component is added by default to all entities. It's essentially a global tag saying "this entity should be simulated" for the purposes of game logic. In Netcode specifically, we use it to control how prediction is applied during rollback.
    I recommend reading https://docs.unity3d.com/Packages/com.unity.netcode@1.0/manual/prediction.html to get an overview of prediction.

    So: When the server sends a snapshot to a specific client, it likely will not include all ghosts. When that snapshot arrives on the client, we undo all predicted ghosts, and re-simulate them all using this new data (in this new snapshot). But, this also means that different ghosts will have different amounts of historic data. Thus, during re-simulation (in the `GhostPredictionSystemGroup`), we may not need to re-predict a specific entity yet. Thus, we use the Simulate Tag component to filter out entities that don't need to be re-predicted.

    Q3 & 4: I'd recommend having two components. `ServerWaypointPath`, which stores the full path and is NOT replicated. Then a new struct, containing a single [GhostField] int2: `NextWaypoint`. This will replicate only the current waypoint (from `ServerWaypointPath`), and will delta-compress extremely well (as it'll always be one tile away).

    If you also need the next one after that (due to how frequently you're resending agents), you can store the next 2 paths. I recommend against using an IBufferElementData here, because replicating the count is redundant.

    Then, use default(int2) to denote "no path".

    It is almost never recommended to use RPCs as a substitute for often changing entity state data. This is because:
    • GhostFields are designed for this purpose. RPCs are not.
    • GhostFields handle pre-spawns, destroyed entities, new joiners, relevancy, and importance.
    • RPCs are reliable, which is overkill for this use-case.

    Q5: Correct. Note that `[GhostField] public Entity Target;` will also work.
    I.e. Entity references are supported (and automatically handled).

    Q6: Unfortunately no. UTP does not support TCP out of the box. You can, however, send reliable RPCs. What kind of data are you trying to send?

    Q7: The GhostComponent is actually optional. It is actually the presence of [GhostField]'s (or [GhostEnabledBit]) that denotes whether or not a component is replicated.

    Cheers!
     
    erenaydin, EmmaPrats and Kmsxkuse like this.