게임에 자주 쓰이는 패턴
소프트웨어 개발에서 자주 발생하는 문제 상황을 객체지향 원리에 따라 재사용 가능하게 정리해 둔 설계 아이디어 템플릿이다.
수많은 디자인 패턴들이 존재하는데 일부는 확장성에 초점을 맞추기도 하고, 일부는 최적화 및 재활용성에 초점을 맞추기도 한다.
게임의 경우, 퍼포먼스가 중요하기 때문에 최적화와 직결되는 디자인 패턴들의 선호도가 높다.
싱글톤 패턴 ( Singleton ) - 생성 ( Creational )
애플리케이션 전역에서 딱 하나의 인스턴스만 존재하도록 보장하고 , 어디서든 쉽게 접근하게 하는 패턴
사용하면 좋은 상황
글로벌 상태 / 서비스 : 설정 , 오디오 매니저 , 저장소 , 씬 전역 이벤트 , 게임 매니저
씬을 넘어 수명 유지가 필요한 매니저
구조
private 생성자 + static 인스턴스 접근자
( Unity ) MonoBehaviour 기반이면 중복 방지 + DontDestroyOnLoad
▼C# 예시 코드
public sealed class GameManager {
private static readonly Lazy<GameManager> _inst = new(() => new GameManager());
public static GameManager Instance => _inst.Value;
private GameManager() { }
public int Score { get; private set; }
public void AddScore(int s) => Score += s;
▼Unity 예시 코드 ( MonoBehaviour )
public class AudioManager : MonoBehaviour {
public static AudioManager Instance { get; private set; }
void Awake() {
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
주의할 점
전역 의존성 증가 → 테스트 / 확장 어려워진다 ( 의존성 주입 고려 )
멀티씬 중복 생성 주의 ( 프리팹 / 씬 구성 점검 )
가능하면 상태 보관보다는 서비스 역할 위주
상태 패턴 ( State ) - 행위 ( Behavioral )
객체의 내부 상태에 따라 행동을 바꾸고 , 상태 클래스를 교체하는 방식으로 분기 / 조건문 폭발을 피하는 패턴
사용하면 좋은 상황
캐릭터 / 적 AI ( Idle / Patrol / Chase / Attack / Dead 등 )
UI 화면 흐름 , 퀘스트 단계 , 네트워크 연결 상태 등 명확한 단계 전이
구조
IState ( Enter / Update / Exit ) + Context ( 현재 상태 보유 , 전이 관리 )
▼예시 코드 ( C# / Unity )
public interface IState { void Enter(); void Update(); void Exit(); }
public class PlayerContext : MonoBehaviour {
IState _state;
public void ChangeState(IState next) { _state?.Exit(); _state = next; _state.Enter(); }
void Update() => _state?.Update();
}
// 예: Idle / Run
public class IdleState : IState {
readonly PlayerContext _ctx;
public IdleState(PlayerContext ctx) { _ctx = ctx; }
public void Enter() { /* 애니 Idle */ }
public void Update() { /* 입력 있으면 _ctx.ChangeState(new RunState(_ctx)); */ }
public void Exit() { }
}
주의할 점
상태 수가 많다면 전이 ( transition ) 표를 적어두면 유지보수가 쉬워진다.
간단한 2~3 개 상태만 있으면 enum + switch 로도 충분하다 ( 오버 엔지니어링 주의 )
타이머와 섞을 때 Exit 시 정리 필수
오브젝트 풀 패턴 ( Object Pool ) - 생성 / 성능 최적화
자주 생성 / 파괴되는 객체를 미리 만들어 풀에 보관했다가 재사용해서 GC 할당 비용을 절감하는 패턴
사용하면 좋은 상황
총알 / 파편 / 이펙트 / 적 스폰 처럼 빈번한 생성 및 삭제가 있는 경우
모바일 / VR 등 프레임 드랍 / GC 스파이크 민감한 환경
구조
Queue / Stack 에 비활성 객체 저장 → Get() 이 꺼내 활성화 → Release() 가 반환 / 비활성
▼Unity 예시 코드
public class GameObjectPool : MonoBehaviour {
[SerializeField] GameObject prefab;
[SerializeField] int prewarm = 10;
readonly Queue<GameObject> pool = new();
void Awake() {
for (int i = 0; i < prewarm; i++) {
var go = Instantiate(prefab);
go.SetActive(false);
pool.Enqueue(go);
}
}
public GameObject Get(Vector3 pos, Quaternion rot) {
var go = pool.Count > 0 ? pool.Dequeue() : Instantiate(prefab);
go.transform.SetPositionAndRotation(pos, rot);
go.SetActive(true);
return go;
}
public void Release(GameObject go) {
go.SetActive(false);
pool.Enqueue(go);
}
}
주의할 점
Rigidbody / Particle 등은 속도 / 타이머 리셋 필수
리사이즈 전략 ( 최대치 / 증분 ) 와 프리워밍 ( 초기 채우기 ) 로 스파이크 회피
상태 초기화 인터페이스 ( IPoolable.OnGet/OnRelease ) 로 누수 상태 방지
컴포넌트 패턴 ( Component ) - 구조 ( Structural ) / 합성 ( Composition )
상속 대신 합성으로 기능을 붙였다 뗏다 하는 설계
사용하면 좋은 상황
이동 / 체력 / 공격 / AI 등 기능이 직교 ( orthogonal ) 하고 재조합 가능한 경우
코드를 작게 쪼개 재사용 하고 싶을 때 , 상속 트리 폭발을 피하고 싶을 때
구조
엔티티 ( 예 : GameObject ) 는 여러 컴포넌트를 보유한다
컴포넌트끼리 인터페이스 / 이벤트로 느슨하게 연결한다
▼Unity 예시 코드
public interface IDamageable { void TakeDamage(int amount); }
public class Health : MonoBehaviour, IDamageable {
[SerializeField] int maxHp = 100;
public int Current { get; private set; }
public event Action<float> OnHpRatioChanged;
void Awake() => Current = maxHp;
public void TakeDamage(int amount) {
Current = Mathf.Max(0, Current - amount);
OnHpRatioChanged?.Invoke(Current / (float)maxHp);
}
}
public class Mover : MonoBehaviour {
[SerializeField] float speed = 5f;
void Update() => transform.position += transform.forward * speed * Time.deltaTime;
}
public class HealthBar : MonoBehaviour {
[SerializeField] Health hp;
[SerializeField] UnityEngine.UI.Image fill;
void OnEnable() => hp.OnHpRatioChanged += OnHp;
void OnDisable() => hp.OnHpRatioChanged -= OnHp;
void OnHp(float ratio) => fill.fillAmount = ratio;
}
주의할 점
God Component ( 비대형 ) 금지 - 단일 책임 원칙
GetComponent 는 캐싱해서 사용 ( 매 프레임 호출 지양 )
통신은 인터페이스 / 이벤트 / 메세지로 , 직접 참조 얽힘 줄이자
옵저버 패턴 ( Observer ) - 행위 ( Behavioral )
한 객체의 상태 변화가 있을 때 구독자들에게 자동 알림을 보내는 이벤트 기반 설계
사용하면 좋은 상황
UI 갱신 ( 점수 / 체력 등 모델 UI ) , 게임 이벤트 브로드캐스트
시스템 간 결합도 낮추기가 중요할 때
구조
주체 ( Subject ) : 상태 보유 , 이벤트 발행
옵저버 ( Observer ) : 이벤트 구독 / 처리
▼예시 코드 ( C# / Unity / 이벤트 / 델리게이트 )
public class ScoreModel {
public event Action<int> OnScoreChanged;
int _score;
public int Score {
get => _score;
set { if (_score == value) return; _score = value; OnScoreChanged?.Invoke(_score); }
}
}
public class ScoreUI : MonoBehaviour {
[SerializeField] ScoreModel model;
[SerializeField] TMPro.TMP_Text text;
void OnEnable() => model.OnScoreChanged += UpdateUI;
void OnDisable() => model.OnScoreChanged -= UpdateUI;
void UpdateUI(int s) => text.text = s.ToString();
}
주의할 점
구독 해제 누락 시 메모리 누수 발생 ( OnDisable 에서 반드시 해제 필요 )
다수 브로드캐스트 시 이벤트 폭발 ( 필요한 토픽만 구독하도록 채널 분리 필요 )
이벤트 체인이 길면 디버깅이 어렵다
정리
- 싱글톤 패턴 : 게임에서 유일한 인스턴스 보장, 전역적인 접근이 필요한 경우 사용되는 패턴
- 상태 패턴 : 게임에서 오브젝트 동작이 상태에 따라 달라지는 경우 사용되는 패턴
- 오브젝트 풀 패턴 : 게임에서 반복적으로 생성되고 소멸되는 객체들을 효율적으로 관리하기 위한 패턴
- 컴포넌트 패턴 : 게임 오브젝트의 동작과 기능을 구현하는데 사용되는 패턴
- 옵져버 패턴 : 상태 변경을 이벤트로 발행해 구독자들이 자동 변경하도록 하는 패턴
참고 자료
https://unity.com/resources/level-up-your-code-with-game-programming-patterns
'⭐C Sharp > 13. 디자인 패턴' 카테고리의 다른 글
| Factory Method (0) | 2025.09.30 |
|---|---|
| 의존성 주입 ( Dependency Injection ) (0) | 2025.09.29 |
| Gang of Four (0) | 2025.09.29 |
| 디자인 패턴 ( Design Pattern ) (0) | 2025.09.29 |
| SOLID 원칙 (0) | 2025.09.29 |