[Unity] 디자인 패턴 - 상태 패턴(FSM)

Gee·2025년 4월 7일
  • 플레이어의 이동, 점프 등을 관리하기에 적합한 기능
  • 상태 패턴을 사용하면 유연한 상태 전환이 가능하고, 새로운 액션의 추가도 용이해진다.

상태 패턴의 장점

  • 책임 분리: 상태마다의 책임 분리가 명확해진다.
  • 전환 조건 명확성: 전환되는 조건을 명확하게 설정할 수 있다.
  • 유지보수 용이: 유지보수가 쉽다.
  • 복잡한 상태 처리: 복잡한 상태 변화도 쉽게 처리가 가능하다. (e.g. 점프 도중 벽 잡기, 벽에서 벽 점프)

관련 스크립트 정리

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 플레이어의 모든 시스템을 초기화하고, 매 프레임 상태 머신을 실행
// Awake에서 상태 머신을 만들고 Idle 상태로 시작 → Update와 FixedUpdate로 상태를 계속 갱신
// 플레이어의 모든 하위 시스템 (입력, 애니메이션, 이동 등)을 관리하는 중심 클래스 (입력뿐 아니라 플레이어에게 필요한 여러 컴포넌트들을 관리)
public class Player : MonoBehaviour
{
    [field: SerializeField] public PlayerSO Data { get; private set; }  // 플레이어 데이터 (속도, 점프력 등 설정)
    
    [field:Header("Animations")]
    [field:SerializeField] public PlayerAnimationData AnimationData { get; private set; }   // 애니메이션 데이터

    public Animator Animator { get; private set; }              // 애니메이션 제어
    public PlayerController Input { get; private set; }         // 입력 처리
    public CharacterController Controller { get; private set; } // 물리 이동
    public ForceReceiver ForceReceiver { get; private set; }
    
    private PlayerStateMachine stateMachine;                    // FSM의 핵심 컨트롤러

    private void Awake()
    {
        AnimationData.Initialize();                         // 애니메이션 파라미터 해시값 초기화
        Animator = GetComponentInChildren<Animator>();      // 애니메이터 연결
        Input = GetComponent<PlayerController>();           // 입력 시스템 연결
        Controller = GetComponent<CharacterController>();   // 캐릭터 컨트롤러(물리) 연결
        ForceReceiver = GetComponent<ForceReceiver>();
        
        stateMachine = new PlayerStateMachine(this);        // 상태 머신 생성
    }

    void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;   // 마우스 잠금
        stateMachine.ChangeState(stateMachine.IdleState);   // 초기 상태를 Idle로 설정
    }
    
    private void Update()
    {
        stateMachine.HandleInput();     // 입력 처리 (키보드, 마우스 입력 감지)
        stateMachine.Update();          // 논리 업데이트 (상태 전환 등)
    }

    private void FixedUpdate()
    {
        stateMachine.PhysicsUpdate();   // 물리 업데이트 (이동, 회전 등)
    }
}

Player.cs

  • FSM 초기화 및 업데이트 관리
  • PlayerStateMachine을 초기화하고 기본 상태를 IdleState로 시작
  • 애니메이션 파라미터를 초기화하고 Animator, Input, Controller, ForceReceiver 연결
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 생성 시 모든 상태를 준비하고, ChangeState로 현재 상태를 전환
public class PlayerStateMachine : StateMachine
{
    public Player Player { get; }       // 플레이어 객체 참조

    public Vector2 MovementInput { get; set; }  // 이동 입력값 (WASD 등)
    public float MovementSpeed { get; private set; }    // 기본 이동 속도
    public float RotationDamping { get; private set; }  
    public float MovementSpeedModifier { get; set; } = 1f;  // 속도 배율 (Walk, Run 등)

    public float JumpForce { get; set; }

    public Transform MainCameraTransform { get; set; }      // 카메라 방향 참조
    
