using UnityEngine; [RequireComponent(typeof(Rigidbody2D))] [RequireComponent(typeof(Animator))] public class PlayerController : MonoBehaviour,ISceneInitializable { // RedAnimController의 파라미터 이름과 정확히 일치해야 한다 private const string MoveXParam = "MoveX"; private const string MoveYParam = "MoveY"; private const string SpeedParam = "Speed"; private const string AttackParam = "Attack"; private const string AttackXParam = "AttackX"; private const string AttackYParam = "AttackY"; // 방향은 8개로 고정이다. 각도가 아니라 이 중 하나로 분류해서 들고 다닌다 private enum Direction { Right, UpRight, Up, UpLeft, Left, DownLeft, Down, DownRight } // 블렌드 트리에 박아둔 자식 좌표와 같은 값이어야 가중치가 그 클립에 온전히 실린다 private static readonly Vector2[] DirectionVectors = { new Vector2( 1f, 0f), // Right new Vector2( 0.7071f, 0.7071f), // UpRight new Vector2( 0f, 1f), // Up new Vector2(-0.7071f, 0.7071f), // UpLeft new Vector2(-1f, 0f), // Left new Vector2(-0.7071f, -0.7071f), // DownLeft new Vector2( 0f, -1f), // Down new Vector2( 0.7071f, -0.7071f) // DownRight }; // 오른손으로 휘두르므로 정면을 볼 땐 시계방향 45도 쪽을 벤다. // 대각선을 볼 땐 그대로 (Slash 시트가 대각선 4장뿐이라 정면 그림 자체가 없다) private static readonly Direction[] AttackDirections = { Direction.DownRight, // Right -> DownRight Direction.UpRight, // UpRight -> UpRight Direction.UpRight, // Up -> UpRight Direction.UpLeft, // UpLeft -> UpLeft Direction.UpLeft, // Left -> UpLeft Direction.DownLeft, // DownLeft -> DownLeft Direction.DownLeft, // Down -> DownLeft Direction.DownRight // DownRight -> DownRight }; [SerializeField] private float _moveSpeed = 4f; private Rigidbody2D _rigidbody; private Animator _animator; private Vector2 _moveInput; private Direction _facing = Direction.Down; private void Awake() { _rigidbody = GetComponent(); _animator = GetComponent(); } public void OnSceneLoaded() { // 씬이 다시 로드돼도 중복 구독되지 않도록 먼저 떼고 붙인다 InputManager.Instance.OnMove_Event -= this.OnMove; InputManager.Instance.OnMove_Event += this.OnMove; InputManager.Instance.OnAttack_Event -= this.OnAttack; InputManager.Instance.OnAttack_Event += this.OnAttack; } private void OnDestroy() { if (InputManager.Instance != null) { InputManager.Instance.OnMove_Event -= this.OnMove; InputManager.Instance.OnAttack_Event -= this.OnAttack; } } // 입력 콜백은 값을 받아두기만 한다. 실제 이동은 물리 주기에 맞춰 FixedUpdate에서 private void OnMove(Vector2 moveInput) { // 대각선 입력이 (1,1)로 들어오면 더 빨라지므로 길이를 1로 제한 _moveInput = Vector2.ClampMagnitude(moveInput, 1f); if (_moveInput.sqrMagnitude > 0.01f) { _facing = ToDirection(_moveInput); Vector2 facingVector = DirectionVectors[(int)_facing]; _animator.SetFloat(MoveXParam, facingVector.x); _animator.SetFloat(MoveYParam, facingVector.y); } // 입력이 0이면 방향을 갱신하지 않는다 -> 멈춰도 마지막으로 보던 쪽을 유지 _animator.SetFloat(SpeedParam, _moveInput.magnitude); } private void OnAttack() { // Slash 블렌드 트리는 MoveX/MoveY가 아니라 AttackX/AttackY를 읽는다. // 그래서 공격이 끝나도 바라보던 방향은 그대로 남는다 Vector2 attackVector = DirectionVectors[(int)AttackDirections[(int)_facing]]; _animator.SetFloat(AttackXParam, attackVector.x); _animator.SetFloat(AttackYParam, attackVector.y); _animator.SetTrigger(AttackParam); } private void FixedUpdate() { // Dynamic 바디지만 힘을 주지 않고 속도를 직접 대입하므로 관성 없이 딱딱 멈춘다 _rigidbody.linearVelocity = _moveInput * _moveSpeed; } /// /// 입력 각도를 45도씩 8개 구간으로 나눠 분류한다. /// 예를 들어 Up은 90도 한 점이 아니라 67.5 ~ 112.5도 구간 전체다. /// private static Direction ToDirection(Vector2 input) { float angle = Mathf.Atan2(input.y, input.x) * Mathf.Rad2Deg; int sector = Mathf.RoundToInt(angle / 45f); return (Direction)((sector + 8) % 8); // -180도는 -4 -> 4(Left)로 접힌다 } }