Using CharacterController.Move / SimpleMove on clients

Hi, I’m dead stuck on trying to move character controllers that are not owned by the host (server and client).

I used the Starter Assets third person controller and modified the ThirdPersonController.cs script. It runs correct on the host but the clients doesn’t move, Everything seems to be working as in the variables are updating, the speed variable and the targetDirection variable are correct on the clients. I know the method is running that is suppose to be moving the Character Controller. But the Character Controller doesn’t move on anyone except the host! I tried Move and SimpleMove, same result.

MoveController method is running on the server on Update (Line 263)
If its not the hosts player object it will call the TellClientMoveClientRpc method (Line 276)

Regards

using Unity.Netcode;
using UnityEngine;
#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
using UnityEngine.InputSystem;
#endif

/* Note: animations are called via the controller for both the character and capsule using animator null checks
 */

namespace StarterAssets
{
   [RequireComponent(typeof(CharacterController))]
#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
   [RequireComponent(typeof(PlayerInput))]
#endif
   public class ThirdPersonController : NetworkBehaviour
   {
       [Header("Player")]
       [Tooltip("Move speed of the character in m/s")]
       public float MoveSpeed = 2.0f;
       [Tooltip("Sprint speed of the character in m/s")]
       public float SprintSpeed = 5.335f;
       [Tooltip("How fast the character turns to face movement direction")]
       [Range(0.0f, 0.3f)]
       public float RotationSmoothTime = 0.12f;
       [Tooltip("Acceleration and deceleration")]
       public float SpeedChangeRate = 10.0f;

       [Space(10)]
       [Tooltip("The height the player can jump")]
       public float JumpHeight = 1.2f;
       [Tooltip("The character uses its own gravity value. The engine default is -9.81f")]
       public float Gravity = -15.0f;

       [Space(10)]
       [Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
       public float JumpTimeout = 0.50f;
       [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
       public float FallTimeout = 0.15f;

       [Header("Player Grounded")]
       [Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
       public bool Grounded = true;
       [Tooltip("Useful for rough ground")]
       public float GroundedOffset = -0.14f;
       [Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
       public float GroundedRadius = 0.28f;
       [Tooltip("What layers the character uses as ground")]
       public LayerMask GroundLayers;

       [Header("Cinemachine")]
       [Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")]
       public GameObject CinemachineCameraTarget;
       [Tooltip("How far in degrees can you move the camera up")]
       public float TopClamp = 70.0f;
       [Tooltip("How far in degrees can you move the camera down")]
       public float BottomClamp = -30.0f;
       [Tooltip("Additional degress to override the camera. Useful for fine tuning camera position when locked")]
       public float CameraAngleOverride = 0.0f;
       [Tooltip("For locking the camera position on all axis")]
       public bool LockCameraPosition = false;

       // cinemachine
       private float _cinemachineTargetYaw;
       private float _cinemachineTargetPitch;

       // player
       private float _speed;
       private float _animationBlend;
       private float _targetRotation = 0.0f;
       private float _rotationVelocity;
       private float _verticalVelocity;
       private float _terminalVelocity = 53.0f;

       // timeout deltatime
       private float _jumpTimeoutDelta;
       private float _fallTimeoutDelta;

       // animation IDs
       private int _animIDSpeed;
       private int _animIDGrounded;
       private int _animIDJump;
       private int _animIDFreeFall;
       private int _animIDMotionSpeed;

       private Animator _animator;
       private CharacterController _controller;
       private StarterAssetsInputs _input;
       private GameObject _mainCamera;

       private const float _threshold = 0.01f;

       private bool _hasAnimator;


       NetworkObject myObject;
       [SerializeField] NetworkVariable<Vector3> targetDirection = new NetworkVariable<Vector3>(NetworkVariableReadPermission.Everyone);
       [SerializeField] NetworkVariable<float> verticalVelocity = new NetworkVariable<float>();

       Vector3 oldTargetDirection;
       float oldTargetVelocity;

       private void Awake()
       {
           // get a reference to our main camera
           if (_mainCamera == null)
           {
               _mainCamera = transform.Find("PlayerCamera").gameObject;
               _mainCamera.transform.parent = null;
           }
           //myObject = transform.GetComponentInParent<NetworkObject>();
       }
       //No furhter!
       private void Start()
       {
           _hasAnimator = TryGetComponent(out _animator);
           _controller = GetComponent<CharacterController>();
           _input = GetComponent<StarterAssetsInputs>();

           AssignAnimationIDs();

           // reset our timeouts on start
           _jumpTimeoutDelta = JumpTimeout;
           _fallTimeoutDelta = FallTimeout;
       }

       private void Update()
       {
           _hasAnimator = TryGetComponent(out _animator);
          
           if(IsServer)
           {
               Debug.Log($"Running on server for {OwnerClientId}");
               // move the player
               MoveController();
           }
           if(IsClient && IsOwner)
           {
               JumpAndGravity();
               GroundedCheck();
               if(_controller.enabled) Move();
           }
       }

       private void LateUpdate()
       {
           if(IsOwner) CameraRotation();
       }

       private void AssignAnimationIDs()
       {
           _animIDSpeed = Animator.StringToHash("Speed");
           _animIDGrounded = Animator.StringToHash("Grounded");
           _animIDJump = Animator.StringToHash("Jump");
           _animIDFreeFall = Animator.StringToHash("FreeFall");
           _animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
       }

       private void GroundedCheck()
       {
           // set sphere position, with offset
           Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z);
           Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers, QueryTriggerInteraction.Ignore);

           // update animator if using character
           if (_hasAnimator)
           {
               _animator.SetBool(_animIDGrounded, Grounded);
           }
       }

       private void CameraRotation()
       {
           // if there is an input and camera position is not fixed
           if (_input.look.sqrMagnitude >= _threshold && !LockCameraPosition)
           {
               _cinemachineTargetYaw += _input.look.x * Time.deltaTime;
               _cinemachineTargetPitch += _input.look.y * Time.deltaTime;
           }

           // clamp our rotations so our values are limited 360 degrees
           _cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
           _cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);

           // Cinemachine will follow this target
           CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride, _cinemachineTargetYaw, 0.0f);
       }

       private void Move()
       {
           // set target speed based on move speed, sprint speed and if sprint is pressed
           float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;

           // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon

           // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
           // if there is no input, set the target speed to 0
           if (_input.move == Vector2.zero) targetSpeed = 0.0f;

           // a reference to the players current horizontal velocity
           float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;

           float speedOffset = 0.1f;
           float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;

           // accelerate or decelerate to target speed
           if (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset)
           {
               // creates curved result rather than a linear one giving a more organic speed change
               // note T in Lerp is clamped, so we don't need to clamp our speed
               _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude, Time.deltaTime * SpeedChangeRate);

               // round speed to 3 decimal places
               _speed = Mathf.Round(_speed * 1000f) / 1000f;
           }
           else
           {
               _speed = targetSpeed;
           }
           _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);

