2025. 3. 29. 10:12ㆍ유니티 unity/디자인패턴
유니티로 게임을 만들다 보면, 반드시 마주하게 되는 구조가 하나 있습니다. 바로 상태에 따라 행동이 달라지는 오브젝트입니다.
예를 들어 적 캐릭터는 대기하다가, 플레이어가 가까워지면 추적하고, 일정 거리 이내로 다가가면 공격하다가, 체력이 다 떨어지면 죽습니다. UI도 마찬가지예요. 메인 메뉴 → 설정 화면 → 인게임 → 일시정지 → 게임 오버까지 전환이 이루어지죠.
이 모든 흐름의 공통점은 상태에 따라 행동이 바뀐다는 점입니다.
처음엔 대부분 이렇게 코드를 짭니다:
void Update()
{
if (isDead)
PlayDeathAnimation();
else if (isAttacking)
Attack();
else if (isChasing)
ChasePlayer();
else
Idle();
}
괜찮아 보이죠. 그런데 상태가 늘어나고 조건이 꼬이기 시작하면?
바로 조건문 지옥이 펼쳐집니다.
이럴 때 필요한 게 바로 **상태 패턴(State Pattern)**입니다.
상태 패턴이란?
상태 패턴은 말 그대로, 객체의 상태를 클래스로 분리해서 관리하는 디자인 패턴입니다. 상태마다 클래스를 만들고, 그 안에 해당 상태의 로직을 넣습니다. 그리고 상태 전환은 명확하게 하나의 관리자가 처리합니다.
즉, 상태 = 객체화된 로직 단위입니다.
이렇게 하면 다음과 같은 이점이 생깁니다:
- ✅ 상태별 로직이 한 곳에 모여 있어서 가독성이 좋아지고
- ✅ 새로운 상태를 추가할 때 기존 코드 수정 없이 확장 가능하고
- ✅ 각 상태를 독립적으로 테스트하거나 유지보수하기도 쉬워집니다.
왜 유니티에서 특히 잘 맞을까?
유니티는 프레임 단위로 Update가 도는 구조고, 게임 오브젝트마다 동작이 나뉘기 때문에 상태 기반 로직이 매우 흔하게 등장합니다. 그런데 MonoBehaviour 기반으로 작성하다 보면 로직이 여기저기 흩어져서, 유지보수가 매우 힘들어집니다.
예를 들어, 적 캐릭터가 추적하다가 공격하거나 도망치는 등의 로직이 섞여 있으면 각 조건을 추적하기도 어렵고, 나중에 새로운 상태를 추가하는 것도 부담스럽습니다.
핵심 개념은 이렇습니다:
- StateMachine<T>: 상태를 관리하는 컨트롤러
- FsmState<T>: 상태의 추상 클래스
- IdleState, ChaseState, AttackState 등: 상태를 구현한 클래스들
- FsmMessage: 상태 간 전달할 수 있는 메시지 구조체
예제 코드로 살펴보기
1. 상태 메시지 구조
public struct FsmMessage
{
public int Type;
public FsmMessage(int type) { Type = type; }
}
2. 상태 추상 클래스
public abstract class FsmState<T> where T : Enum
{
public T StateType { get; private set; }
protected FsmState(T stateType)
{
StateType = stateType;
}
public virtual void OnEnter(T fromState, FsmMessage msg) { }
public virtual void OnUpdate(float deltaTime) { }
public virtual void OnExit(T toState) { }
public virtual void OnMessage(FsmMessage msg) { }
}
3. 상태 머신 클래스
public class StateMachine<T> where T : Enum
{
private Dictionary<T, FsmState<T>> _states = new();
private FsmState<T> _currentState;
public void AddState(FsmState<T> state)
{
_states[state.StateType] = state;
}
public void ChangeState(T newState, FsmMessage msg = default)
{
_currentState?.OnExit(newState);
_currentState = _states[newState];
_currentState.OnEnter(newState, msg);
}
public void Update(float deltaTime)
{
_currentState?.OnUpdate(deltaTime);
}
}
상태 구현 예시: Idle, Chase
public enum EnemyState
{
Idle,
Chase
}
public class IdleState : FsmState<EnemyState>
{
private Enemy enemy;
public IdleState(Enemy enemy) : base(EnemyState.Idle)
{
this.enemy = enemy;
}
public override void OnEnter(EnemyState from, FsmMessage msg)
{
Debug.Log("Idle 상태 진입");
}
public override void OnUpdate(float deltaTime)
{
if (enemy.IsPlayerInRange())
enemy.FSM.ChangeState(EnemyState.Chase);
}
}
public class ChaseState : FsmState<EnemyState>
{
private Enemy enemy;
public ChaseState(Enemy enemy) : base(EnemyState.Chase)
{
this.enemy = enemy;
}
public override void OnEnter(EnemyState from, FsmMessage msg)
{
Debug.Log("Chase 상태 진입");
}
public override void OnUpdate(float deltaTime)
{
enemy.MoveTowardsPlayer();
if (!enemy.IsPlayerInRange())
enemy.FSM.ChangeState(EnemyState.Idle);
}
}
이렇게 바뀝니다
Before
- 상태가 늘어날수록 if, else if, switch가 늘어남
- 코드가 중복되고 수정이 어려움
- 테스트 불가능
After (상태 패턴 적용)
- 각 상태가 독립적으로 존재
- 상태 간 전환 명확
- 테스트 가능 / 확장 용이
상태 패턴은 단순히 “코드를 예쁘게 만들자”는 것이 아닙니다.
“유지보수 가능한 구조를 만들자”,
**“상태가 많아질수록 더 유리한 구조로 가자”**라는 철학에 가깝습니다.
이 패턴은 특히 유니티처럼 상태 기반 동작이 잦은 환경에 정말 잘 어울립니다.
직접 상태 클래스를 나눠보면 생각보다 훨씬 깔끔하고,
“아 이래서 다들 상태 패턴을 쓰는구나” 하는 감이 오게 됩니다.
'유니티 unity > 디자인패턴' 카테고리의 다른 글
유니티에서 이벤트 버스(Event Bus) 패턴을 활용한 유연한 시스템 만들기 (0) | 2025.03.29 |
---|---|
유니티에서 옵저버 패턴(Observer Pattern)을 제대로 활용해보세요 (0) | 2025.03.29 |
유니티에서 의존성 주입(DI) 패턴을 적용하는 이유와 방법 (0) | 2025.03.29 |
Unity에서 C# 리플렉션 제대로 활용하기! Type, FieldInfo, Activator 완벽 정리 (0) | 2025.02.19 |
Unity에서 KISS 원칙(Keep It Simple, Stupid) 적용하기 (0) | 2025.02.16 |