유니티 개발 효율을 높여주는 핵심 디자인 패턴
유니티에서 디자인 패턴을 사용하는 궁극적인 목적은 코드 간의
의존성을 낮추고(Low Coupling)
응집도를 높이는(High Cohesion) 것입니다.
실무에서 가장 자주 쓰이는 5가지 패턴을 정리합니다.
1. Component Pattern (컴포넌트 패턴)
객체의 기능을 독립적인 컴포넌트 단위로 분리하여 조립하는 방식입니다.
- 이점: 기능 확장과 재사용이 쉽고, 단위 테스트가 용이합니다.
- 실무: 한 클래스에 모든 기능을 넣지 않고 Mover, Health, Input 등으로 나눕니다.
// 기능을 분리하여 필요한 것만 부착
public class PlayerHealth : MonoBehaviour {
public int hp = 100;
public void TakeDamage(int amt) => hp -= amt;
}
2. Observer Pattern (옵저버 패턴)
이벤트를 통해 객체 간의 상태 변화를 전달하는 패턴입니다.
- 이점: UI와 게임 로직 사이의 결합도를 획기적으로 낮춥니다.
- 핵심: Publisher(알림)와 Subscriber(구독) 구조로 동작합니다.
// 이벤트 발생 및 구독 예시
public static event Action OnPlayerDead;
void Die() => OnPlayerDead?.Invoke();
// 구독 시에는 반드시 OnDisable에서 -= 로 해제해줘야 메모리 누수가 없습니다.
3. State Pattern (상태 패턴)
객체의 상태를 클래스로 캡슐화하여 상태에 따라 행동을 바꾸는 패턴입니다.
- 이점: 복잡한 if/switch 문을 제거하고 Enter/Exit 로직을 깔끔하게 관리합니다.
- 실무: 플레이어의 Idle, Run, Jump 등 FSM(유한 상태 머신) 구현에 필수입니다.
목적: 객체의 내부 상태가 바뀜에 따라 전체적인 행동(흐름)을 변경합니다.
특징: 상태 클래스들이 서로의 존재를 알거나, 상태 머신이 다음 상태로의 '전이(Transition)'를 관리합니다.
특징: 상태 클래스들이 서로의 존재를 알거나, 상태 머신이 다음 상태로의 '전이(Transition)'를 관리합니다.
// 상태 인터페이스
public interface IPlayerState {
void EnterState(PlayerController player);
void UpdateState(PlayerController player);
}
// 구체적인 상태: 대기(Idle)
public class IdleState : IPlayerState {
public void EnterState(PlayerController player) => Debug.Log("대기 상태 진입");
public void UpdateState(PlayerController player) {
if (Input.GetKeyDown(KeyCode.Space)) player.ChangeState(new JumpState());
}
}
// 구체적인 상태: 점프(Jump)
public class JumpState : IPlayerState {
public void EnterState(PlayerController player) => Debug.Log("점프!");
public void UpdateState(PlayerController player) { /* 착지 체크 후 Idle로 복귀 로직 */ }
}
// 상태 머신 (컨텍스트)
public class PlayerController : MonoBehaviour {
private IPlayerState currentState;
void Start() => ChangeState(new IdleState());
void Update() => currentState.UpdateState(this);
public void ChangeState(IPlayerState newState) {
currentState = newState;
currentState.EnterState(this);
}
}
4. Strategy Pattern (전략 패턴)
실시간으로 객체의 행동(알고리즘)을 갈아 끼울 수 있는 구조입니다.
- 이점: 상속의 한계를 극복하고 런타임에 기능을 유연하게 교체합니다.
- 비교: State는 '흐름'을 제어하고, Strategy는 '상세 기능'을 교체할 때 씁니다.
목적: 특정 기능을 수행하는 알고리즘(방법)을 실시간으로 교체합니다.
특징: 각 전략은 서로 독립적이며, 객체(Context)가 어떤 전략을 사용할지 '부품'처럼 선택합니다.
특징: 각 전략은 서로 독립적이며, 객체(Context)가 어떤 전략을 사용할지 '부품'처럼 선택합니다.
// 이동 전략 인터페이스
public interface IMoveStrategy {
void Move(Transform transform);
}
// 구체적인 전략 1: 걷기
public class WalkStrategy : IMoveStrategy {
public void Move(Transform transform) => transform.Translate(Vector3.forward * 2f * Time.deltaTime);
}
// 구체적인 전략 2: 달리기
public class RunStrategy : IMoveStrategy {
public void Move(Transform transform) => transform.Translate(Vector3.forward * 5f * Time.deltaTime);
}
// 전략을 사용하는 객체
public class PlayerMovement : MonoBehaviour {
private IMoveStrategy moveStrategy;
// 실시간으로 전략 교체 가능 (무기 교체 등에도 활용 가능)
public void SetMoveStrategy(IMoveStrategy strategy) => moveStrategy = strategy;
void Update() {
if (moveStrategy != null) moveStrategy.Move(transform);
}
}
State 패턴 vs Strategy 패턴
두 패턴은 구조적으로 매우 유사하지만, 사용 목적에 큰 차이가 있습니다.
핵심 차이점 요약
| 구분 | State 패턴 | Strategy 패턴 |
|---|---|---|
| 포커스 | 상태의 흐름과 전이 (무엇을 하는가) | 기능의 상세 구현 (어떻게 하는가) |
| 결정권 | 상태 스스로 다음 상태를 결정하기도 함 | 외부(사용자)가 필요한 전략을 주입함 |
5. Object Pooling (오브젝트 풀링)
자주 생성/파괴되는 객체를 미리 만들어 두고 재사용하는 기법입니다.
- 이점: 가비지 컬렉터(GC) 부하를 줄여 프레임 드랍을 방지합니다.
- 주의: 객체를 다시 꺼낼 때 상태 초기화를 반드시 수행해야 합니다.
// 유니티 내장 Pool 활용 예시
var pool = new ObjectPool<Bullet>(CreateFunc, OnGet, OnRelease);
# 마치며
유니티 디자인 패턴은 게임 개발에 있어 지속가능한 확장성을 목표로 하는게 핵심인것 같습니다. 이러한 패턴들을 기억해 두었다가 유용하게 적재적소에 적용을 하는 것이 아키텍처 설계의 첫 걸음이며 단순히 코드를 짜는 프로그래머가 아닌 시스템을 설계하는 프로그래머로서 필요한 역량인 것 같습니다.
'클라이언트 개발자 노트' 카테고리의 다른 글
| 객체 지향 프로그래밍 OOP(Object Oriented Programming) (3) | 2026.04.22 |
|---|