    public PlayerIdleState IdleState { get; private set; }  // 대기 상태
    public PlayerWalkState WalkState { get; private set;}   // 걷기 상태
    public PlayerRunState RunState { get; private set;}     // 달리기 상태
    public PlayerJumpState JumpState { get; private set;}   // 점프 상태
    public PlayerFallState FallState { get; private set;}   // 추락 상태

    public PlayerStateMachine(Player player)
    {
        this.Player = player;

        MainCameraTransform = Camera.main.transform;        // 카메라 연결
        
        IdleState = new PlayerIdleState(this);
        WalkState = new PlayerWalkState(this);
        RunState = new PlayerRunState(this);
        
        JumpState = new PlayerJumpState(this);
        FallState = new PlayerFallState(this);

        MovementSpeed = player.Data.GroundData.BaseSpeed;   // 기본 속도 설정
        RotationDamping = player.Data.GroundData.BaseRotationDamping;
    }
}

PlayerStateMachine.cs

  • 상태 객체 생성 및 전환 관리
  • 상태 인스턴스(IdleState, WalkState 등)를 생성하고, 공용 데이터(MovementInput, MovementSpeed 등)를 저장
  • ChangeState: 현재 상태 Exit -> 새 상태 Enter.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 플레이어의 키보드, 마우스 입력을 받아내고 FSM이나 이동 로직이 이걸 참조해서 행동함
public class PlayerController : MonoBehaviour
{
    public PlayerInputs playerInputs { get; private set; }  // 인풋 시스템에서 전체 입력을 다루는 객체
    public PlayerInputs.PlayerActions playerActions { get; private set; }   // Player 액션 맵 (이동, 점프 등 액션)

    private void Awake()
    {
        playerInputs = new PlayerInputs();      // InputActions 생성
        playerActions = playerInputs.Player;    // 플레이어 인풋 안의 액션 맵, 그 옆 액션을 표시
    }

    private void OnEnable()     // 켜질 때
    {
        playerInputs.Enable();  // 이 오브젝트가 활성화되면 인풋 활성화
    }

    private void OnDisable()    // 꺼질 때
    {
        playerInputs.Disable(); // 비활성화되면 인풋 비활성화
    }
}

PlayerController.cs

  • Input System 생성 및 활성화
  • OnEnable, OnEnable로 방어 처리
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

// 모든 상태가 공통으로 사용하는 기능(입력 읽기, 이동, 회전)을 제공
// 상태가 시작되면 입력을 감지하고, 매 프레임 이동 로직을 처리
public class PlayerBaseState : IState
{
    protected PlayerStateMachine stateMachine;      // 상태 머신 참조 (상태 전환, 데이터 공유용)
    protected readonly PlayerGroundData groundData; // 지상 상태에서 사용할 데이터 (속도, 회전 감쇠 등)
    
    // 상태 머신 초기화
    public PlayerBaseState(PlayerStateMachine stateMachine)
    {
        this.stateMachine = stateMachine;   // 상태 머신 연결
        groundData = stateMachine.Player.Data.GroundData;   // PlayerSO에서 지상 데이터 가져오기
    }
    
    /// <summary>
    /// 상태에 진입할 때 호출 (상태 초기화)
    /// </summary>
    public virtual void Enter()
    {
        AddInputActionsCallbacks();     // 입력 이벤트(이동, 달리기 등) 감지하도록 등록
    }
    
    /// <summary>
    /// 상태에서 나갈 때 호출 (정리 작업)
    /// </summary>
    public virtual void Exit()
    {
        RemoveInputActionsCallbacks();  // 입력 이벤트 해제
    }
    
    /// <summary>
    /// 입력 이벤트를 등록하는 메서드
    /// </summary>
    protected virtual void AddInputActionsCallbacks()
    {
        PlayerController input = stateMachine.Player.Input;             // 플레이어의 입력 컨트롤러 가져오기
        input.playerActions.Movement.canceled += OnMovementCanceled;    // 이동 입력이 끝날 때 호출
        input.playerActions.Run.started += OnRunStarted;                // 달리기 시작 시 호출
        input.playerActions.Jump.started += OnJumpStarted;
    }
    