           // normalise input direction
           Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;

           // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
           // if there is a move input rotate player when the player is moving
           if (_input.move != Vector2.zero)
           {
               _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;
               float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity, RotationSmoothTime);

               // rotate to face input direction relative to camera position
               transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
           }


           Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;
           if(targetDirection != oldTargetDirection)
            {
               UpdateClientTargetDirectionServerRpc(targetDirection);
               oldTargetDirection = targetDirection;
            }

           // update animator if using character
           if (_hasAnimator)
           {
               _animator.SetFloat(_animIDSpeed, _animationBlend);
               _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
           }
       }
       [ServerRpc]
       public void UpdateClientTargetDirectionServerRpc(Vector3 _targetDirection)
        {
           targetDirection.Value = _targetDirection;
           //targetDirection.SetDirty(true);
        }
       [ServerRpc]
       public void UpdateClientVerticalVelocityServerRpc(float _verticalVelocity)
        {
           verticalVelocity.Value = _verticalVelocity;
        }
       void MoveController()
       {
           if(!IsOwner)
            {
               Debug.Log($"Trying to move {_controller.GetComponentInParent<NetworkObject>().OwnerClientId} with targetDirection : {targetDirection.Value} and verticalVelocity : {verticalVelocity.Value}.");
            }
           if (IsHost && IsLocalPlayer)
           {
               //_controller.Move(targetDirection.Value.normalized * (_speed * Time.deltaTime) + new Vector3(0.0f, verticalVelocity.Value, 0.0f) * Time.deltaTime);
               _controller.SimpleMove(targetDirection.Value.normalized * (_speed /** Time.deltaTime*/) + new Vector3(0.0f, verticalVelocity.Value, 0.0f) /** Time.deltaTime*/);
           }
           else TellClientMoveClientRpc();
       }
       [ClientRpc]
       public void TellClientMoveClientRpc()
        {
           if (_controller == null || !IsLocalPlayer) return;
           Debug.Log($"TellClientMoveClientRpc: Target direction is : {targetDirection.Value.normalized}. Speed is : {_speed}. Vertical Velocity is {verticalVelocity.Value}.");
           //transform.parent.name = "Player " + myObject.OwnerClientId.ToString();
           _controller.SimpleMove(targetDirection.Value.normalized * (_speed /* Time.deltaTime*/) + new Vector3(0.0f, verticalVelocity.Value, 0.0f) /** Time.deltaTime*/);
       }

       private void JumpAndGravity()
       {
           if (Grounded)
           {
               // reset the fall timeout timer
               _fallTimeoutDelta = FallTimeout;

               // update animator if using character
               if (_hasAnimator)
               {
                   _animator.SetBool(_animIDJump, false);
                   _animator.SetBool(_animIDFreeFall, false);
               }

               // stop our velocity dropping infinitely when grounded
               if (_verticalVelocity < 0.0f)
               {
                   _verticalVelocity = -2f;
               }

               // Jump
               if (_input.jump && _jumpTimeoutDelta <= 0.0f)
               {
                   // the square root of H * -2 * G = how much velocity needed to reach desired height
                   _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);

                   // update animator if using character
                   if (_hasAnimator)
                   {
                       _animator.SetBool(_animIDJump, true);
                   }
               }

               // jump timeout
               if (_jumpTimeoutDelta >= 0.0f)
               {
                   _jumpTimeoutDelta -= Time.deltaTime;
               }
           }
           else
           {
               // reset the jump timeout timer
               _jumpTimeoutDelta = JumpTimeout;

               // fall timeout
               if (_fallTimeoutDelta >= 0.0f)
               {
                   _fallTimeoutDelta -= Time.deltaTime;
               }
               else
               {
                   // update animator if using character
                   if (_hasAnimator)
                   {
                       _animator.SetBool(_animIDFreeFall, true);
                   }
               }

               // if we are not grounded, do not jump
               _input.jump = false;
           }

           // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
           if (_verticalVelocity < _terminalVelocity)
           {
               _verticalVelocity += Gravity * Time.deltaTime;
           }
       }

       private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
       {
           if (lfAngle < -360f) lfAngle += 360f;
           if (lfAngle > 360f) lfAngle -= 360f;
           return Mathf.Clamp(lfAngle, lfMin, lfMax);
       }

       private void OnDrawGizmosSelected()
       {
           Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
           Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);

           if (Grounded) Gizmos.color = transparentGreen;
           else Gizmos.color = transparentRed;
          
           // when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
           Gizmos.DrawSphere(new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z), GroundedRadius);
       }
   }
}

Not sure if this is your problem but I ran into something very similar when using the unity default First Person Character Controller.

Unity Netcode is server authoritative by default. I had to use the Client Network Transform https://docs-multiplayer.unity3d.com/docs/components/networktransform/index.html#clientnetworktransform in order to get my clients moving.

1 Like

For anyone interested I changed the code so the position and rotation works on hosts and clients. Not much credit to me. I mainly learned from these tutorials and applied it to the ThirdPersonController script :

and

Here is my solution if anyone is interested. It seems to be working (no animator syncing). Use it, improve it.

using Unity.Netcode;
using UnityEngine;
#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
using UnityEngine.InputSystem;
#endif

/* Note: animations are called via the controller for both the character and capsule using animator null checks
*/

namespace StarterAssets
{
    [RequireComponent(typeof(CharacterController))]
#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
    [RequireComponent(typeof(PlayerInput))]
#endif
    public class ThirdPersonController : NetworkBehaviour
    {
        [Header("Player")]
        [Tooltip("Move speed of the character in m/s")]
        public float MoveSpeed = 2.0f;
        [Tooltip("Sprint speed of the character in m/s")]
        public float SprintSpeed = 5.335f;
        [Tooltip("How fast the character turns to face movement direction")]
        [Range(0.0f, 0.3f)]
        public float RotationSmoothTime = 0.12f;
        [Tooltip("Acceleration and deceleration")]
        public float SpeedChangeRate = 10.0f;

