728x90
반응형
상태 패턴(State Pattern)은 객체의 상태에 따라 동작을 달리하는 디자인 패턴이다.
이 패턴을 사용하면 상태에 따른 조건문을 많이 사용하는 코드보다 훨씬 더 깔끔하고 유지보수하기 쉬운 코드를 작성할 수 있다.
유니티(Unity)에서 상태 패턴을 사용하는 예제를 통해 이를 알아보자.
1. 상태 인터페이스 정의
우선 먼저 모든 상태의 클래스가 구현 해야 할 매서드를 정의한다.
public interface ICharacterState
{
void EnterState(Character character);
void UpdateState(Character character);
void ExitState(Character character);
}
2. State Machine 구현
이제 모든 상태들을 관리해줄 State Machine을 만든다.
public class StateMachine
{
public ICharacterState currentState { get; private set; }
public IdleState IdleState;
public WalkingState WalkingState;
public StateMachine(Character character)
{
IdleState = new IdleState(character);
WalkingState = new WalkingState(character);
JumpingState = new JumpingState(character);
}
public void Initialize(ICharacterState initState)
{
currentState = initState;
currentState.EnterState();
}
public void ChangeState(ICharacterState newState)
{
currentState.ExitState(); // 기존의 상태를 끝낸다
currentState = newState; // 새로운 상태로 변경
currentState.EnterState(); // 새로운 상태를 시작
}
public void Update()
{
currentState.UpdateState();
}
}
3. State 구현
이제 캐릭터의 상태를 구현 해준다.
우선 Idle 과 Move 만 만들었다.
public class IdleState : ICharacterState
{
private readonly Character character;
public IdleState(Character character)
{
this.character = character;
}
public void EnterState()
{
}
public void UpdateState()
{
var isMove = false;
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.D))
{
isMove = true;
}
if (isMove)
{
character.StateMachine.ChangeState(character.StateMachine.WalkingState);
}
}
public void ExitState()
{
}
}
public class WalkingState : ICharacterState
{
private readonly Character character;
public WalkingState(Character character)
{
this.character = character;
}
public void EnterState()
{
}
public void UpdateState()
{
var moveDirection = character.GetInputDirection();
if (moveDirection == Vector3.zero)
{
character.StateMachine.ChangeState(character.StateMachine.IdleState);
}
else
{
character.Move(moveDirection);
}
}
public void ExitState()
{
}
}
4. Character 구현
이동 하는 함수를 만들어주고 각 상태들을 만든다.
public class Character : MonoBehaviour
{
public StateMachine StateMachine { get; private set; }
public float moveSpeed = 5f;
void Start()
{
StateMachine = new StateMachine(this);
StateMachine.Initialize(StateMachine.IdleState);
}
void Update()
{
StateMachine.Update();
}
public void Move(Vector3 direction)
{
transform.Translate(direction * moveSpeed * Time.deltaTime);
}
}
이렇게 하면 상태를 바꿔가며 움직인다.
5. 새로운 상태 추가
기존의 상태에서 새로운 상태인 점프를 넣어보자
우선 점프 State를 구현해준다.
public class JumpingState : ICharacterState
{
private readonly Character character;
public JumpingState(Character character)
{
this.character = character;
}
public void EnterState()
{
character.Jump();
}
public void UpdateState()
{
// 점프 중에도 Input을 받아 움직일 수 있다.
var moveDirection = character.GetInputDirection();
// 땅에 닿아야지 다른 상태로 넘어갈 수 있다.
if (character.IsGrounded)
{
if (moveDirection == Vector3.zero)
{
character.StateMachine.ChangeState(character.StateMachine.IdleState);
}
else
{
character.StateMachine.ChangeState(character.StateMachine.WalkingState);
}
}
else if (moveDirection != Vector3.zero)
{
character.Move(moveDirection);
}
}
public void ExitState()
{
}
}
그 다음 캐릭터에게 점프 관련 함수를 만들고 리지드바디 컴포넌트를 사용해 점프 할 것이므로 컴포넌트를 추가 해준다.
public class Character : MonoBehaviour
{
public StateMachine StateMachine { get; private set; }
public float moveSpeed = 5f;
public float jumpForce = 7f;
public bool IsGrounded { get; private set; }
public TMP_Text StateText;
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
StateMachine = new StateMachine(this);
StateMachine.Initialize(StateMachine.IdleState);
}
void Update()
{
StateMachine.Update();
}
public void Move(Vector3 direction)
{
transform.Translate(direction * moveSpeed * Time.deltaTime);
}
public void Jump()
{
if (IsGrounded)
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
IsGrounded = false;
}
}
void OnCollisionEnter(Collision collision)
{
// 바닥에 닿으면 점프 가능 상태로 변경
if (collision.contacts[0].normal == Vector3.up)
{
IsGrounded = true;
}
}
public Vector3 GetInputDirection()
{
Vector3 moveDirection = Vector3.zero;
if (Input.GetKey(KeyCode.W))
{
moveDirection += Vector3.forward;
}
if (Input.GetKey(KeyCode.S))
{
moveDirection += Vector3.back;
}
if (Input.GetKey(KeyCode.A))
{
moveDirection += Vector3.left;
}
if (Input.GetKey(KeyCode.D))
{
moveDirection += Vector3.right;
}
return moveDirection;
}
}
Idle과 Move에서도 점프를 하게끔 수정 해준다.
public class IdleState : ICharacterState
{
private readonly Character character;
public IdleState(Character character)
{
this.character = character;
}
public void EnterState()
{
}
public void UpdateState()
{
var isMove = false;
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.D))
{
isMove = true;
}
if (Input.GetKeyDown(KeyCode.Space))
{
character.StateMachine.ChangeState(character.StateMachine.JumpingState);
}
else if (isMove)
{
character.StateMachine.ChangeState(character.StateMachine.WalkingState);
}
}
public void ExitState()
{
}
}
public class WalkingState : ICharacterState
{
private readonly Character character;
public WalkingState(Character character)
{
this.character = character;
}
public void EnterState()
{
}
public void UpdateState()
{
var moveDirection = character.GetInputDirection();
if (Input.GetKeyDown(KeyCode.Space))
{
character.StateMachine.ChangeState(character.StateMachine.JumpingState);
}
else if (moveDirection == Vector3.zero)
{
character.StateMachine.ChangeState(character.StateMachine.IdleState);
}
else
{
character.Move(moveDirection);
}
}
public void ExitState()
{
}
}
결과
728x90
반응형
'Unity' 카테고리의 다른 글
Coroutine 사용 방법 (0) | 2024.06.18 |
---|---|
UniTask 사용 방법 (0) | 2024.06.17 |
씬 변경을 해도 파괴되지 않는 오브젝트 만들기 (0) | 2024.06.10 |
Recorder 동영상 찍기 (0) | 2024.06.09 |
Object Pooling 유니티 공식 지원 오브젝트 풀링 (0) | 2024.06.07 |