    /// <summary>
    /// 입력 이벤트를 해제하는 메서드 (메모리 누수 방지)
    /// </summary>
    protected virtual void RemoveInputActionsCallbacks()
    {
        PlayerController input = stateMachine.Player.Input;
        input.playerActions.Movement.canceled -= OnMovementCanceled;    // 이동 이벤트 해제
        input.playerActions.Run.started -= OnRunStarted;                // 달리기 이벤트 해제
        input.playerActions.Jump.started -= OnJumpStarted;
    }
    
    /// <summary>
    /// 매 프레임 입력 처리
    /// </summary>
    public virtual void HandleInput()
    {
        ReadMovementInput();    // WASD 등 입력 값을 읽어서 stateMachine에 저장
    }
    
    /// <summary>
    /// 물리 업데이트
    /// </summary>
    public virtual void PhysicsUpdate()
    {
        
    }
    
    /// <summary>
    /// 논리 업데이트
    /// </summary>
    public virtual void Update()
    {
        Move();     // 이동 및 회전 실행
    }
    
    /// <summary>
    /// 이동 입력이 취소될 때 호출 (e.g. WASD 키 뗐을 때)
    /// </summary>
    /// <param name="context"></param>
    protected virtual void OnMovementCanceled(InputAction.CallbackContext context)
    {

    }
    
    /// <summary>
    /// 달리기 입력이 시작될 때 호출 (e.g. Shift 키 눌렀을 때)
    /// </summary>
    /// <param name="context"></param>
    protected virtual void OnRunStarted(InputAction.CallbackContext context)
    {

    }

    protected virtual void OnJumpStarted(InputAction.CallbackContext context)
    {
        
    }
    
    // 애니메이션 시작
    protected void StartAnimation(int animationHash)
    {
        stateMachine.Player.Animator.SetBool(animationHash, true);  // Animator의 bool 파라미터 true로 설정
    }

    // 애니메이션 종료
    protected void StopAnimation(int animationHash)
    {
        stateMachine.Player.Animator.SetBool(animationHash, false);
    }
    
    // WASD 입력을 읽어서 상태 머신에 저장
    private void ReadMovementInput()
    {
        stateMachine.MovementInput = stateMachine.Player.Input.playerActions.Movement.ReadValue<Vector2>();
    }
    
    // 이동과 회전을 총괄하는 메서드
    private void Move()
    {
        Vector3 movementDirection = GetMovementDirection(); // 카메라 기준 이동 방향 계산
        
        Move(movementDirection);    // 플레이어를 해당 방향으로 이동
        
        Rotate(movementDirection);  // 이동 방향으로 플레이어 회전
    }
    
    // 카메라를 기준으로 WASD 입력을 3D 방향으로 변환
    private Vector3 GetMovementDirection()
    {
        Vector3 forward = stateMachine.MainCameraTransform.forward; // 카메라의 앞 방향
        Vector3 right = stateMachine.MainCameraTransform.right;     // 카메라의 우측 방향

        forward.y = 0;  // 수평 이동만 하도록 y를 0으로
        right.y = 0;

        forward.Normalize();    // 방향 벡터 크기를 1로 맞춤
        right.Normalize();
        
        // 입력값을 카메라 방향에 맞춰 합성
        return forward * stateMachine.MovementInput.y + right * stateMachine.MovementInput.x; 
    }
    
    // 플레이어를 실제로 이동시킴 (CharacterController 사용)
    private void Move(Vector3 direction)
    {
        float movementSpeed = GetMovementSpeed();   // 현재 속도 계산
        
        stateMachine.Player.Controller.Move(((direction * movementSpeed) + stateMachine.Player.ForceReceiver.Movement ) * Time.deltaTime); 
    }
    
    // 이동 속도 계산
    private float GetMovementSpeed()
    {
        float moveSpeed = stateMachine.MovementSpeed * stateMachine.MovementSpeedModifier;
        return moveSpeed;
    }
    