        [Space(10)]
        [Tooltip("The height the player can jump")]
        public float JumpHeight = 1.2f;
        [Tooltip("The character uses its own gravity value. The engine default is -9.81f")]
        public float Gravity = -15.0f;

        [Space(10)]
        [Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
        public float JumpTimeout = 0.50f;
        [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
        public float FallTimeout = 0.15f;

        [Header("Player Grounded")]
        [Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
        public bool Grounded = true;
        [Tooltip("Useful for rough ground")]
        public float GroundedOffset = -0.14f;
        [Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
        public float GroundedRadius = 0.28f;
        [Tooltip("What layers the character uses as ground")]
        public LayerMask GroundLayers;

        [Header("Cinemachine")]
        [Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")]
        public GameObject CinemachineCameraTarget;
        [Tooltip("How far in degrees can you move the camera up")]
        public float TopClamp = 70.0f;
        [Tooltip("How far in degrees can you move the camera down")]
        public float BottomClamp = -30.0f;
        [Tooltip("Additional degress to override the camera. Useful for fine tuning camera position when locked")]
        public float CameraAngleOverride = 0.0f;
        [Tooltip("For locking the camera position on all axis")]
        public bool LockCameraPosition = false;

        // cinemachine
        private float _cinemachineTargetYaw;
        private float _cinemachineTargetPitch;

        // player
        private float _speed;
        private float _animationBlend;
        private float _targetRotation = 0.0f;
        private float _rotationVelocity;
        private float _verticalVelocity;
        private float _terminalVelocity = 53.0f;

        // timeout deltatime
        private float _jumpTimeoutDelta;
        private float _fallTimeoutDelta;

        // animation IDs
        private int _animIDSpeed;
        private int _animIDGrounded;
        private int _animIDJump;
        private int _animIDFreeFall;
        private int _animIDMotionSpeed;

        private Animator _animator;
        private CharacterController _controller;
        private StarterAssetsInputs _input;
        private GameObject _mainCamera;

        private const float _threshold = 0.01f;

        private bool _hasAnimator;


        NetworkObject myObject;
        [SerializeField] NetworkVariable<Vector3> inputDirection = new NetworkVariable<Vector3>();
        [SerializeField] NetworkVariable<float> verticalVelocity = new NetworkVariable<float>();
        [SerializeField] NetworkVariable<float> movementTargetSpeed = new NetworkVariable<float>();
        [SerializeField] NetworkVariable<float> targetRotation = new NetworkVariable<float>();
        [SerializeField] NetworkVariable<float> inputMagnitude = new NetworkVariable<float>();

        float oldSpeed;
        float oldRotation;
        float oldInputMagnitude;
        /// <summary>
        /// Old Time Since Server Start
        /// </summary>
        float oldTSSS = 0f;

        private void Awake()
        {
            // get a reference to our main camera
            if (_mainCamera == null)
            {
                _mainCamera = transform.Find("PlayerCamera").gameObject;
                _mainCamera.transform.parent = null;
            }
            //myObject = transform.GetComponentInParent<NetworkObject>();
        }
        //No furhter!
        private void Start()
        {
            _hasAnimator = TryGetComponent(out _animator);
            _controller = GetComponent<CharacterController>();
            _input = GetComponent<StarterAssetsInputs>();

            AssignAnimationIDs();

            // reset our timeouts on start
            _jumpTimeoutDelta = JumpTimeout;
            _fallTimeoutDelta = FallTimeout;
        }

        private void Update()
        {
            _hasAnimator = TryGetComponent(out _animator);
           
            /*if(IsServer)
            {
                Debug.Log($"Running on server for {OwnerClientId}");
                // move the player
                MoveController();
            }
            else
            {
                Debug.Log($"{OwnerClientId} IsServer : {IsServer}. IsOwnedByServer : {IsOwnedByServer}. IsClient : {IsClient}. IsSpawned : {IsSpawned}. IsLocalPlayer : {IsLocalPlayer}");
            }*/
            if(IsClient && IsOwner)
            {
                JumpAndGravity();
                GroundedCheck();
                if(_controller.enabled) ClientInput();
            }
            if (IsServer)
            {
                MoveController();

            }
        }

        private void LateUpdate()
        {
            if(IsOwner) CameraRotation();
        }

        private void AssignAnimationIDs()
        {
            _animIDSpeed = Animator.StringToHash("Speed");
            _animIDGrounded = Animator.StringToHash("Grounded");
            _animIDJump = Animator.StringToHash("Jump");
            _animIDFreeFall = Animator.StringToHash("FreeFall");
            _animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
        }

        private void GroundedCheck()
        {
            // set sphere position, with offset
            Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z);
            Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers, QueryTriggerInteraction.Ignore);

            // update animator if using character
            if (_hasAnimator)
            {
                _animator.SetBool(_animIDGrounded, Grounded);
            }
        }

        private void CameraRotation()
        {
            // if there is an input and camera position is not fixed
            if (_input.look.sqrMagnitude >= _threshold && !LockCameraPosition)
            {
                _cinemachineTargetYaw += _input.look.x * Time.deltaTime;
                _cinemachineTargetPitch += _input.look.y * Time.deltaTime;
            }

            // clamp our rotations so our values are limited 360 degrees
            _cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
            _cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);

            // Cinemachine will follow this target
            CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride, _cinemachineTargetYaw, 0.0f);
        }

        private void ClientInput()
        {
            // set target speed based on move speed, sprint speed and if sprint is pressed
            float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;

            // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon

            // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
            // if there is no input, set the target speed to 0
            if (_input.move == Vector2.zero) targetSpeed = 0.0f;

            if (targetSpeed != oldSpeed)
            {
                UpdateClientSpeedServerRPC(targetSpeed);
                oldSpeed = targetSpeed;
            }

            // normalise input direction
            Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;

            float inputMagnitude = _input.analogMovement ? inputDirection.magnitude : 1f;

            if(inputMagnitude != oldInputMagnitude)
            {
                UpdateClientInputMagnitudeServerRpc(inputMagnitude);
                oldInputMagnitude = inputMagnitude;
            }
            // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
            // if there is a move input rotate player when the player is moving
            if (inputDirection != Vector3.zero)
            {
                _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;
            }
            // rotate to face input direction relative to camera position
            if (_targetRotation != oldRotation)
            {
                UpdateClientRotationServerRpc(_targetRotation);
                oldRotation = _targetRotation;
            }

            _animationBlend = Mathf.Lerp(_animationBlend, movementTargetSpeed.Value, Time.deltaTime * SpeedChangeRate);
            // update animator if using character
            if (_hasAnimator)
            {
                _animator.SetFloat(_animIDSpeed, _animationBlend);
                _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
            }
        }
        [ServerRpc]
        public void UpdateClientInputMagnitudeServerRpc(float _inputMagnitude)
        {
            inputMagnitude.Value = _inputMagnitude;
        }
        [ServerRpc]
        public void UpdateClientSpeedServerRPC(float speed)
        {
            movementTargetSpeed.Value = speed;
        }
        [ServerRpc]
        public void UpdateClientVerticalVelocityServerRpc(float _verticalVelocity)
        {
            verticalVelocity.Value = _verticalVelocity;
        }
        [ServerRpc]
        public void UpdateClientRotationServerRpc(float _targetRotation)
        {
            targetRotation.Value = _targetRotation;
        }
        /// <summary>
        /// Callled from the server on this client
        /// </summary>
        public void MoveController()
        {
            if (_controller == null) return;

            // a reference to the players current horizontal velocity
            float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;

            float speedOffset = 0.1f;

            // accelerate or decelerate to target speed
            if (currentHorizontalSpeed < movementTargetSpeed.Value - speedOffset || currentHorizontalSpeed > movementTargetSpeed.Value + speedOffset)
            {
                // creates curved result rather than a linear one giving a more organic speed change
                // note T in Lerp is clamped, so we don't need to clamp our speed
                _speed = Mathf.Lerp(currentHorizontalSpeed, movementTargetSpeed.Value * inputMagnitude.Value, Time.deltaTime * SpeedChangeRate);

                // round speed to 3 decimal places
                _speed = Mathf.Round(_speed * 1000f) / 1000f;
            }
            else
            {
                _speed = movementTargetSpeed.Value;
            }

            // Calculate movement
            Vector3 targetDirection = Quaternion.Euler(0.0f, targetRotation.Value, 0.0f) * Vector3.forward;
            _controller.Move(targetDirection.normalized * (movementTargetSpeed.Value) * Time.deltaTime + new Vector3(0.0f, verticalVelocity.Value, 0.0f) * Time.deltaTime);
            // Calculate rotation
            float blendedRotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation.Value, ref _rotationVelocity, RotationSmoothTime);
            transform.rotation = Quaternion.Euler(0.0f, blendedRotation, 0.0f);
        }

