using UnityEngine; using Mirror; using UnityEngine.InputSystem; public class CharacterMovement : NetworkBehaviour { Animator animator; int isWalkingHash; int isRunningHash; PlayerInput input; Vector2 currentMovement; Vector2 combinedMovement; bool movementPressed; bool runPressed; // track last sent state to avoid network spam bool lastSentWalking; bool lastSentRunning; void Awake() { input = new PlayerInput(); input.CharacterControls.Movement.performed += ctx => { currentMovement = ctx.ReadValue(); movementPressed = currentMovement.x != 0 || currentMovement.y != 0; }; input.CharacterControls.Movement.canceled += ctx => { currentMovement = Vector2.zero; movementPressed = false; }; input.CharacterControls.Run.performed += ctx => runPressed = ctx.ReadValueAsButton(); input.CharacterControls.Run.canceled += ctx => runPressed = false; } void Start() { animator = GetComponent(); isWalkingHash = Animator.StringToHash("isWalking"); isRunningHash = Animator.StringToHash("isRunning"); } // Only the local player should drive input/movement void Update() { if (!isLocalPlayer) return; HandleMovement(); HandleRotation(); } void HandleRotation() { Vector3 currentPosition = transform.position; Vector3 newPosition = new Vector3(combinedMovement.x, 0, combinedMovement.y); // only rotate when there is movement to look at if (newPosition.sqrMagnitude > 0f) { Vector3 positionToLookAt = currentPosition + newPosition; transform.LookAt(positionToLookAt); } } void HandleMovement() { // read keyboard WASD (supplements / works alongside Input System action bindings) Vector2 keyboardMovement = Vector2.zero; var kb = Keyboard.current; if (kb != null) { if (kb.wKey.isPressed) keyboardMovement.y += 1f; if (kb.sKey.isPressed) keyboardMovement.y -= 1f; if (kb.aKey.isPressed) keyboardMovement.x -= 1f; if (kb.dKey.isPressed) keyboardMovement.x += 1f; } // combine input action movement and keyboard movement combinedMovement = currentMovement + keyboardMovement; // prevent faster diagonal movement combinedMovement = Vector2.ClampMagnitude(combinedMovement, 1f); movementPressed = combinedMovement.x != 0 || combinedMovement.y != 0; // detect Shift keys as an additional run input bool shiftPressed = kb != null && (kb.leftShiftKey.isPressed || kb.rightShiftKey.isPressed); bool runActive = runPressed || shiftPressed; bool isRunning = animator.GetBool(isRunningHash); bool isWalking = animator.GetBool(isWalkingHash); if (movementPressed && !isWalking) { animator.SetBool(isWalkingHash, true); isWalking = true; } if (!movementPressed && isWalking) { animator.SetBool(isWalkingHash, false); isWalking = false; } if ((movementPressed && runActive) && !isRunning) { animator.SetBool(isRunningHash, true); isRunning = true; } if ((!movementPressed || !runActive) && isRunning) { animator.SetBool(isRunningHash, false); isRunning = false; } // send animator state to server only when changed if (isWalking != lastSentWalking || isRunning != lastSentRunning) { lastSentWalking = isWalking; lastSentRunning = isRunning; CmdSendAnimationState(isWalking, isRunning); } // movement translation (local) float speed = isRunning ? 6f : 3f; Vector3 move = new Vector3(combinedMovement.x, 0, combinedMovement.y) * speed * Time.deltaTime; transform.position += move; } public override void OnStartLocalPlayer() { Debug.Log("Local player started: " + netId); input.CharacterControls.Enable(); } public override void OnStopLocalPlayer() { input.CharacterControls.Disable(); } // Called on the server when client issues the command [Command] void CmdSendAnimationState(bool walking, bool running) { // update server-side animator if present if (animator == null) animator = GetComponent(); if (animator != null) { animator.SetBool(isWalkingHash, walking); animator.SetBool(isRunningHash, running); } // broadcast to all clients RpcUpdateAnimationState(walking, running); } // Executed on all clients to apply animator changes [ClientRpc] void RpcUpdateAnimationState(bool walking, bool running) { if (animator == null) animator = GetComponent(); if (animator == null) return; animator.SetBool(isWalkingHash, walking); animator.SetBool(isRunningHash, running); } }