    // 플레이어를 이동 방향으로 부드럽게 회전
    private void Rotate(Vector3 direction)
    {
        if(direction != Vector3.zero)   // 방향이 있을 때만 회전
        {
            Transform playerTransform = stateMachine.Player.transform;
            Quaternion targetRotation = Quaternion.LookRotation(direction); // 목표 회전각 계산
            playerTransform.rotation = Quaternion.Slerp(playerTransform.rotation, targetRotation, stateMachine.RotationDamping * Time.deltaTime);   // 현재 회전에서 목표 회전으로 보간
        }
    }
}

PlayerBaseState.cs

  • 모든 상태의 공통 로직 제공
  • 자식 상태에서 재정의(override)로 동작 확장
  • HandleInput: WASD 입력 읽기 → MovementInput 저장
  • Update: Move() 호출 (방향 계산 → 이동 → 회전)
  • Move: CharacterController.Move로 이동 적용
  • GetMovementDirection: 카메라 기준 이동 방향 계산
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerGroundState : PlayerBaseState
{
    public PlayerGroundState(PlayerStateMachine stateMachine) : base(stateMachine)
    {
        
    }
    
    // 바닥에 있을 때 호출
    public override void Enter()
    {
        base.Enter();
        StartAnimation(stateMachine.Player.AnimationData.GroundParameterHash);
    }

    // 바닥에서 떨어질 때 호출
    public override void Exit()
    {
        base.Exit();
        StopAnimation(stateMachine.Player.AnimationData.GroundParameterHash);
    }

    // 논리 업데이트
    public override void Update()
    {
        base.Update();
    }

    // 물리 업데이트
    public override void PhysicsUpdate()
    {
        base.PhysicsUpdate();
        
        // 플레이어가 바닥에 있지 않고 아래로 떨어지는 속도가 중력보다 크면 (공중에 떠 있는 상태)
        if (!stateMachine.Player.Controller.isGrounded 
            && stateMachine.Player.Controller.velocity.y < Physics.gravity.y * Time.fixedDeltaTime)
        {
            stateMachine.ChangeState(stateMachine.FallState);   // 추락 상태로 전환
            return;
        }
    }
    
    // 이동 입력이 취소될 때
    protected override void OnMovementCanceled(InputAction.CallbackContext context)
    {
        if(stateMachine.MovementInput == Vector2.zero)  // 입력이 완전히 없으면
        {
            return;
        }

        stateMachine.ChangeState(stateMachine.IdleState);   // 대기 상태로 전환

        base.OnMovementCanceled(context);
    }
    
    // 점프 입력이 시작될 때
    protected override void OnJumpStarted(InputAction.CallbackContext context)
    {
        base.OnJumpStarted(context);
        stateMachine.ChangeState(stateMachine.JumpState);   // 점프 상태로 전환
    }
}

PlayerGroundState.cs

  • 지상 상태의 공통 로직
  • Enter: 지상 애니메이션 시작
  • PhysicsUpdate: 땅에서 떨어지면 FallState로 전환
  • OnJumpStarted: Space 키 입력 시 JumpState로 전환
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerAirState : PlayerBaseState
{
    public PlayerAirState(PlayerStateMachine playerStateMachine) : base(playerStateMachine)
    {
    }

    // 공중 상태에 진입할 때 호출
    public override void Enter()
    {
        base.Enter();
        StartAnimation(stateMachine.Player.AnimationData.AirParameterHash); // Air 시작
    }

    // 공중 상태에서 나갈 때 호출
    public override void Exit()
    {
        base.Exit();
        StopAnimation(stateMachine.Player.AnimationData.AirParameterHash);  // Air 종료
    }
}

PlayerAirState.cs

  • 공중 상태의 공통 로직
  • Enter: 공중 애니메이션 시작
  • Exit: 공중 애니메이션 정지

