Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Join us on Thursday, June 8, for a Q&A with Unity's Content Pipeline group here on the forum, and on the Unity Discord, and discuss topics around Content Build, Import Workflows, Asset Database, and Addressables!
    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


    Apr 19, 2019
    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

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

    For the multiplayer version, I want:
    • The
      to run only in the Server.
      • So I will add
    • The
      runs in both the client and the server, because movement is client-predicted.

    Q1: Is the attribute
    the correct one to add to

    Q2: Should I add
    's entity query? What does this
    tag mean exactly?

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

    • The Agents are NPCs, so they are NOT owned by any Player.
    • The
      buffer will be written by the server and read by the clients.
    • The clients should not try to predict or interpolate the
    • I only want to sync the
      buffer when there are changes, which will NOT happen many times per second.
    • The
      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:

    Q4: Do you recommend to not have the
    buffer be a
    , 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
    to an
    component, it is NOT synchronized by default, right?

    Thank you for your time!
  2. NikiWalker


    Unity Technologies

    May 18, 2021
    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 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.

    EmmaPrats and Kmsxkuse like this.