        private void JumpAndGravity()
        {
            if (Grounded)
            {
                // reset the fall timeout timer
                _fallTimeoutDelta = FallTimeout;

                // update animator if using character
                if (_hasAnimator)
                {
                    _animator.SetBool(_animIDJump, false);
                    _animator.SetBool(_animIDFreeFall, false);
                }

                // stop our velocity dropping infinitely when grounded
                if (_verticalVelocity < 0.0f)
                {
                    _verticalVelocity = -2f;
                }

                // Jump
                if (_input.jump && _jumpTimeoutDelta <= 0.0f)
                {
                    // the square root of H * -2 * G = how much velocity needed to reach desired height
                    _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);

                    // update animator if using character
                    if (_hasAnimator)
                    {
                        _animator.SetBool(_animIDJump, true);
                    }
                }

                // jump timeout
                if (_jumpTimeoutDelta >= 0.0f)
                {
                    _jumpTimeoutDelta -= Time.deltaTime;
                }
            }
            else
            {
                // reset the jump timeout timer
                _jumpTimeoutDelta = JumpTimeout;

                // fall timeout
                if (_fallTimeoutDelta >= 0.0f)
                {
                    _fallTimeoutDelta -= Time.deltaTime;
                }
                else
                {
                    // update animator if using character
                    if (_hasAnimator)
                    {
                        _animator.SetBool(_animIDFreeFall, true);
                    }
                }

                // if we are not grounded, do not jump
                _input.jump = false;
            }

            // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
            if (_verticalVelocity < _terminalVelocity)
            {
                _verticalVelocity += Gravity * Time.deltaTime;
            }
            UpdateClientVerticalVelocityServerRpc(_verticalVelocity);
        }

        private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
        {
            if (lfAngle < -360f) lfAngle += 360f;
            if (lfAngle > 360f) lfAngle -= 360f;
            return Mathf.Clamp(lfAngle, lfMin, lfMax);
        }

        private void OnDrawGizmosSelected()
        {
            Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
            Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);

            if (Grounded) Gizmos.color = transparentGreen;
            else Gizmos.color = transparentRed;
           
            // when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
            Gizmos.DrawSphere(new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z), GroundedRadius);
        }
    }
}

Hi, i am having the very same problem. Tryed with networkTransform and ClientNetworkTransform but do not fix the issue. Someone has some ideas? The ThirdPersonController scritp i am using is below.

Regards.

using Unity.Netcode;
using UnityEngine;
using UnityEngine.UIElements;
#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
using UnityEngine.InputSystem;
#endif
/* Note: animations are called via the controller for both the character and capsule using animator null checks
*/
namespace StarterAssets
{
    [RequireComponent(typeof(CharacterController))]
#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
    [RequireComponent(typeof(PlayerInput))]
#endif
    public class ThirdPersonController : NetworkBehaviour
    {
        [Header("Player")]
        [Tooltip("Move speed of the character in m/s")]
        public float MoveSpeed = 2.0f;
        [Tooltip("Sprint speed of the character in m/s")]
        public float SprintSpeed = 5.335f;
        [Tooltip("How fast the character turns to face movement direction")]
        [Range(0.0f, 0.3f)]
        public float RotationSmoothTime = 0.12f;
        [Tooltip("Acceleration and deceleration")]
        public float SpeedChangeRate = 10.0f;

        [Space(10)]
        [Tooltip("The height the player can jump")]
        public float JumpHeight = 1.2f;
        [Tooltip("The character uses its own gravity value. The engine default is -9.81f")]
        public float Gravity = -15.0f;

