From 89a2bfa424c0c7e2fddc8e035cb55d0635bf7cfe Mon Sep 17 00:00:00 2001 From: Mingu Kim Date: Fri, 15 Aug 2025 20:05:29 +0900 Subject: [PATCH] =?UTF-8?q?FSM=20=EA=B5=AC=ED=98=84=EC=A4=91...=20?= =?UTF-8?q?=EC=8B=A4=ED=96=89=ED=95=98=EB=A9=B4=20=EC=9C=A0=EB=8B=88?= =?UTF-8?q?=ED=8B=B0=EA=B0=80=20=EC=A2=85=EB=A3=8C=EB=90=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scripts/Player/PlayerMove.cs | 164 +++++------------------ Assets/Scripts/Player/StateMachine.cs | 179 ++++++++++++++++++++++++-- 2 files changed, 202 insertions(+), 141 deletions(-) diff --git a/Assets/Scripts/Player/PlayerMove.cs b/Assets/Scripts/Player/PlayerMove.cs index 4679adea..2a9bbb11 100644 --- a/Assets/Scripts/Player/PlayerMove.cs +++ b/Assets/Scripts/Player/PlayerMove.cs @@ -1,171 +1,71 @@ using System; using UnityEngine; -public class IdleState : IState -{ - public void Enter() - { - - } - - public void Update() - { - - } - - public void Exit() - { - - } - - public IState CheckTransition() - { - // 우선 순위 - // Jump를 우선 - // Move는 Jump보다 낮은 우선 - - throw new NotImplementedException(); - } -} - - public class PlayerMove : MonoBehaviour { - private static readonly int IsJumping = Animator.StringToHash("isJumping"); - private static readonly int IsMoveing = Animator.StringToHash("isMoveing"); + // 컴포넌트 프로퍼티 + public Rigidbody2D RigidBody { get; private set; } + public SpriteRenderer SpriteRenderer { get; private set; } + public Animator Animator { get; private set; } + + // 다른 클래스에서 접근할 수 있도록 private static 변수와 public 프로퍼티를 분리 + private static int _isJumping; + private static int _isMoving; + + public static int IsJumping => _isJumping; + public static int IsMoving => _isMoving; public float maxSpeed; public float jumpPower; - Rigidbody2D _rigidBody; - SpriteRenderer _spriteRenderer; - - // TODO : 점프, 공격, 돌진, 슈퍼 대시 - Animator _animator; // 변수 [SerializeField] public int hp = 5; + private StateMachine _stateMachine; + // 사용하는 컴포넌트들 초기화 void Awake() { - _rigidBody = GetComponent(); - _spriteRenderer = GetComponent(); - _animator = GetComponent(); + // 중복 변수 제거하고 public 프로퍼티에 직접 할당 + RigidBody = GetComponent(); + SpriteRenderer = GetComponent(); + Animator = GetComponent(); + + // 2. Animator가 초기화된 후에 해시 값 할당 + _isJumping = Animator.StringToHash("isJumping"); + _isMoving = Animator.StringToHash("isMoving"); + + _stateMachine = new StateMachine(new IdleState(this)); } private void Update() { - // 점프 ( 연속 점프 방지 ) - // TODO : C# 최적화 방법 중 1가지 > GC.Alloc 최소화 - // string >> class변수 (힙 메모리 저장), 가비지 컬렉터에 의해 제거됨 - // struct >> 스택 메모리로 사용하지 않으면 바로 제거됨 - - if (Input.GetButtonDown("Jump") && _animator.GetBool(IsJumping) == false) - { - _rigidBody.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse); - _animator.SetBool(IsJumping, true); - } - - // 키 입력 땔때 캐릭터 멈춤 - if (Input.GetButtonUp("Horizontal")) - { - _rigidBody.linearVelocity = new Vector2(_rigidBody.linearVelocity.normalized.x * 0.0000001f, _rigidBody.linearVelocity.y); - } - - // 캐릭터(Sprite)이동 방향 바라보도록 스프라이트 플립 - if (Input.GetButton("Horizontal")) - { - _spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") >= 1; - } - - // 애니메이션 설정 - if (MathF.Abs(_rigidBody.linearVelocity.x) < 0.5) - { - _animator.SetBool(IsMoveing, false); - } - else - { - _animator.SetBool(IsMoveing, true); - } + _stateMachine.Update(); } void FixedUpdate() { - // 캐릭터 움직임 컨트롤 - float h = Input.GetAxisRaw("Horizontal"); - _rigidBody.AddForce(Vector2.right * h, ForceMode2D.Impulse); - - // 유니티6 부터 Velocity에서 LinearVelocity로 변경 - if (_rigidBody.linearVelocityX > maxSpeed) // 오른쪽 최대 속도 - { - _rigidBody.linearVelocity = new Vector2(maxSpeed, _rigidBody.linearVelocity.y); - } - else if (_rigidBody.linearVelocityX < maxSpeed * (-1)) - { - _rigidBody.linearVelocity = new Vector2(maxSpeed * (-1), _rigidBody.linearVelocity.y); - } - - // 플랫폼을 밟고 있을 때 - if (_rigidBody.linearVelocity.y < 0) - { - Debug.DrawRay(_rigidBody.position, Vector3.down, Color.green); - - RaycastHit2D rayHit = Physics2D.Raycast(_rigidBody.position, Vector3.down, 1, LayerMask.GetMask("Platform")); - - if(rayHit.collider != null) - { - // // 바닥에 닿아있을 때 애니메이션 변경 - if(rayHit.distance < 0.7f) - { - _animator.SetBool(IsJumping, false); - } - } - } + _stateMachine.FixedUpdate(); } void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.CompareTag("Enemy")) { - // 적과 충돌했을 때 - OnDameged(collision.transform.position); - Debug.Log("플레이어가 맞았습니다."); + // FSM에 피격 로직 추가 + // _stateMachine.SetTransition(new DamagedState(this, collision.transform.position)); } } - void OnDameged(Vector2 targetPosition) + void OnDamaged(Vector2 targetPosition) { - // 적과 충돌 후 무적 ( 11번 레이어 PlayerDamaged로 변경 ) - // 물리 설정에서 PlayerDamaged 레이어와 Enemy 레이어가 충돌하지 않도록 설정함 - gameObject.layer = 11; - - // 플레이어 HP 감소 - hp--; - - Debug.Log(hp); - - // 무적 표시 - _spriteRenderer.color = new Color(1,1,1,0.5f); // 플레이어 반투명 - - // 적과의 충돌(피격 시) 뒤로 밀려남 - int direction = transform.position.x - targetPosition.x > 0 ? 1 : -1; // 적과의 상대 위치에 따라 방향 결정 - if (direction == -1) - { - _rigidBody.AddForce(Vector2.left * 5, ForceMode2D.Impulse); - } - else - { - _rigidBody.AddForce(Vector2.right * 5, ForceMode2D.Impulse); - } - - Invoke("OffDamaeged", 1); + // OnDamaged 로직은 DamagedState의 Enter()로 이동 + // 기존 Invoke("OffDamaged", 1)는 DamagedState 내부에서 처리 } - void OffDamaeged() + void OffDamaged() { - gameObject.layer = 10; - _spriteRenderer.color = new Color(1,1,1,1); // 플레이어 반투명 - + // OffDamaged 로직은 DamagedState의 Exit()로 이동 } } \ No newline at end of file diff --git a/Assets/Scripts/Player/StateMachine.cs b/Assets/Scripts/Player/StateMachine.cs index 645851df..3792256d 100644 --- a/Assets/Scripts/Player/StateMachine.cs +++ b/Assets/Scripts/Player/StateMachine.cs @@ -2,16 +2,10 @@ using UnityEngine; public class StateMachine { - private Animator animator; - private IState _state; - private PlayerMove _playerBase; - // private TextMeshProUGUI _textState; + private IState _state; - public StateMachine(IState state, PlayerMove playerMove) + public StateMachine(IState state) { - // 초기 상태 객체 생성 - _playerBase = playerMove;; - _state = state; _state.Enter(); } @@ -19,9 +13,9 @@ public class StateMachine public void Update() { _state.Update(); - var newState = _state.CheckTransition(); + // 상태가 변경되었는지 확인 (참조가 다르면) if (_state != newState) { _state.Exit(); @@ -29,6 +23,11 @@ public class StateMachine } } + public void FixedUpdate() + { + _state.FixedUpdate(); + } + public void SetTransition(IState state) { // 다음음 상태로 전환 @@ -43,8 +42,170 @@ public interface IState void Update(); + void FixedUpdate(); // FixedUpdate 추가 + void Exit(); // 트리거 조건일 경우 다음 상태로 전환 IState CheckTransition(); } + +public class IdleState : IState +{ + private PlayerMove _player; // PlayerMove 객체에 대한 참조 + private MoveState _moveState; + private JumpState _jumpState; + + public IdleState(PlayerMove player) + { + _player = player; + _moveState = new MoveState(_player); + _jumpState = new JumpState(_player); + } + + public void Enter() + { + _player.Animator.SetBool(PlayerMove.IsMoving, false); + } + + public void Update() + { + // 상태별 로직은 여기서는 필요 없음 + } + + public void FixedUpdate() + { + // 물리적인 로직이 없으므로 비워둠 + } + + public void Exit() + { + // Idle 상태에서 벗어날 때의 로직 + } + + public IState CheckTransition() + { + // 우선 순위: Jump -> Move + if (Input.GetButtonDown("Jump")) + { + return _jumpState; + } + if (Input.GetAxisRaw("Horizontal") != 0) + { + return _moveState; + } + return this; + } +} + +public class MoveState : IState +{ + private PlayerMove _player; + private IdleState _idleState; + private JumpState _jumpState; + + public MoveState(PlayerMove player) + { + _player = player; + _idleState = new IdleState(_player); + _jumpState = new JumpState(_player); + } + + public void Enter() + { + _player.Animator.SetBool(PlayerMove.IsMoving, true); + } + + public void Update() + { + // 상태별 로직 + _player.SpriteRenderer.flipX = Input.GetAxisRaw("Horizontal") < 0; + } + + public void FixedUpdate() + { + float h = Input.GetAxisRaw("Horizontal"); + _player.RigidBody.AddForce(Vector2.right * h, ForceMode2D.Impulse); + + if (_player.RigidBody.linearVelocity.x > _player.maxSpeed) + { + _player.RigidBody.linearVelocity = new Vector2(_player.maxSpeed, _player.RigidBody.linearVelocity.y); + } + else if (_player.RigidBody.linearVelocity.x < -_player.maxSpeed) + { + _player.RigidBody.linearVelocity = new Vector2(-_player.maxSpeed, _player.RigidBody.linearVelocity.y); + } + } + + public void Exit() + { + // 이동 상태에서 벗어날 때 필요한 로직 + _player.Animator.SetBool(PlayerMove.IsMoving, false); + } + + public IState CheckTransition() + { + if (Input.GetButtonDown("Jump")) + { + return _jumpState; + } + if (Mathf.Abs(Input.GetAxisRaw("Horizontal")) < 0.1f) // 키 입력이 없을 때 + { + return _idleState; + } + return this; + } +} + +public class JumpState : IState +{ + private PlayerMove _player; + private IdleState _idleState; + + public JumpState(PlayerMove player) + { + _player = player; + _idleState = new IdleState(_player); + } + + public void Enter() + { + _player.RigidBody.AddForce(Vector2.up * _player.jumpPower, ForceMode2D.Impulse); + _player.Animator.SetBool(PlayerMove.IsJumping, true); + } + + public void Update() + { + // 점프 중 스프라이트 방향 변경 + if (Input.GetButton("Horizontal")) + { + _player.SpriteRenderer.flipX = Input.GetAxisRaw("Horizontal") < 0; + } + } + + public void FixedUpdate() + { + // FixedUpdate에 물리 로직이 없으므로 비워둠 + } + + public void Exit() + { + _player.Animator.SetBool(PlayerMove.IsJumping, false); + } + + public IState CheckTransition() + { + // 바닥에 닿았는지 확인 -> IdleState로 전환 + if (_player.RigidBody.linearVelocity.y < 0) + { + Debug.DrawRay(_player.RigidBody.position, Vector3.down, Color.green); + RaycastHit2D rayHit = Physics2D.Raycast(_player.RigidBody.position, Vector3.down, 1, LayerMask.GetMask("Platform")); + + if (rayHit.collider != null && rayHit.distance < 0.7f) + { + return _idleState; + } + } + return this; + } +} \ No newline at end of file