Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

[NetCode] Multiple bullet spawn prediction

Discussion in 'NetCode for ECS' started by Sibz9000, Mar 26, 2020.

  1. Sibz9000

    Sibz9000

    Joined:
    Feb 24, 2018
    Posts:
    149
    I am trying to get my head around how predictive spawning works. I've looked at the asteroids example and got my code working with single bullets/rockets. However if I try spawn two bullets client side and two server side, the first two are created as intended, but the following triggers the client and server spawns do not get linked.

    EDIT: Actually I slowed down the client added, 1000ms delay, I found the first time I create a spawn, it's not even creating on the client side, subsequent spawns are. Also the client and server spawns are linking up just fine. But with <100ms delay, I get one client side spawn at first, then two that get replaced each subsequent spawn and never linked.
    EDIT2: In MarkPredictedGhostsJob I had
    predictionSpawnGhosts[i]
    instead of
    predictionSpawnGhosts[ent].
    . Fixing this now links them reliably. However I still can't explain why the first time I trigger my spawn system, it does not create the client side ghosts.

    I am struggling to understand how
    MarkPredictedGhosts
    is supposed to work.

    At the moment I am keeping the code basic (not jobified yet) to understand the concept, so to spawn I create a singleton to trigger this system to fire:
    Code (CSharp):
    1.  protected override JobHandle OnUpdate(JobHandle inputDependencies)
    2.     {
    3.         EntityManager.DestroyEntity(GetSingletonEntity<CreateFirstObject>());
    4.  
    5.         int bulletId = 42 + count;
    6.         SpawnPredictedBullet(bulletId);
    7.         SpawnPredictedBullet(bulletId+1);
    8.  
    9.         Entity cmdTarget = GetSingletonEntity<CommandTargetComponent>();
    10.         SpawnBullet(bulletId, cmdTarget);
    11.         SpawnBullet(bulletId+1, cmdTarget);
    12.  
    13.         count++;
    14.         count++;
    15.  
    16.         return inputDependencies;
    17.     }
    18.  
    19.     private void SpawnPredictedBullet(int index)
    20.     {
    21.         Entity e = EntityManager.CreateEntity(bulletArchetype);
    22.         var buff = EntityManager.AddBuffer<BulletSnapshotData>(e);
    23.         BulletSnapshotData bulletData = default;
    24.         bulletData.tick = predictionGroup.PredictingTick;
    25.         bulletData.SetBulletComponentIndex(index);
    26.         buff.Add(bulletData);
    27.     }
    28.  
    29.     private void SpawnBullet(int index, Entity commandTarget)
    30.     {
    31.         Entity req = EntityManager.CreateEntity();
    32.         SpawnRpc spawnRpc = new SpawnRpc {Index = index};
    33.         EntityManager.AddComponentData(req, spawnRpc);
    34.         EntityManager.AddComponentData(req, new SendRpcCommandRequestComponent {TargetConnection = commandTarget});
    35.     }

    The server simply creates an entity from the server prefab and assigned the id.

    My MarkPredictedGhosts is as follows, I've just copied how the asteroids example does it, without the playerId matching because I am just testing single player at this stage. I really don't know how it works, and reading comments/code in the example or in the base
    DefaultGhostSpawnSystem - CopyInitialStateJob
    hasn't clarified things for me.

    Code (CSharp):
    1. protected override JobHandle MarkPredictedGhosts(NativeArray<BulletSnapshotData> snapshots,
    2.         NativeArray<int> predictionMask, NativeList<PredictSpawnGhost> predictSpawnGhosts,
    3.         JobHandle inputDeps)
    4.     {
    5.         Debug.Log($"MarkPredictedGhosts run on {World.Name}");
    6.         var job = new MarkPredictedJob
    7.         {
    8.             snapshot = snapshots,
    9.             predictedMask = predictionMask,
    10.             predictionSpawnGhosts = predictSpawnGhosts
    11.         };
    12.         return job.Schedule(predictionMask.Length, 8, inputDeps);
    13.     }
    14.  
    15.     [BurstCompile]
    16.     struct MarkPredictedJob : IJobParallelFor
    17.     {
    18.         [ReadOnly] public NativeArray<BulletSnapshotData> snapshot;
    19.         public NativeArray<int> predictedMask;
    20.         [ReadOnly] public NativeList<PredictSpawnGhost> predictionSpawnGhosts;
    21.  
    22.         public void Execute(int i)
    23.         {
    24.             predictedMask[i] = 1;
    25.             for (int ent = 0; ent < predictionSpawnGhosts.Length; ++ent)
    26.             {
    27.                 if (predictionSpawnGhosts[i].snapshotData.GetBulletComponentIndex() ==
    28.                     snapshot[i].GetBulletComponentIndex())
    29.                 {
    30.                     predictedMask[i] = ent + 2;
    31.                     break;
    32.                 }
    33.             }
    34.         }
    35.     }

    Can anyone shed any light on how this is supposed to work?
     
    Last edited: Mar 26, 2020
  2. Sibz9000

    Sibz9000

    Joined:
    Feb 24, 2018
    Posts:
    149
    Ok in my system SpawnPredictedBullet, I changed
    bulletData.tick = predictionGroup.PredictingTick;

    to
    bulletData.tick = World.GetExistingSystem<ClientSimulationSystemGroup>().ServerTick;

    And everything now works as intended (mostly, if I repeatedly create client prediction ghosts before the server sends the spawn request, the additional client prediction ghosts disappear). I still really don't have a great understanding on all this so would still appreciate some elaboration from an expert.
     
    Last edited: Mar 26, 2020
    john-wallace likes this.