Clients Rigidbodies aren't moving on Server

Hello everyone,
I am making a ClientPrediction using this tutorial : Unity Netcode For Gameobject - Client Prediction - YouTube.
Everything is working correctly, but my players aren’t moving server side.
What I can tell you is that my problem is specific to rigidbodies : replacing m_Rigidbody.velocity = … by Transform.Translate(…) is working on both sides. So the server is correctly receiving infos, but it is blocked.

Here is my class for moving :

using Unity.Netcode;
using UnityEngine;

public class PlayerMovementPredictive : NetworkBehaviour
{
    [SerializeField] private int defaultSpeed;
  
    [SerializeField] private Transform turret;

    [SerializeField] private Mesh debugMesh;

    private Rigidbody m_Rigidbody;
    private PlayerInputs m_PlayerInputs;
    private Camera m_MainCamera;
  
    private bool m_IsMousePresent;
  
    private Vector3 m_LookTarget;
    private float m_Speed;

  
  
    private int m_Tick;
    private const float TickRate = 1f / 60f;
    private float m_TickDeltaTime;

    private const int BufferSize = 1024;

    private readonly InputState[] m_InputStates = new InputState[BufferSize];
    private readonly TransformState[] m_TransformStates = new TransformState[BufferSize];
  
    private readonly NetworkVariable<TransformState> m_ServerTransformState = new();
    private TransformState m_PreviousTransformState;

    public override void OnNetworkSpawn()
    {
        base.OnNetworkSpawn();
        m_Rigidbody = GetComponent<Rigidbody>();
        m_PlayerInputs = GetComponent<PlayerInputs>();
        m_MainCamera = Camera.main;
      
        m_IsMousePresent = Input.mousePresent;
        m_Speed = defaultSpeed;

        m_ServerTransformState.OnValueChanged += OnServerStateChanged;
    }

    private void OnServerStateChanged(TransformState previousValue, TransformState newValue)
    {
        m_PreviousTransformState = previousValue;
    }
  
    private void Update()
    {
        var moveInput = m_PlayerInputs.PlayerControls.Player.Move.ReadValue<Vector2>();
        UpdateLookTarget();
      
        if (IsClient && IsLocalPlayer)
        {
            ProcessLocalPlayerMovement(moveInput, m_LookTarget);
        }
        else
        {
            ProcessSimulatedPlayerMovement();
        }
    }
  
    private void UpdateLookTarget()
    {
        if (m_IsMousePresent)
        {
            var ray = m_MainCamera.ScreenPointToRay(m_PlayerInputs.PlayerControls.Player.Look.ReadValue<Vector2>());
          
            if (Physics.Raycast(ray, out var hit, 100000, 1 <<  9))
            {
                m_LookTarget = new Vector3(hit.point.x, 0, hit.point.z);
            }
        }
        else
        {
            var tmpLook = m_PlayerInputs.PlayerControls.Player.Look.ReadValue<Vector2>();
            if (tmpLook.x is not 0 || tmpLook.y is not 0)
            {
                m_LookTarget = new Vector3(tmpLook.x, 0, tmpLook.y);
            }
        }
    }
  
    private void ProcessLocalPlayerMovement(Vector2 moveInput, Vector3 lookTarget)
    {
        m_TickDeltaTime += Time.deltaTime;

        if (m_TickDeltaTime > TickRate)
        {
            var bufferIndex = m_Tick % BufferSize;

            if (!IsServer)
            {
                MoveWithTickServerRpc(m_Tick, moveInput, lookTarget);
                Move(moveInput);
                Look(lookTarget);
            }
            else
            {
                Move(moveInput);
                Look(lookTarget);
              
                var state = new TransformState()
                {
                    Tick = m_Tick,
                    Position = transform.position,
                    Rotation = transform.rotation,
                    TurretRotation = turret.rotation,
                    IsMoving = true
                };

                m_ServerTransformState.Value = state;
            }

            var inputState = new InputState()
            {
                Tick = m_Tick,
                MoveInput = moveInput,
                LookTarget = lookTarget,
            };
          
            var transformState = new TransformState()
            {
                Tick = m_Tick,
                Position = transform.position,
                Rotation = transform.rotation,
                TurretRotation = turret.rotation,
                IsMoving = true
            };
          
            m_InputStates[bufferIndex] = inputState;
            m_TransformStates[bufferIndex] = transformState;
          
            m_TickDeltaTime -= TickRate;
            if (m_Tick is BufferSize)
            {
                m_Tick = 0;
            }
            else
            {
                m_Tick += 1;
            }
        }
    }

