using UnityEngine; using UnityEngine.InputSystem; using Unity.Netcode; using Colosseum.Network; using Colosseum.Skills; namespace Colosseum.Player { /// /// 3인칭 플레이어 이동 (네트워크 동기화) /// [RequireComponent(typeof(CharacterController))] public class PlayerMovement : NetworkBehaviour { [Header("Movement Settings")] [SerializeField] private float moveSpeed = 5f; [SerializeField] private float rotationSpeed = 10f; [SerializeField] private float gravity = -9.81f; [Header("Jump Settings")] [SerializeField] private float jumpForce = 5f; [Header("References")] [SerializeField] private SkillController skillController; [SerializeField] private Animator animator; private CharacterController characterController; private Vector3 velocity; private Vector2 moveInput; private InputSystem_Actions inputActions; private bool isJumping; private bool wasGrounded; /// /// 현재 이동 속도 (애니메이션용) /// public float CurrentMoveSpeed => moveInput.magnitude * moveSpeed; /// /// 현재 지면 접촉 상태 /// public bool IsGrounded => characterController != null ? characterController.isGrounded : false; /// /// 점프 중 상태 /// public bool IsJumping => isJumping; public override void OnNetworkSpawn() { // 로컬 플레이어가 아니면 입력 비활성화 if (!IsOwner) { enabled = false; return; } characterController = GetComponent(); // SkillController 참조 if (skillController == null) { skillController = GetComponent(); } // Animator 참조 if (animator == null) { animator = GetComponentInChildren(); } // 스폰 포인트에서 위치 설정 SetSpawnPosition(); // Input Actions 초기화 InitializeInputActions(); // 카메라 설정 SetupCamera(); } /// /// 입력 액션 초기화 /// private void InitializeInputActions() { inputActions = new InputSystem_Actions(); inputActions.Player.Enable(); // Move 액션 콜백 등록 inputActions.Player.Move.performed += OnMovePerformed; inputActions.Player.Move.canceled += OnMoveCanceled; // Jump 액션 콜백 등록 inputActions.Player.Jump.performed += OnJumpPerformed; } /// /// 입력 액션 해제 /// private void CleanupInputActions() { if (inputActions != null) { inputActions.Player.Move.performed -= OnMovePerformed; inputActions.Player.Move.canceled -= OnMoveCanceled; inputActions.Player.Jump.performed -= OnJumpPerformed; inputActions.Player.Disable(); } } private void OnDisable() { // 컴포넌트 비활성화 시 입력 해제 CleanupInputActions(); // 입력 초기화 moveInput = Vector2.zero; } private void OnEnable() { // 컴포넌트 재활성화 시 입력 다시 등록 if (IsOwner && inputActions != null) { inputActions.Player.Enable(); inputActions.Player.Move.performed += OnMovePerformed; inputActions.Player.Move.canceled += OnMoveCanceled; inputActions.Player.Jump.performed += OnJumpPerformed; } } /// /// 스폰 위치 설정 /// private void SetSpawnPosition() { Transform spawnPoint = PlayerSpawnPoint.GetRandomSpawnPoint(); if (spawnPoint != null) { // CharacterController 비활성화 후 위치 설정 (충돌 문제 방지) characterController.enabled = false; transform.position = spawnPoint.position; transform.rotation = spawnPoint.rotation; characterController.enabled = true; } } /// /// 네트워크 정리 /// public override void OnNetworkDespawn() { CleanupInputActions(); } private void OnMovePerformed(InputAction.CallbackContext context) { moveInput = context.ReadValue(); } private void OnMoveCanceled(InputAction.CallbackContext context) { moveInput = Vector2.zero; } private void OnJumpPerformed(InputAction.CallbackContext context) { if (!isJumping && characterController.isGrounded) { Jump(); } } private void SetupCamera() { var cameraController = GetComponent(); if (cameraController == null) { cameraController = gameObject.AddComponent(); } cameraController.Initialize(transform, inputActions); } /// /// 카메라 재설정 (씬 로드 후 호출) /// public void RefreshCamera() { SetupCamera(); } private void Update() { if (!IsOwner) return; ApplyGravity(); Move(); } private void ApplyGravity() { if (wasGrounded && velocity.y < 0) { velocity.y = -2f; } else { velocity.y += gravity * Time.deltaTime; } } private void Move() { if (characterController == null) return; // 스킬 애니메이션 재생 중에는 이동 불가 (루트 모션은 OnAnimatorMove에서 처리) if (skillController != null && skillController.IsPlayingAnimation) { // 루트 모션을 사용하지 않는 경우 중력만 적용 if (!skillController.UsesRootMotion) { characterController.Move(velocity * Time.deltaTime); } return; } // 이동 방향 계산 (카메라 기준) Vector3 moveDirection = new Vector3(moveInput.x, 0f, moveInput.y); moveDirection = TransformDirectionByCamera(moveDirection); moveDirection.Normalize(); // 이동 적용 Vector3 moveVector = moveDirection * moveSpeed * Time.deltaTime; characterController.Move(moveVector + velocity * Time.deltaTime); // 회전 (이동 중일 때만) if (moveDirection != Vector3.zero) { Quaternion targetRotation = Quaternion.LookRotation(moveDirection); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime); } // 착지 체크 (Move 후에 isGrounded가 업데이트됨) if (!wasGrounded && characterController.isGrounded && isJumping) { OnJumpEnd(); } // 다음 프레임을 위해 현재 상태 저장 wasGrounded = characterController.isGrounded; } private void Jump() { isJumping = true; velocity.y = jumpForce; // 애니메이션 컨트롤러에 점프 알림 var animController = GetComponent(); if (animController != null) { animController.PlayJump(); } } /// /// 점프 중 상태가 끝나면 IsJumping = false; /// public void OnJumpEnd() { isJumping = false; } private Vector3 TransformDirectionByCamera(Vector3 direction) { if (Camera.main == null) return direction; Transform cameraTransform = Camera.main.transform; Vector3 cameraForward = cameraTransform.forward; Vector3 cameraRight = cameraTransform.right; // Y축 제거 cameraForward.y = 0f; cameraRight.y = 0f; cameraForward.Normalize(); cameraRight.Normalize(); return cameraRight * direction.x + cameraForward * direction.z; } /// /// 루트 모션 처리. 스킬 애니메이션 중에 애니메이션의 이동/회전 데이터를 적용합니다. /// private void OnAnimatorMove() { if (!IsOwner) return; if (animator == null || characterController == null) return; if (skillController == null || !skillController.IsPlayingAnimation) return; if (!skillController.UsesRootMotion) return; // 루트 모션 이동 적용 Vector3 deltaPosition = animator.deltaPosition; // Y축 무시 설정 시 중력 유지 if (skillController.IgnoreRootMotionY) { deltaPosition.y = 0f; characterController.Move(deltaPosition + velocity * Time.deltaTime); } else { characterController.Move(deltaPosition); } // 루트 모션 회전 적용 if (animator.deltaRotation != Quaternion.identity) { transform.rotation *= animator.deltaRotation; } // 착지 체크 if (!wasGrounded && characterController.isGrounded && isJumping) { OnJumpEnd(); } wasGrounded = characterController.isGrounded; } } }