        [Space(10)]
        [Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
        public float JumpTimeout = 0.50f;
        [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
        public float FallTimeout = 0.15f;

        [Header("Player Grounded")]
        [Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
        public bool Grounded = true;
        [Tooltip("Useful for rough ground")]
        public float GroundedOffset = -0.14f;
        [Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
        public float GroundedRadius = 0.28f;
        [Tooltip("What layers the character uses as ground")]
        public LayerMask GroundLayers;

        [Header("Cinemachine")]
        [Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")]
        public GameObject CinemachineCameraTarget;
        [Tooltip("How far in degrees can you move the camera up")]
        public float TopClamp = 70.0f;
        [Tooltip("How far in degrees can you move the camera down")]
        public float BottomClamp = -30.0f;
        [Tooltip("Additional degress to override the camera. Useful for fine tuning camera position when locked")]
        public float CameraAngleOverride = 0.0f;
        [Tooltip("For locking the camera position on all axis")]
        public bool LockCameraPosition = false;

        // cinemachine
        private float _cinemachineTargetYaw;
        private float _cinemachineTargetPitch;

        // player
        private float _speed;
        private float _animationBlend;
        private float _targetRotation = 0.0f;
        private float _rotationVelocity;
        private float _verticalVelocity;
        private float _terminalVelocity = 53.0f;

        // timeout deltatime
        private float _jumpTimeoutDelta;
        private float _fallTimeoutDelta;

        // animation IDs
        private int _animIDSpeed;
        private int _animIDGrounded;
        private int _animIDJump;
        private int _animIDFreeFall;
        private int _animIDMotionSpeed;

        private Animator _animator;
        private CharacterController _controller;
        private StarterAssetsInputs _input;
        private GameObject _mainCamera;

        private const float _threshold = 0.01f;

        private bool _hasAnimator;

        [SerializeField]
        private NetworkVariable<Vector3> networkPositionDirection = new NetworkVariable<Vector3>();

        [SerializeField]
        private NetworkVariable<Vector3> networkRotationDirection = new NetworkVariable<Vector3>();

        public override void OnNetworkSpawn()
        {
            base.OnNetworkSpawn();
            transform.position = new Vector3(Random.Range(-5f, 5f), 0, Random.Range(1f, 5f));
        }

        private void Awake()
        {
            // get a reference to our main camera
            if (_mainCamera == null)
            {
                _mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
            }
        }

        private void Start()
        {
            _hasAnimator = TryGetComponent(out _animator);
            _controller = GetComponent<CharacterController>();
            _input = GetComponent<StarterAssetsInputs>();

            AssignAnimationIDs();
               
            // reset our timeouts on start
            _jumpTimeoutDelta = JumpTimeout;
            _fallTimeoutDelta = FallTimeout;
        }

        private void Update()
        {
            _hasAnimator = TryGetComponent(out _animator);
           
            if (IsClient && IsOwner)
            {
                JumpAndGravity();
                GroundedCheck();
                Move();
                print("I am the client " + OwnerClientId + " in Update Position ");
            }
           
            UpdateServer();
        }

        private void LateUpdate()
        {
            CameraRotation();
        }

        private void UpdateServer()
        {
            if (networkPositionDirection.Value != Vector3.zero)
            {
                _controller.Move(networkPositionDirection.Value);
            }
            if (networkRotationDirection.Value != Vector3.zero)
            {
                transform.rotation = Quaternion.Euler(networkRotationDirection.Value);
            }
        }
       
        [ServerRpc]
        private void UpdateClientPositionServerRpc(Vector3 newPosition)
        {
            networkPositionDirection.Value = newPosition;

        }
       
        [ServerRpc]
        private void UpdateClientRotationServerRpc(Vector3 newRotation)
        {
            networkRotationDirection.Value = newRotation;
        }
       
        private void AssignAnimationIDs()
        {
            _animIDSpeed = Animator.StringToHash("Speed");
            _animIDGrounded = Animator.StringToHash("Grounded");
            _animIDJump = Animator.StringToHash("Jump");
            _animIDFreeFall = Animator.StringToHash("FreeFall");
            _animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
        }

        private void GroundedCheck()
        {
            // set sphere position, with offset
            Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z);
            Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers, QueryTriggerInteraction.Ignore);

            // update animator if using character
            if (_hasAnimator)
            {
                _animator.SetBool(_animIDGrounded, Grounded);
            }
        }

        private void CameraRotation()
        {
            // if there is an input and camera position is not fixed
            if (_input.look.sqrMagnitude >= _threshold && !LockCameraPosition)
            {
                _cinemachineTargetYaw += _input.look.x * Time.deltaTime;
                _cinemachineTargetPitch += _input.look.y * Time.deltaTime;
            }

            // clamp our rotations so our values are limited 360 degrees
            _cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
            _cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);

            // Cinemachine will follow this target
            CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride, _cinemachineTargetYaw, 0.0f);
        }

        private void Move()
        {
            // set target speed based on move speed, sprint speed and if sprint is pressed
            float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;

            // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon

            // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
            // if there is no input, set the target speed to 0
            if (_input.move == Vector2.zero) targetSpeed = 0.0f;

            // a reference to the players current horizontal velocity
            float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;

            float speedOffset = 0.1f;
            float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;

            // accelerate or decelerate to target speed
            if (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset)
            {
                // creates curved result rather than a linear one giving a more organic speed change
                // note T in Lerp is clamped, so we don't need to clamp our speed
                _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude, Time.deltaTime * SpeedChangeRate);

                // round speed to 3 decimal places
                _speed = Mathf.Round(_speed * 1000f) / 1000f;
            }
            else
            {
                _speed = targetSpeed;
            }
            _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);

            // normalise input direction
            Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;

            // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
            // if there is a move input rotate player when the player is moving
            if (_input.move != Vector2.zero)
            {
                _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg +
                                  _mainCamera.transform.eulerAngles.y;
                float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
                    RotationSmoothTime);
               
                //transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
                UpdateClientRotationServerRpc(new Vector3(0.0f, rotation, 0.0f));
            }

            Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;
            Vector3 targetMovement = targetDirection.normalized * (_speed * Time.deltaTime) +
                                     new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime;
           