PlayerJumpState.cs

  • 점프 동작 처리
  • Enter: ForceReceiver.Jump 호출 → 점프 애니메이션
  • PhysicsUpdate: Y 속도 0 이하 → FallState
  • Update: 수평 이동 유지

PlayerFallState.cs

  • 추락 동작 처리
  • Enter: 추락 애니메이션
  • PhysicsUpdate: 착지(isGrounded) → IdleState
  • Update: 공중 수평 이동
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ForceReceiver : MonoBehaviour
{
    [SerializeField] private CharacterController _controller;   // 캐릭터 컨트롤러 참조
    
    private float _verticalVelocity;    // 세로 방향의 속도 (중력, 점프 등 적용)
    
    public Vector3 Movement => Vector3.up * _verticalVelocity;  // 현재 이동 벡터 (y축만 사용)
    
    void Start()
    {
        _controller = GetComponent<CharacterController>();  // 컨트롤러 초기화
    }
    
    void Update()
    {
        if (_controller.isGrounded) // 비닥에 닿아 있다면
        {
            _verticalVelocity = Physics.gravity.y * Time.deltaTime; // 바닥에 붙어 있도록 유지
        }
        else  // 바닥에 닿아 있지 않다면
        {
            _verticalVelocity += Physics.gravity.y * Time.deltaTime;    // 중력의 영향을 받도록 더해줌
        }
    }

    // 점프 요청 시 호출 (점프 힘을 세로 속도에 추가)
    public void Jump(float jumpForce)
    {
        _verticalVelocity += jumpForce; // 순간적으로 위로 속도 증가
    }
}

ForceReceiver.cs

  • 중력 및 점프 물리 처리
  • Update:
    • 착지 시: 속도 약간 음수 유지
    • 공중 시: 중력 적용 (_verticalVelocity += gravity)
  • Jump: 초기 속도 부여

FSM 흐름 요약

  1. 초기화:
    • Player.Awake: 상태 머신 생성 → 컴포넌트 연결
    • Player.Start: IdleState로 시작, 커서 잠금
  2. 입력 감지:
    • PlayerController: WASD, Space, Shift 입력 → PlayerBaseState.HandleInput
  3. 상태 처리:
    • Update: 이동/회전(Move), 애니메이션 관리
    • PhysicsUpdate: 물리(점프, 추락, 착지) 체크 → 상태 전환
  4. 상태 전환:
    • Idle ↔ Walk: WASD 입력/해제
    • Ground → Jump: Space 입력
    • Jump → Fall: Y 속도 <= 0
    • Fall → Idle: 착지
  • 상태 클래스 (PlayerIdleState, PlayerWalkState)에서 조건에 따라 stateMachine.ChangeState(...) 호출
  • FSM은 상태별로 독립적인 로직을 가지며, PlayerBaseState에서 공통 이동/회전 로직을 제공해 코드 중복을 줄임
  • PlayerBaseState에서 공통 로직을 제공하기 때문에 새 상태를 추가할 때 기존 코드를 활용 가능(코드 재사용성)

