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

Third Party FishNetworking Client Prediction Jitter

Discussion in 'Multiplayer' started by gxslim, May 7, 2024.

  1. gxslim

    gxslim

    Joined:
    Feb 10, 2013
    Posts:
    4
    Hi all,

    I've been attempting to figure out CSP with server authority using Fishnet prediction 2.0. I've followed the documentation and whatever guides I could find. It seems like I have it working correctly.

    The issue I'm having now though, is a client's owned Player network object seems to jitter like crazy locally on during reconciliation. I've tried messing with the interpolation slider under Prediction on the network object and the higher it goes the smoother it is, but it becomes way too unresponsive before it becomes smooth. I've also tried disabling state forwarding as that was mentioned on the FishNet discord, but that didn't help - With state forwarding on, the server sees everything fairly smoothly, client has jitter on its own local machine, and without state forwarding, the server sees everything smoothly, but the client only sees its own movement, not the host's.

    Does anyone have any advice for smoothing this out properly?

    Here are the settings on my player:



    Here are the movement scripts:
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using FishNet.Object.Prediction;
    6. using FishNet.Object;
    7. using FishNet.Component.Prediction;
    8. using FishNet.Transporting;
    9.  
    10. public class ServerAuthMovementWithPredic : MovementScript
    11. {
    12.     bool _subscribed;
    13.     protected PredictionRigidbody2D PredictionRigidbody2D { get; private set; } = new();
    14.  
    15.     private void Awake()
    16.     {
    17.         PredictionRigidbody2D.Initialize(GetComponent<Rigidbody2D>());
    18.     }
    19.  
    20.     private struct ReconcileData : IReconcileData
    21.     {
    22.         public PredictionRigidbody2D PredictionRigidbody2D;
    23.         public Rigidbody2DState Rigidbody2DState;
    24.  
    25.         public ReconcileData(PredictionRigidbody2D pr)
    26.         {
    27.             Rigidbody2DState = new Rigidbody2DState(pr.Rigidbody2D);
    28.             PredictionRigidbody2D = pr;
    29.             _tick = 0;
    30.         }
    31.  
    32.         private uint _tick;
    33.         public void Dispose() { }
    34.         public uint GetTick() => _tick;
    35.         public void SetTick(uint value) => _tick = value;
    36.     }
    37.  
    38.     private void SubscribeToTimeManager(bool subscribe)
    39.     {
    40.         if (base.TimeManager == null)
    41.             return;
    42.         if (subscribe == _subscribed)
    43.             return;
    44.         _subscribed = subscribe;
    45.  
    46.         if (subscribe)
    47.         {
    48.             base.TimeManager.OnTick += TimeManagerOnTick;
    49.             base.TimeManager.OnPostTick += TimeManagerOnPostTick;
    50.         }
    51.         else
    52.         {
    53.             base.TimeManager.OnTick -= TimeManagerOnTick;
    54.             base.TimeManager.OnPostTick -= TimeManagerOnPostTick;
    55.         }
    56.     }
    57.  
    58.     private void TimeManagerOnPostTick()
    59.     {
    60.         if (IsServerStarted)
    61.         {
    62.             CreateReconcile();
    63.         }
    64.     }
    65.  
    66.     public override void CreateReconcile()
    67.     {
    68.         base.CreateReconcile();
    69.         ReconcileData rd = new ReconcileData(PredictionRigidbody2D);
    70.         Reconciliation(rd);
    71.     }
    72.  
    73.     private void TimeManagerOnTick()
    74.     {
    75.         if (!GameManager.Instance.IsGamePlaying()) return;
    76.         if (ship.GetShipSO() == null) return;
    77.  
    78.         Move(BuildMoveData());
    79.     }
    80.  
    81.     private MoveData BuildMoveData()
    82.     {
    83.         if (!base.IsOwner)
    84.             return default;
    85.  
    86.         Vector2 inputVector = GameInput.Instance.GetMovementVectorNormalized();
    87.  
    88.         MoveData md = new MoveData(_shoot, _wrap, inputVector.x, inputVector.y);
    89.         _shoot = false;
    90.         _wrap = false;
    91.  
    92.         return md;
    93.     }
    94.  
    95.     public override void OnStartNetwork()
    96.     {
    97.         SubscribeToTimeManager(true);
    98.     }
    99.  
    100.     public override void OnStopNetwork()
    101.     {
    102.         SubscribeToTimeManager(false);
    103.     }
    104.  
    105.     private void OnDestroy()
    106.     {
    107.         SubscribeToTimeManager(false);
    108.     }
    109.  
    110.     [Replicate]
    111.     private void Move(MoveData moveData, ReplicateState state = ReplicateState.Invalid, Channel channel = Channel.Unreliable)
    112.     {
    113.  
    114.         Rotate(moveData.Horizontal);
    115.         Vector2 forceToAdd = Vector2.zero;
    116.         forceToAdd = CalculateForceToAdd(moveData.Vertical);
    117.         if (forceToAdd != Vector2.zero)
    118.         {
    119.             PredictionRigidbody2D.AddForce(forceToAdd);
    120.         }
    121.         //Shoot(_shoot);
    122.         //Wrap(_wrap);
    123.  
    124.         PredictionRigidbody2D.Simulate();
    125.     }
    126.  
    127.     [Reconcile]
    128.     private void Reconciliation(ReconcileData rd, Channel channel = Channel.Unreliable)
    129.     {
    130.         Rigidbody2D rb = PredictionRigidbody2D.Rigidbody2D;
    131.         rb.SetState(rd.Rigidbody2DState);
    132.         PredictionRigidbody2D.Reconcile(rd.PredictionRigidbody2D);
    133.     }
    134. }
    135.  

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using FishNet.Object;
    5. using FishNet.Object.Prediction;
    6.  
    7. public class MovementScript : NetworkBehaviour
    8. {
    9.     protected Ship ship;
    10.     public ShipExhaustManager shipExhaustManager;
    11.  
    12.     protected bool _wrap;
    13.     protected bool _shoot;
    14.    
    15.     protected struct MoveData : IReplicateData
    16.     {
    17.         public bool Wrap;
    18.         public bool Shoot;
    19.         public readonly float Horizontal;
    20.         public readonly float Vertical;
    21.         private uint _tick;
    22.         public MoveData(bool shoot, bool wrap, float horizontal, float vertical)
    23.         {
    24.             Wrap = wrap;
    25.             Shoot = shoot;
    26.             Horizontal = horizontal;
    27.             Vertical = vertical;
    28.             _tick = 0;
    29.         }
    30.  
    31.         public void Dispose() { }
    32.         public uint GetTick() => _tick;
    33.         public void SetTick(uint value) => _tick = value;
    34.     }
    35.  
    36.     private void Start()
    37.     {
    38.         ship = GetComponent<Ship>();
    39.     }
    40.  
    41.     protected virtual void Rotate(float inputVectorX)
    42.     {
    43.         if (ship == null) return;
    44.         if (ship.GetShipSO() == null) return;
    45.         float rotation = inputVectorX * ship.GetShipSO().rotationSpeed * (float)TimeManager.TickDelta;
    46.         transform.Rotate(Vector3.forward, -rotation);
    47.         if (inputVectorX != 0) { ship.rb.angularVelocity = 0; }
    48.     }
    49.  
    50.     protected virtual Vector2 CalculateForceToAdd(float inputVectorY)
    51.     {
    52.         Vector2 forceToAdd = Vector2.zero;
    53.         switch (inputVectorY)
    54.         {
    55.             case > 0:
    56.                 ship.shipState = Ship.ShipState.Accelerating;
    57.                 shipExhaustManager.CreateExhaust();
    58.                 if (ship.rb.velocity.magnitude <= ship.GetShipSO().maxSpeedWhileThrusting)
    59.                 {
    60.                     forceToAdd = transform.up * ship.GetShipSO().thrustForce * inputVectorY;
    61.                 }
    62.                 else
    63.                 {
    64.                     Vector2 adjustedForce = transform.up * ship.GetShipSO().thrustForce * inputVectorY;
    65.                     Vector2 oppositeForce = ship.rb.velocity * -1; //cancel out forward velocity from new force to prevent accelerating in the old direction beyond speedlimit
    66.  
    67.                     forceToAdd = adjustedForce + oppositeForce; //add only net new force that isnt breaking speed limits
    68.                 }
    69.                 break;
    70.             case < 0:
    71.                 if (ship.GetShipSO().canReverse && (ship.rb.velocity.magnitude < ship.GetShipSO().maxSpeedWhileThrusting))
    72.                 {
    73.                     ship.shipState = Ship.ShipState.Decelerating;
    74.                     forceToAdd = transform.up * ship.GetShipSO().thrustForce * inputVectorY;
    75.                 }
    76.                 break;
    77.             case 0:
    78.                 ship.shipState = Ship.ShipState.Neutral;
    79.                 break;
    80.         }
    81.  
    82.         return forceToAdd;
    83.     }
    84. }
    85.  
     
  2. vexstorm

    vexstorm

    Joined:
    Apr 26, 2020
    Posts:
    9
    Hey! FirstGearGames, the creator of FishNet, is actually in the process of updating prediction documentation to accurately match the recent updates to prediction as we speak! He expects those updates to be a day or so. :)
     
    gxslim likes this.
  3. gxslim

    gxslim

    Joined:
    Feb 10, 2013
    Posts:
    4
    Great - hopefully that will reveal that I've done something horribly wrong and I can fix it :D
     
    vexstorm likes this.
  4. Punfish

    Punfish

    Joined:
    Dec 7, 2014
    Posts:
    422
    The documentation updates are live. There's going to be more coming for prediction in the next several days as well.