            //_controller.Move(targetMovement);
            UpdateClientPositionServerRpc(targetMovement);
           
            // update animator if using character
            if (_hasAnimator)
            {
                _animator.SetFloat(_animIDSpeed, _animationBlend);
                _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
            }
        }

        private void JumpAndGravity()
        {
            if (Grounded)
            {
                // reset the fall timeout timer
                _fallTimeoutDelta = FallTimeout;

                // update animator if using character
                if (_hasAnimator)
                {
                    _animator.SetBool(_animIDJump, false);
                    _animator.SetBool(_animIDFreeFall, false);
                }

                // stop our velocity dropping infinitely when grounded
                if (_verticalVelocity < 0.0f)
                {
                    _verticalVelocity = -2f;
                }

                // Jump
                if (_input.jump && _jumpTimeoutDelta <= 0.0f)
                {
                    // the square root of H * -2 * G = how much velocity needed to reach desired height
                    _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);

                    // update animator if using character
                    if (_hasAnimator)
                    {
                        _animator.SetBool(_animIDJump, true);
                    }
                }

                // jump timeout
                if (_jumpTimeoutDelta >= 0.0f)
                {
                    _jumpTimeoutDelta -= Time.deltaTime;
                }
            }
            else
            {
                // reset the jump timeout timer
                _jumpTimeoutDelta = JumpTimeout;

                // fall timeout
                if (_fallTimeoutDelta >= 0.0f)
                {
                    _fallTimeoutDelta -= Time.deltaTime;
                }
                else
                {
                    // update animator if using character
                    if (_hasAnimator)
                    {
                        _animator.SetBool(_animIDFreeFall, true);
                    }
                }

                // if we are not grounded, do not jump
                _input.jump = false;
            }

            // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
            if (_verticalVelocity < _terminalVelocity)
            {
                _verticalVelocity += Gravity * Time.deltaTime;
            }
        }

        private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
        {
            if (lfAngle < -360f) lfAngle += 360f;
            if (lfAngle > 360f) lfAngle -= 360f;
            return Mathf.Clamp(lfAngle, lfMin, lfMax);
        }

        private void OnDrawGizmosSelected()
        {
            Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
            Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);

            if (Grounded) Gizmos.color = transparentGreen;
            else Gizmos.color = transparentRed;
           
            // when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
            Gizmos.DrawSphere(new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z), GroundedRadius);
        }
    }
}

I think the problem may be that you are not initializing the Vector3 values of your NetworkVariables.

Unfortunately initializing the networkVariables values do not solve the problem. I did this to init the values, maybe there is a better/smart way?

[SerializeField]
        private NetworkVariable<Vector3> networkPositionDirection = new NetworkVariable<Vector3>(Vector3.zero);

        [SerializeField]
        private NetworkVariable<Vector3> networkRotationDirection = new NetworkVariable<Vector3>(Vector3.zero);

I think i found the problem. as said there MLAPI with New Input System -> Clients have the wrong "Actions" in "Player Input" component In the Player Input component, the Host player has the correct Actions, but the clients have a cloned version of it, which doesn’t exist. Unfortunately the code provided in the thread does not work for me.
I am working on a slimilar solution to assign the correct Action after the player spawn, but if any of you have a better idea please share here.

Regards

Im having the same issue. Anyone has a fix?

For any one reading today, I would suggest disabling the player input component and enabling it in the OnNetworkSpawn override:

public override void OnNetworkSpawn()
{
    if (!IsOwner) return;
    GetComponent<PlayerInput>().enabled = true;
}

And as this thread is several month old I’m tagging it as resolved (not expecting any answer but will reply if needed)!

1 Like

Literally having the same problem here. Finally found a thread
Here is my code if anyone has resolved this

using UnityEngine;
using Unity.Netcode;
using Cinemachine;
using UnityEngine.Networking;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif

/* Note: animations are called via the controller for both the character and capsule using animator null checks
*/