스크립트 기능 정리

  1. Player.cs

    • 플레이어 객체의 중심 클래스. 모든 하위 시스템(입력, 애니메이션, 이동, 물리)을 초기화하고 FSM을 실행.
    • 상태 머신과 컴포넌트(Animator, Input, Controller, ForceReceiver)를 연결.
    • 매 프레임 FSM의 입력 처리, 논리 업데이트, 물리 업데이트를 호출.
    • 주요 역할: FSM 초기화 및 업데이트 관리.
  2. PlayerAnimationData.cs

    • 애니메이션 관련 데이터를 저장하고 초기화.
    • Animator의 파라미터 해시값(예: JumpParameterHash, GroundParameterHash)을 생성해 상태별 애니메이션 제어를 지원.
    • 주요 역할: 애니메이션 파라미터 관리.
  3. PlayerController.cs

    • Unity Input System을 통해 플레이어 입력(WASD, Space, Shift 등)을 감지하고 제공.
    • PlayerInputs 객체와 PlayerActions 액션 맵을 생성 및 활성화/비활성화.
    • 주요 역할: 키보드/마우스 입력 처리.
  4. ForceReceiver.cs

    • CharacterController에 적용할 수직 물리(중력, 점프 힘)를 관리.
    • 매 프레임 중력을 적용하고, 점프 요청 시 초기 속도를 부여.
    • 주요 역할: 중력 및 점프 물리 계산.
  5. PlayerAirState.cs

    • 공중 상태(Jump, Fall)의 공통 로직 제공.
    • 공중 애니메이션 시작/종료를 처리.
    • 주요 역할: 공중 상태 부모 클래스.
  6. PlayerBaseState.cs

    • 모든 상태의 기본 로직(입력 읽기, 이동, 회전)을 정의.
    • WASD 입력을 읽어 방향 계산, CharacterController.Move로 이동, 카메라 기준 회전 구현.
    • 입력 이벤트(이동 취소, 달리기, 점프)를 등록/해제.
    • 주요 역할: 상태 공통 로직 제공.
  7. PlayerFallState.cs

    • 추락 상태 관리.
    • 추락 애니메이션 재생, 착지 시 IdleState로 전환.
    • 공중에서 수평 이동 유지.
    • 주요 역할: 추락 동작 처리.
  8. PlayerGroundState.cs

    • 지상 상태(Idle, Walk, Run)의 공통 로직 제공.
    • 지상 애니메이션 처리, 땅에서 떨어지면 FallState, Space 입력 시 JumpState로 전환.
    • 주요 역할: 지상 상태 부모 클래스.
  9. PlayerIdleState.cs

    • 대기 상태 관리.
    • 대기 애니메이션 재생, WASD 입력 시 WalkState로 전환.
    • 주요 역할: 정지 상태 처리.
  10. PlayerJumpState.cs

    • 점프 상태 관리.
    • ForceReceiver.Jump로 점프 시작, 점프 애니메이션 재생, Y 속도 0 이하 시 FallState로 전환.
    • 공중 수평 이동 유지.
    • 주요 역할: 점프 동작 처리.
  11. PlayerRunState.cs

    • 달리기 상태 관리.
      달리기 애니메이션 재생, 속도 수정자(MovementSpeedModifier) 증가(예: 2f).
    • Shift 뗄 시 WalkState, 입력 없으면 IdleState로 전환.
    • 주요 역할: 빠른 이동 처리.
  12. PlayerStateMachine.cs

    • 모든 상태 객체를 생성하고 상태 전환(ChangeState) 관리.
    • 공용 데이터(MovementInput, MovementSpeed, JumpForce 등) 저장 및 제공.
    • 주요 역할: FSM의 핵심 컨트롤러.
  13. PlayerWalkState.cs

    • 걷기 상태 관리.
    • 걷기 애니메이션 재생, WASD 입력으로 이동.
    • Shift 입력 시 RunState, 입력 없으면 IdleState로 전환.
    • 주요 역할: 기본 이동 처리.
  14. StateMachine.cs

    • FSM의 기본 틀 제공.
    • ChangeState 메서드로 현재 상태를 전환(Exit → Enter).
    • 주요 역할: 상태 전환 로직 정의.
  15. PlayerSO.cs (ScriptableObject)

    • 플레이어 설정 데이터(속도, 점프력, 회전 감쇠 등)를 저장.
    • GroundData와 AirData로 지상/공중 설정 분리.
    • 주요 역할: 설정 데이터 관리.

