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. Dismiss Notice

Question Local player's position predict error occured when spawn ghost in client

Discussion in 'NetCode for ECS' started by wechat_os_Qy03BarR3U5AgZ1fs0SBA-4bY, Sep 19, 2023.

  1. wechat_os_Qy03BarR3U5AgZ1fs0SBA-4bY

    wechat_os_Qy03BarR3U5AgZ1fs0SBA-4bY

    Joined:
    Aug 11, 2023
    Posts:
    3
    I'm working on Netcode for Entities
    There is a player(me) in the scene, and I move it monontone and continuedly. In the absence of other conditions, its moving is smooth
    And I create a system which update in PredictedSimulationGroup, the system's purpose is to create projectile continuedly, it's written like this:

    Code (CSharp):
    1. public struct ProjectileState : IComponentData {
    2.     [GhostField]
    3.     public uint spawnId;
    4.     public float lifeTime;
    5. }
    6.  
    7. public struct Player : IComponentData {
    8.     [GhostField]
    9.     public int playerId;
    10.  
    11.     // server
    12.     public Entity connectionEnt;
    13. }
    14.  
    15. public struct TargetActor : IComponentData {
    16.     // The actual player entity which has LocalTransform
    17.     [GhostField] public Entity actorEnt;
    18. }
    19.  
    20. [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]  // [Edit]
    21. [UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
    22. public partial class ActorShootPredictedProcessSystem : SystemBase {
    23.     private EntityQuery projectilePrefabQuery;
    24.     private float timer;
    25.     protected override void OnCreate() {
    26.         projectilePrefabQuery = GetEntityQuery(new EntityQueryDesc() {
    27.             All = new ComponentType[] {
    28.                 typeof(GhostInstance),
    29.                 typeof(ProjectileState),
    30.                 typeof(Prefab),
    31.             },
    32.             None = new ComponentType[] {
    33.                 typeof(InitializedTag)
    34.             },
    35.             Options = EntityQueryOptions.IncludePrefab,
    36.         });
    37.         RequireForUpdate<GameLoadedTag>();
    38.     }
    39.     protected override void OnUpdate() {
    40.         timer -= UnityEngine.Time.fixedDeltaTime;
    41.         bool isServer = World.IsServer();
    42.         var networkTime = SystemAPI.GetSingleton<NetworkTime>();
    43.         if (!networkTime.IsFirstTimeFullyPredictingTick) {
    44.             return;
    45.         }
    46.         Entities
    47.             .WithStructuralChanges()
    48.             .WithAll<Player>()
    49.             .ForEach((ref Entity entity, ref TargetActor targetActor) => {
    50.                 NetworkTick tick = networkTime.ServerTick;
    51.  
    52.                 if (timer <= 0) {
    53.                     if (!isServer) {
    54.                         Entity projectileClient = SpawnProjectile(prevSampledTick.TickIndexForValidTick + 9999, false, targetActor, new float3(1, 1, 1));
    55.                     }
    56.                     timer = 0.05f;
    57.                 }
    58.             }).Run();
    59.     }
    60.     private Entity SpawnProjectile(uint spawnId, bool isPredictedSpawn, TargetActor targetActor, float3 spawnPosOffset) {
    61.         Entity prefab = projectilePrefabQuery.GetSingletonEntity();
    62.         Entity projectile = EntityManager.Instantiate(prefab);
    63.         EntityManager.AddComponent<EntityPrefabInstanceTag>(projectile);
    64.         EntityManager.SetComponentData(projectile, new ProjectileState() {
    65.             spawnId = spawnId,
    66.             lifeTime = 5,
    67.         });
    68.         LocalTransform actorTrans = EntityManager.GetComponentData<LocalTransform>(targetActor.actorEnt);
    69.         EntityManager.SetComponentData(projectile, new LocalTransform() {
    70.             Position = actorTrans.Position + spawnPosOffset,
    71.             Rotation = quaternion.identity,
    72.             Scale = 1f
    73.         });
    74.         return projectile;
    75.     }
    76. }
    After this system start working, I found prediction error in the character's movement, The phenomenon is that the player is constantly overmove
    https://github.com/ViEsLab/ImageStorage/blob/main/video.gif

    The projectile has no collider, and there is no any collider in scene except ground, so in theory there is no object blocking the player's movement

    I try to check player's LocalTransform data, found that its position did roll back. In the ex below, the player is moving right. But in Tick-267, its position occured a big change, but it goes back next tick
    https://github.com/ViEsLab/ImageStorage/blob/main/snapshot_1695109511833.png


    Does anyone know something about it?Thank you very much!
     
    Last edited: Sep 19, 2023
  2. wechat_os_Qy03BarR3U5AgZ1fs0SBA-4bY

    wechat_os_Qy03BarR3U5AgZ1fs0SBA-4bY

    Joined:
    Aug 11, 2023
    Posts:
    3
    I tried to get the Ghost content when sending packets and receiving packets for prediction, found that when this problem occued, the amount of displacement between the receipt of the packet by the client and the completion of the first prediction by the client greatly exceeded its speed.

    For example, there are 2 ticks below, the x distance is 0.458 which is exceeded its speed(5.5) can reach
    https://github.com/ViEsLab/ImageStorage/blob/main/Snapshot_16951156322594.png
     
  3. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    774
    I see three problem in that code:
    1) timer -= UnityEngine.Time.fixedDeltaTime; You should not use UnityEngineTime but the SystemAPI.Time for getting the delta. Otherwise client and server does not run the same code and decrement the timer differently.
    2)
    Code (csharp):
    1.  
    2. [LIST=1]
    3. [*].WithStructuralChanges()
    4. [*]            .WithAll<Player>()
    5. [*]            .ForEach((ref Entity entity, ref TargetActor targetActor)
    6. [/LIST]
    7.  
    You also need to add the WithAll<Simulate> otherwise you are predicting this also when another entity rollback but the Player does not need to be resimulated.

    3)
    Code (csharp):
    1.  
    2. [LIST=1]
    3. [*]if (!isServer) {
    4. [*]                        Entity projectileClient = SpawnProjectile(prevSampledTick.TickIndexForValidTick + 9999, false, targetActor, new float3(1, 1, 1));
    5. [*]                    }
    6. [/LIST]
    7.  
    Why this is done only on the client ? And never on the server? does the server spawn this in another place? Also, why you are passing the isPredictedSpawn to false? (because this is a predicted spawn)

    4)
    Code (csharp):
    1.  
    2. [LIST=1]
    3. [*]projectilePrefabQuery = GetEntityQuery(new EntityQueryDesc() {
    4. [*]            All = new ComponentType[] {
    5. [*]                typeof(GhostInstance),
    6. [*]                typeof(ProjectileState),
    7. [*]                typeof(Prefab),
    8. [*]            },
    9. [*]            None = new ComponentType[] {
    10. [*]                typeof(InitializedTag)
    11. [*]            },
    12. [*]            Options = EntityQueryOptions.IncludePrefab,
    13. [*]        });
    14. [/LIST]
    15.  
    That prefab you get, are you sure that is the one with PredictedSpawnRequest (on the client is required, otherwise things will not work)
     
  4. wechat_os_Qy03BarR3U5AgZ1fs0SBA-4bY

    wechat_os_Qy03BarR3U5AgZ1fs0SBA-4bY

    Joined:
    Aug 11, 2023
    Posts:
    3
    First thank you for your help!

    For problem 1 and 3, I forget to mark that ActorShootPredictedProcessSystem is only run on client([WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]), sorry. Because I want to simulate the failure of prediction spawning in this system, it's also the reason I passing the isPredictedSpawn to false, the field doesn't matter here, don't worry about it for a moment. I will delete this part of the above. And for the same reason, the timer is only count down in client

    For problem 4, yes, I got the true prefab with PredictedSpawnRequest, It's working fine in my formal predicted spawning system

    For problem 2, I try to add WithAll<Simulate> as you said, but it doesn't seem to be working. I add it like this:
    Code (CSharp):
    1. Entities
    2.     .WithStructuralChanges()
    3.     .WithAll<Player>()
    4.     .WithAll<Simulate>()
    5.     .ForEach((ref Entity entity, ref TargetActor targetActor) => {
    6. ...

    And I notice that I didn't give the struct of Player And TargetActor, their structure looks like this:
    Code (CSharp):
    1. public struct Player : IComponentData {
    2.    [GhostField]
    3.    public int playerId;
    4.  
    5.    // server
    6.    public Entity connectionEnt;
    7. }
    8.  
    9. public struct TargetActor : IComponentData {
    10.    // The actual player entity which has LocalTransform
    11.    [GhostField] public Entity actorEnt;
    12. }
    Did I add the code in the wrong place or there is other problem?
     
    Last edited: Sep 19, 2023