namespace StarterAssets
{
[RequireComponent(typeof(CharacterController))]
#if ENABLE_INPUT_SYSTEM
[RequireComponent(typeof(PlayerInput))]
#endif
public class Player2 : NetworkBehaviour
{
[Header(“Player”)]
[Tooltip(“Move speed of the character in m/s”)]
public float MoveSpeed = 2.0f;

[Tooltip(“Sprint speed of the character in m/s”)]
public float SprintSpeed = 5.335f;

[Tooltip(“How fast the character turns to face movement direction”)]
[Range(0.0f, 0.3f)]
public float RotationSmoothTime = 0.12f;

[Tooltip(“Acceleration and deceleration”)]
public float SpeedChangeRate = 10.0f;

public AudioClip LandingAudioClip;
public AudioClip[ ] FootstepAudioClips;
[Range(0, 1)] public float FootstepAudioVolume = 0.5f;

[Space(10)]
[Tooltip(“The height the player can jump”)]
public float JumpHeight = 1.2f;

[Tooltip(“The character uses its own gravity value. The engine default is -9.81f”)]
public float Gravity = -15.0f;

[Space(10)]
[Tooltip(“Time required to pass before being able to jump again. Set to 0f to instantly jump again”)]
public float JumpTimeout = 0.50f;

[Tooltip(“Time required to pass before entering the fall state. Useful for walking down stairs”)]
public float FallTimeout = 0.15f;

[Header(“Player Grounded”)]
[Tooltip(“If the character is grounded or not. Not part of the CharacterController built in grounded check”)]
public bool Grounded = true;

[Tooltip(“Useful for rough ground”)]
public float GroundedOffset = -0.14f;

[Tooltip(“The radius of the grounded check. Should match the radius of the CharacterController”)]
public float GroundedRadius = 0.28f;

[Tooltip(“What layers the character uses as ground”)]
public LayerMask GroundLayers;

[Header(“Cinemachine”)]
[Tooltip(“The follow target set in the Cinemachine Virtual Camera that the camera will follow”)]
public GameObject CinemachineCameraTarget;

[Tooltip(“How far in degrees can you move the camera up”)]
public float TopClamp = 70.0f;

[Tooltip(“How far in degrees can you move the camera down”)]
public float BottomClamp = -30.0f;

[Tooltip(“Additional degress to override the camera. Useful for fine tuning camera position when locked”)]
public float CameraAngleOverride = 0.0f;

[Tooltip(“For locking the camera position on all axis”)]
public bool LockCameraPosition = false;

// cinemachine
private float _cinemachineTargetYaw;
private float _cinemachineTargetPitch;

// player
private float _speed;
private float _animationBlend;
private float _targetRotation = 0.0f;
private float _rotationVelocity;
private float _verticalVelocity;
private float _terminalVelocity = 53.0f;

// timeout deltatime
private float _jumpTimeoutDelta;
private float _fallTimeoutDelta;

// animation IDs
private int _animIDSpeed;
private int _animIDGrounded;
private int _animIDJump;
private int _animIDFreeFall;
private int _animIDMotionSpeed;

#if ENABLE_INPUT_SYSTEM
private PlayerInput _playerInput;
#endif
private Animator _animator;
private CharacterController _controller;
private StarterAssetsInputs _input;
private GameObject _mainCamera;
private CinemachineVirtualCamera _cinemachineVirtualmachine;

private const float _threshold = 0.01f;

private bool _hasAnimator;

private bool IsCurrentDeviceMouse
{
get
{
#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
return _playerInput.currentControlScheme == “KeyboardMouse”;
#else
return false;
#endif
}
}

private void Awake()
{

// get a reference to our main camera
if (_mainCamera == null)
{
_mainCamera = GameObject.FindGameObjectWithTag(“AnotherCam”);
}
if (_cinemachineVirtualmachine == null)
{
_cinemachineVirtualmachine = FindObjectOfType();
}

}

private void Start()
{
_cinemachineTargetYaw = CinemachineCameraTarget.transform.rotation.eulerAngles.y;
_hasAnimator = TryGetComponent(out _animator);
_controller = GetComponent();
_input = GetComponent();

AssignAnimationIDs();

// reset our timeouts on start
_jumpTimeoutDelta = JumpTimeout;
_fallTimeoutDelta = FallTimeout;
}

public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
if (IsClient && IsOwner)
{
_playerInput = GetComponent();
_playerInput.enabled = true;
/* _cinemachineVirtualmachine.Follow = transform.Find(“PlayerCameraRoot”);*/

}

}

public void Update()
{
if (IsLocalPlayer)
{
_hasAnimator = TryGetComponent(out _animator);

JumpAndGravity();
GroundedCheck();
Move();

}

}

public void LateUpdate()
{
CameraRotation();
}

public void AssignAnimationIDs()
{
_animIDSpeed = Animator.StringToHash(“Speed”);
_animIDGrounded = Animator.StringToHash(“Grounded”);
_animIDJump = Animator.StringToHash(“Jump”);
_animIDFreeFall = Animator.StringToHash(“FreeFall”);
_animIDMotionSpeed = Animator.StringToHash(“MotionSpeed”);
}

public void GroundedCheck()
{
// set sphere position, with offset
Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset,
transform.position.z);
Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers,
QueryTriggerInteraction.Ignore);

// update animator if using character
if (_hasAnimator)
{
_animator.SetBool(_animIDGrounded, Grounded);
}
}

public void CameraRotation()
{
// if there is an input and camera position is not fixed
if (_input.look.sqrMagnitude >= _threshold && !LockCameraPosition)
{
//Don’t multiply mouse input by Time.deltaTime;
float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;

_cinemachineTargetYaw += _input.look.x * deltaTimeMultiplier;
_cinemachineTargetPitch += _input.look.y * deltaTimeMultiplier;
}

// clamp our rotations so our values are limited 360 degrees
_cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
_cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);

// Cinemachine will follow this target
CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride,
_cinemachineTargetYaw, 0.0f);
}

public void Move()
{
// set target speed based on move speed, sprint speed and if sprint is pressed
float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;

// a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon

// note: Vector2’s == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
// if there is no input, set the target speed to 0
if (_input.move == Vector2.zero) targetSpeed = 0.0f;

// a reference to the players current horizontal velocity
float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;

float speedOffset = 0.1f;
float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;

// accelerate or decelerate to target speed
if (currentHorizontalSpeed < targetSpeed - speedOffset ||
currentHorizontalSpeed > targetSpeed + speedOffset)
{
// creates curved result rather than a linear one giving a more organic speed change
// note T in Lerp is clamped, so we don’t need to clamp our speed
_speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
Time.deltaTime * SpeedChangeRate);

// round speed to 3 decimal places
_speed = Mathf.Round(_speed * 1000f) / 1000f;
}
else
{
_speed = targetSpeed;
}

_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
if (_animationBlend < 0.01f) _animationBlend = 0f;

// normalise input direction
Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;

// note: Vector2’s != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
// if there is a move input rotate player when the player is moving
if (_input.move != Vector2.zero)
{
_targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.y) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;
float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,RotationSmoothTime);

// rotate to face input direction relative to camera position
transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
}

Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;

// move the player
_controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) +
new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);

// update animator if using character
if (_hasAnimator)
{
_animator.SetFloat(_animIDSpeed, _animationBlend);
_animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
}
}

public void JumpAndGravity()
{
if (Grounded)
{
// reset the fall timeout timer
_fallTimeoutDelta = FallTimeout;

// update animator if using character
if (_hasAnimator)
{
_animator.SetBool(_animIDJump, false);
_animator.SetBool(_animIDFreeFall, false);
}

// stop our velocity dropping infinitely when grounded
if (_verticalVelocity < 0.0f)
{
_verticalVelocity = -2f;
}

// Jump
if (_input.jump && _jumpTimeoutDelta <= 0.0f)
{
// the square root of H * -2 * G = how much velocity needed to reach desired height
_verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);

// update animator if using character
if (_hasAnimator)
{
_animator.SetBool(_animIDJump, true);
}
}

// jump timeout
if (_jumpTimeoutDelta >= 0.0f)
{
_jumpTimeoutDelta -= Time.deltaTime;
}
}
else
{
// reset the jump timeout timer
_jumpTimeoutDelta = JumpTimeout;

// fall timeout
if (_fallTimeoutDelta >= 0.0f)
{
_fallTimeoutDelta -= Time.deltaTime;
}
else
{
// update animator if using character
if (_hasAnimator)
{
_animator.SetBool(_animIDFreeFall, true);
}
}

// if we are not grounded, do not jump
_input.jump = false;
}

// apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
if (_verticalVelocity < _terminalVelocity)
{
_verticalVelocity += Gravity * Time.deltaTime;
}
}