상세 흐름

  1. 게임 시작
  • Player.Awake:
    • PlayerAnimationData.Initialize(): 애니메이션 해시값 생성.
    • Animator, Input, Controller, ForceReceiver를 GetComponent로 연결.
    • stateMachine = new PlayerStateMachine(this): 상태 머신 생성.
  • PlayerStateMachine 생성자:
    • IdleState, WalkState, RunState, JumpState, FallState 인스턴스 생성.
    • MovementSpeed, RotationDamping, JumpForce를 PlayerSO에서 초기화.
    • MainCameraTransform을 Camera.main으로 설정.
  • Player.Start:
    • Cursor.lockState = CursorLockMode.Locked: 마우스 커서 잠금.
    • stateMachine.ChangeState(stateMachine.IdleState):
      • StateMachine.ChangeState: currentState = IdleState → IdleState.Enter.
  1. 초기 상태: IdleState
  • PlayerIdleState.Enter:
    • PlayerBaseState.Enter: AddInputActionsCallbacks() 호출.
      • PlayerController.playerActions에 이벤트 등록 (Movement.canceled, Run.started, Jump.started).
    • StartAnimation(IdleParameterHash): 대기 애니메이션 시작.
  • Player.Update:
    • stateMachine.HandleInput() → PlayerBaseState.HandleInput:
      • ReadMovementInput: WASD 입력 읽기 → MovementInput 갱신.
    • stateMachine.Update() → PlayerBaseState.Update:
      • Move(): MovementInput이 0이므로 이동/회전 없음.
  1. 입력 감지 및 상태 전환
  • WASD 입력:
    • HandleInput: MovementInput 갱신 (예: (1, 0)).
    • PlayerIdleState.Update: 입력 감지 → stateMachine.ChangeState(WalkState).
      • IdleState.Exit: RemoveInputActionsCallbacks() → StopAnimation.
      • WalkState.Enter: AddInputActionsCallbacks() → StartAnimation(WalkParameterHash).
  • Shift 입력 (달리기):
    • PlayerBaseState.OnRunStarted: stateMachine.ChangeState(RunState).
      • WalkState.Exit → RunState.Enter:
        • MovementSpeedModifier = 2f (달리기 속도 증가).
        • StartAnimation(RunParameterHash).
  • Space 입력 (점프):
    • PlayerGroundState.OnJumpStarted: stateMachine.ChangeState(JumpState).
      • WalkState.Exit → JumpState.Enter:
        • ForceReceiver.Jump(JumpForce): _verticalVelocity 증가.
        • StartAnimation(JumpParameterHash).
  1. 이동 및 물리 처리
  • PlayerWalkState.Update:
    • PlayerBaseState.Move:
      • GetMovementDirection: 카메라 기준 방향 계산.
      • Move(direction): Controller.Move로 수평 이동.
      • Rotate(direction): 플레이어 회전.
  • Player.FixedUpdate:
    • stateMachine.PhysicsUpdate() → 상태별 물리 체크.
  • ForceReceiver.Update:
    • 착지 시: _verticalVelocity를 약간 음수로 유지.
    • 공중 시: _verticalVelocity += Physics.gravity.y * Time.deltaTime.
  1. 점프와 추락
  • PlayerJumpState:
    • Enter: 점프 힘 적용 → 애니메이션.
    • PhysicsUpdate: Controller.velocity.y <= 0 → ChangeState(FallState).
      • JumpState.Exit → FallState.Enter:
        • StopAnimation(JumpParameterHash) → StartAnimation(FallParameterHash).
  • PlayerFallState:
    • Update: 공중 수평 이동 유지 (Move 호출).
    • PhysicsUpdate: Controller.isGrounded → ChangeState(IdleState).
      • FallState.Exit → IdleState.Enter.
  1. 상태 전환 조건
  • Idle → Walk: WASD 입력.
  • Walk → Run: Shift 입력.
  • Walk/Run → Idle: OnMovementCanceled에서 입력 0 확인.
  • Ground → Jump: OnJumpStarted.
  • Jump → Fall: Y 속도 <= 0.
  • Fall → Idle: 착지.
  • Ground → Fall: 땅에서 떨어짐 (!isGrounded && velocity.y < gravity).
  1. 전체 루프
  • Player.Update: 입력 → 논리 업데이트 → 상태 전환.
  • Player.FixedUpdate: 물리 체크 → 상태 전환.
  • ForceReceiver.Update: 중력/점프 적용 → Controller.Move.
profile
...

0개의 댓글