    [ServerRpc]
    private void MoveWithTickServerRpc(int tick, Vector2 moveInput, Vector3 lookTarget)
    {
        Move(moveInput);
        Look(lookTarget);

        var transformState = new TransformState()
        {
            Tick = tick,
            Position = transform.position,
            Rotation = transform.rotation,
            TurretRotation = turret.rotation,
            IsMoving = true
        };

        m_ServerTransformState.Value = transformState;
    }

    private void ProcessSimulatedPlayerMovement()
    {
        m_TickDeltaTime += Time.deltaTime;

        if (m_TickDeltaTime > TickRate)
        {
            if (m_ServerTransformState.Value is not null && m_ServerTransformState.Value.IsMoving)
            {
                transform.position = m_ServerTransformState.Value.Position;
                transform.rotation = m_ServerTransformState.Value.Rotation;
                turret.rotation = m_ServerTransformState.Value.TurretRotation;
            }
          
            m_TickDeltaTime -= TickRate;
            if (m_Tick is BufferSize)
            {
                m_Tick = 0;
            }
            else
            {
                m_Tick += 1;
            }
        }
    }
  
    private void Move(Vector2 moveInput)
    {
        moveInput = Vector2.ClampMagnitude(moveInput, 1);
        var moveVector = new Vector3(moveInput.x, 0, moveInput.y);
      
        if (moveVector != Vector3.zero)
        {
            m_Rigidbody.velocity = moveVector * m_Speed;
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(moveVector), 0.1f);
        }
        else
        {
            m_Rigidbody.velocity = Vector3.Slerp(m_Rigidbody.velocity, Vector3.zero, 0.1f);
        }
    }
  
    private void Look(Vector3 lookTarget)
    {
        if (lookTarget == Vector3.zero)
            return;
      
        if (m_IsMousePresent)
        {
            lookTarget -= transform.position;
        }
      
        lookTarget.y = 0;
        turret.rotation = Quaternion.LookRotation(lookTarget);
    }

    private void OnDrawGizmos()
    {
        if (m_ServerTransformState.Value is not null)
        {
            Gizmos.color = Color.magenta;
            Gizmos.DrawMesh(debugMesh, m_ServerTransformState.Value.Position, m_ServerTransformState.Value.Rotation);
        }
    }
}

Also my player’s components :

I am using Unity 2022.3.1f1 and NGO 1.4.0

Thank you !

Hi, did you try using a NetworkRigidbody instead of a Rigidbody?

Ok now that my minds are more clear and it’s not 4am I can say that disabling transform.position = m_ServerTransformState.Value.Position; (line 177) fixed the problem at the cost of… syncing.

I believe I am doing something in the wrong order, I will try to find the issue but any help is appreciated ^^

Also if you spot something that is not “good practice” I would love to hear about ;

Hi Riku,
I did try it, but with my setup I want to be the only master in syncing the transform so that is why I removed network transform and networking rigidbody, I still have them on my non-predictive player prefab.

I found the issue atleast ! After some guessing I can say that ProcessSimulatedPlayerMovement() is interferring with the
MoveWithTickServerRpc() and that is why my character happen to be “lock” on place on the server-side.

Can you try to spot my mistake T_T ? It is in the “order” of the logic, I will try to find a fix aswell and keep you updated :wink:

Thank you for you time

Ok I think I found out why, rigidbody.velocity is set over time compared to transform.translate, so I think my
ProcessLocalPlayerMovement is stopping player velocity or something like that

Thanks for sharing! I see you’re also writing to m_ServerTransformState.Value in both the RPC and the ProcessSimulatedPlayerMovement(), so you could have race conditions there

1 Like

OKAY,
It’s silly to do ProcessSimulatedPlayerMovement() in Update() if you’re the server, it’ll undo the move you made haha, it took me a while to figure out that was what you were saying Riku xD

Thank you once again ^^

hehe, happy to see you found that helpful :smile: