어제도 디자인 패턴에 대한것을 알아봤는데, 어제 전략패턴을 공부하고 상태패턴까지 공부를 할 생각이었기에 상태패턴에 관한 내용을 작성하겠다.
상태패턴은 말 그대로 객체의 상태에 따라 동작을 정의하는 패턴이다.
코드들을 보면서 코드의 흐름을 따라가보자.
public interface IPlayerState
{
void EnterState(PlayerController player);
void UpdateState(PlayerController player);
void ExitState(PlayerController player);
}
상태 인터페이스를 구현했다.
이 상태 인터페이스를 상속받는 idle, move, jump를 구현해보자
public class IdleState : IPlayerState
{
public void EnterState(PlayerController player)
{
//Idle상태에서 필요한 작업 ex) 휴식을시작하여 체력이 회복됩니다.
}
public void UpdateState(PlayerController player)
{
//이동키 입력시 상태전환
if (Input.GetKeyDown(KeyCode.W))
{
player.SwitchState(new MovingState());
}
//점프키 입력시 상태전환
else if (Input.GetKeyDown(KeyCode.Space))
{
player.SwitchState(new JumpingState());
}
}
public void ExitState(PlayerController player)
{
//체력회복 끄기
}
}
public class MovingState : IPlayerState
{
public void EnterState(PlayerController player)
{
}
public void UpdateState(PlayerController player)
{
if (Input.GetKeyUp(KeyCode.W)) // W를 때면 idle로 전환요청
{
player.SwitchState(new IdleState());
}
else if (Input.GetKeyDown(KeyCode.Space)) // 점프키 입력시 이동중 jumping으로 전환요청
{
player.SwitchState(new JumpingState());
}
}
public void ExitState(PlayerController player)
{
}
}
public class JumpingState : IPlayerState
{
public void EnterState(PlayerController player)
{
}
public void UpdateState(PlayerController player)
{
if (player.IsGrounded()) // 땅에 닿으면 Idle로 전환요청
{
player.SwitchState(new IdleState());
}
}
public void ExitState(PlayerController player)
{
}
}
이런식으로 구성한다음 이 상태들을 관리해주는 컨트롤러 스크립트를 만들어준다면
아주 깔끔하고 유지보수가 매~~~~~~~우 용이한 스크립트가 완성된다.
public class PlayerController : MonoBehaviour
{
private IPlayerState currentState;
private void Start()
{
currentState = new IdleState(); // 초기상태설정
currentState.EnterState(this);
}
private void Update()
{
currentState.UpdateState(this); // 현재 상태의 Update 메서드 호출
}
//상태전환 메서드 각 상태들에서 호출해서 사용하면 된다.
public void SwitchState(IPlayerState newState)
{
currentState.ExitState(this);
currentState = newState;
currentState.EnterState(this);
}
public bool IsGrounded()
{
// 아까 jumping상태의 IsGrounded()로 불값을 받아오는 로직
return Physics.Raycast(transform.position, Vector3.down, 0.1f);
}
}
이걸 왜 이렇게 복잡하게 만들어 ? 라는 생각이 든다면 이런저런 코드를 더 많이 작성하고 다시 보는것이 도움이 될것이다.
다음부터는 이런 상태패턴을 적용하여 플레이어의 상태를 구분하고 쉽게 수정할 수 있는 코딩을 해보도록 노력해봐야겠다.
--------------------------------------
업데이트
이렇게 상태패턴을 정리해보고 너무 마음에 들고 깔끔한 디자인 패턴이라 직접 적용해보았다.
내배캠에서 진행중인 개인 과제에서 적용했었는데, 디자인패턴들을 정리하던 중 나도 다시 한번 짚고 넘어가면 좋을 것 같아서 정리해보기로 했다
적용은 AI Navigation을 적용한 NPC에게 했으며, 해당 NPC는 Idle, Move, Talk 3가지 상태를 가지고 있다.
// 인터페이스 구현
public interface INPCState
{
void EnterState(NPCController npc);
void UpdateState(NPCController npc);
void ExitState(NPCController npc);
}
// Idle
public class IdleState : INPCState
{ // 정해진 위치로 이동하는 함수 호출
public void EnterState(NPCController npc)
{
npc.animator.SetFloat("Speed", 0f);
npc.StartMoveToTarget();
}
public void UpdateState(NPCController npc)
{
npc.SwitchState(new MoveState());
}
public void ExitState(NPCController npc) { }
}
public class MoveState : INPCState
{
public void EnterState(NPCController npc)
{
npc.animator.SetFloat("Speed", 1f);
}
public void UpdateState(NPCController npc)
{ // 만약 도착하면
if (npc.AtTarget())
{ // 다음 이동위치를 정한 뒤
npc.SetNextWaypoint();
// 대화상태로 전환요청
npc.SwitchState(new TalkState());
}
}
public void ExitState(NPCController npc) { }
}
public class TalkState : INPCState
{
private Coroutine talkCoroutine;
public void EnterState(NPCController npc)
{
npc.animator.SetFloat("Speed", 0f);
npc.animator.SetBool("IsTalk", true);
// talk 코루틴 호출 (TalkRoutine참고) 3초후 Idle상태로 전환요청
talkCoroutine = npc.StartCoroutine(TalkRoutine(npc));
}
public void UpdateState(NPCController npc)
{
}
public void ExitState(NPCController npc)
{
npc.animator.SetBool("IsTalk", false);
if (talkCoroutine != null)
{
npc.StopCoroutine(talkCoroutine);
talkCoroutine = null;
}
}
private IEnumerator TalkRoutine(NPCController npc)
{
yield return new WaitForSeconds(3f);
npc.SwitchState(new IdleState());
}
}
완벽하게 잘 사용한것인지는 사실 모르겠으나 여기서 NPC에게 원하는 행동이 몇가지 추가되거나 빼고싶어도 복잡하지 않고 간단하게 수정할 수 있을 것 같다.
NPCController는 아래
public class NPCController : MonoBehaviour
{
public Animator animator;
public Transform[] waypoints;
private int currentWaypointIndex;
private NavMeshAgent agent;
private INPCState currentState;
private void Start()
{
agent = GetComponent<NavMeshAgent>();
animator.speed = 1.0f;
currentState = new IdleState();
currentState.EnterState(this);
}
private void Update()
{
currentState.UpdateState(this);
}
public void SwitchState(INPCState newState)
{
currentState.ExitState(this);
currentState = newState;
currentState.EnterState(this);
}
public void StartMoveToTarget()
{
if (waypoints.Length > 0)
{
agent.SetDestination(waypoints[currentWaypointIndex].position);
}
}
public bool AtTarget()
{
return !agent.pathPending && agent.remainingDistance <= agent.stoppingDistance;
}
public void SetNextWaypoint()
{
currentWaypointIndex = (currentWaypointIndex + 1) % waypoints.Length;
}
}
'[UNITY],[C#] > TIL : UNITY' 카테고리의 다른 글
[C#]딕셔너리를 알아보자 (0) | 2024.10.31 |
---|---|
[디자인패턴] 이벤트 버스 패턴 (0) | 2024.10.30 |
[디자인패턴] 전략패턴에 관하여 (0) | 2024.10.28 |
움직이는 플랫폼 및 위에 있는 객체 함께 이동하기 (0) | 2024.10.27 |
[Unity] 제네릭 싱글톤 알아보기 (1) | 2024.10.27 |