private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
{
if (lfAngle < -360f) lfAngle += 360f;
if (lfAngle > 360f) lfAngle -= 360f;
return Mathf.Clamp(lfAngle, lfMin, lfMax);
}

private void OnDrawGizmosSelected()
{
Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);

if (Grounded) Gizmos.color = transparentGreen;
else Gizmos.color = transparentRed;

// when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
Gizmos.DrawSphere(
new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z),
GroundedRadius);
}

private void OnFootstep(AnimationEvent animationEvent)
{
if (animationEvent.animatorClipInfo.weight > 0.5f)
{
if (FootstepAudioClips.Length > 0)
{
var index = Random.Range(0, FootstepAudioClips.Length);
AudioSource.PlayClipAtPoint(FootstepAudioClips[index], transform.TransformPoint(_controller.center), FootstepAudioVolume);
}
}
}

private void OnLand(AnimationEvent animationEvent)
{
if (animationEvent.animatorClipInfo.weight > 0.5f)
{
AudioSource.PlayClipAtPoint(LandingAudioClip, transform.TransformPoint(_controller.center), FootstepAudioVolume);
}
}
}
}

Hi @Khizarrahim ,

I copy/pasted your code in a test project and it’s working fine, no errors prompted and I am able to play at host side as at client side (just having the S key moving forward the player and not backward).

I used Unity version 2021.3.19f1, what’s yours?
Would you mind sharing the project so I can reproduce the problem?

By the way, for sharing code prefer using the insert code button instead of pasting it in text, it will help readability:
9050677--1250572--upload_2023-6-1_16-12-2.png

The error i am getting is on this line
_targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.y) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;
MissingReferenceException: The object of type ‘GameObject’ has been destroyed but you are still trying to access it. Your script should either check if it is null or you should not destroy the object. StarterAssets.ThirdPersonController.Move () (at Assets/StarterAssets/ThirdPersonController/Scripts/ThirdPersonController.cs:287) StarterAssets.ThirdPersonController.Update () (at Assets/StarterAssets/ThirdPersonController/Scripts/ThirdPersonController.cs:186)
This is when i use the client
The unity version is 2021.3.21f1

Here is the drive ink to import my project. It would help me alot if this gets fixed.
https://drive.google.com/drive/folders/1Q_d8eZV6XdSj-hGDnwNNG9cQSQyvewde?usp=sharing

Can you try changing the _mainCamera.transform.eulerAngles.y to Camera.main.transform.eulerAngles.y

This actually worked but now i am having problem with the cinemachine camera follow

 public override void OnNetworkSpawn()
        {
            base.OnNetworkSpawn();
            if (IsClient && IsOwner)
            {
                _playerInput = GetComponent<PlayerInput>();
                _playerInput.enabled = true;
                _cinemachineVirtualmachine.Follow = transform.Find("PlayerCameraRoot");

            }

        }

I wrote this cinemachine line it works for host but gives the following error in client
NullReferenceException: Object reference not set to an instance of an object StarterAssets.ThirdPersonController.OnNetworkSpawn () (at Assets/StarterAssets/ThirdPersonController/Scripts/ThirdPersonController.cs:169)

Any more suggestion
Thankyouu so much for the replies

Try changing the Awake method. I commented out the if-statement so it always assigns the variables on Awake

private void Awake()
{

// get a reference to our main camera
/*if (_mainCamera == null)
{*/
_mainCamera = GameObject.FindGameObjectWithTag("AnotherCam");
//}
/*if (_cinemachineVirtualmachine == null)
{*/
_cinemachineVirtualmachine = FindObjectOfType<CinemachineVirtualCamera>();
//}


}

Still error
NullReferenceException: Object reference not set to an instance of an object StarterAssets.ThirdPersonController.OnNetworkSpawn () (at Assets/StarterAssets/ThirdPersonController/Scripts/ThirdPersonController.cs:169)
_cinemachineVirtualmachine.Follow = transform.Find(“PlayerCameraRoot”);

I downloaded your project and I don’t have same errors than you (but it’s not working anyways).

I was wondering, why do you have two scripts for the player? There is a ThirdPersonController.cs and a Player2.cs scripts, both with same logic. You should only have one script for those and I would use if (!IsOwner) return; instead of if (IsClient && IsOwner) or if (IsLocalPlayer) :

public override void OnNetworkSpawn()
        {
            if (!IsOwner) return;
            GetComponent<PlayerInput>().enabled = true;
        }

//[...]

private void Update()
        {
            if (!IsOwner) { return; }
            _hasAnimator = TryGetComponent(out _animator);

            JumpAndGravity();
            GroundedCheck();
            Move();
        }

I wouldn’t use a second prefab for the other player (Nova) but change the visual at network spawn using an id or something like that.

I think the camera problem was due to the missing “AnotherCamera” tag. Using “MainCamera” will work for both players as they’ll take their own main camera.

For starting, I would recommend having only one script for all players, make it work, and then add your player selection logic, prefabs, etc.

Hope this helps!

I made the other script to just test out
Should i have seperate Main Cameras For Both? And Insert them inside the Prefabs itself?

In general, cameras should not be in the prefabs of your networked objects. You should isntead spawn and set them up only for the local player, at runtime.
the same applies for all other components that are related to the local player (UI, input systems, etc…) otherwise you’ll end up controlling multiple players with the same input, having problems with players not moving, UI resizing incorrectly, etc…

Have you already had a look at the examples in the official ClientDriven sample? (Download here)

It show how to use the third person controller asset in a multiplayer environment for movement and picking objects up.

As concerns the other issues discussed in this thread, they’re on the same line of the ones solved in this thread . tl;dr: ensure your player prefab has no input script enabled by default, and that you enable it only on the local Player when it spawns.
You also need to ensure that you’re using the ClientNetworkTransform script displayed at the bottom of this page to make the server accept the player movement coming from the clients.

Here’s more info about how to do so with the Third Person Controller that comes with the standards assets.

Does this help?

1 Like

Still havent figured out the cinemachine issue
I have just 1 Main Camera ( Contains Cinemachine Brain) and PlayerFollowCamera (That contains CinemachineVirtualCemra)
It just doesn’t assigns the camera to the client only works for host.
Or sometimes even uses the hosts camera
I am so confused