: 클래스를 하나의 인스턴스로만 존재하도록 만드는 패턴
간단한 방식의 싱글턴
using UnityEngine;
public class StudySingleton : MonoBehaviour
{
// 외부 접근을 허용, 내부에서 설정 가능
public static StudySingleton Instance { get; private set; }
void Awake()
{
if (Instance == null)
{
Instance = this; // 현재 객체를 싱글턴 인스턴스로 설정
}
else
{
Destroy(gameObject); // 중복 생성 방지
}
}
}
instance라는 변수가 static인 것임. 이는 static class와 다름.
-> static class(정적 클래스) 이미 메모리에 위치함
-> singleton은 new로 생성하기 전까지는 그냥 변수 자체만 있음. 즉, 원하는 시점에 유연하게 사용 가능하다는 뜻.
Generic <T>를 활용한 Singleton
public class SingletonMonobehaviour<T> : MonoBehaviour where T : MonoBehaviour
{
static T instance = null;
public static T Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<T>();
if (instance == null)
{
var newObj = new GameObject(typeof(T).ToString());
instance = newObj.AddComponent<T>();
}
}
return instance;
}
}
protected virtual void Awake()
{
if (instance == null)
{
instance = this as T;
DontDestroyOnLoad(this.gameObject);
}
else
Destroy(this.gameObject);
}
}
제너릭 싱글턴
공통된 싱글턴 로직을 제너릭 기반 부모 클래스로 만들어서 재사용
다양한 매니저 클래스들이 상속만으로 싱글턴 구현 가능
: 필요한 오브젝트를 미리 만들어 두고 재사용하는 패턴
Instantiate() & Destroy()를 반복하면 GC(가비지 컬렉션)와 CPU 부하가 커짐
→ 한 번 만든 객체를 꺼냈다가 다시 넣는 방식으로 재사용
Queue 활용한 오브젝트 풀
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StudyObjectPool : StudyGenericSingleton<StudyObjectPool>
{
public Queue<GameObject> objQueue = new Queue<GameObject>(); // 오브젝트가 들어갈 풀
public GameObject objPrefab; // 생성될 오브젝트
public int poolSize = 500;
void Start()
{
CreateObject();
}
private void CreateObject()
{
for (int i = 0; i < poolSize; i++)
{
GameObject newObj = Instantiate(objPrefab, transform);
EnqueueObject(newObj);
}
}
public void EnqueueObject(GameObject obj) // 오브젝트를 넣는 기능
{
objQueue.Enqueue(obj);
obj.SetActive(false);
}
public GameObject DequeueObject() // 오브젝트를 뽑는 기능
{
GameObject obj = objQueue.Dequeue();
return obj;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (objQueue.Count < 10)
CreateObject();
GameObject obj = DequeueObject(); // 풀에서 오브젝트를 뽑아서 사용
obj.transform.SetPositionAndRotation(transform.position, Quaternion.identity);
}
}
}
유니티에 내장된 Pool 기능
using UnityEngine;
using UnityEngine.Pool;
public class PoolManager : MonoBehaviour
{
public ObjectPool<GameObject> pool;
public GameObject prefab;
void Awake()
{
pool = new ObjectPool<GameObject>(CreateObject, OnGetObject, OnReleaseObject, OnDestroyObject, maxSize: 100);
}
private GameObject CreateObject()
{
GameObject obj = Instantiate(prefab);
obj.SetActive(false);
return obj;
}
private void OnGetObject(GameObject obj)
{
obj.SetActive(true);
}
private void OnReleaseObject(GameObject obj)
{
obj.SetActive(false);
}
private void OnDestroyObject(GameObject obj)
{
Destroy(obj);
}
void Update()
{
if (Input.GetMouseButton(0))
{
GameObject obj = pool.Get();
}
}
}
using UnityEngine;
public class PoolItem : MonoBehaviour
{
private PoolManager poolManager;
private bool isInit = false;
void Awake()
{
poolManager = FindFirstObjectByType<PoolManager>();
}
void OnEnable()
{
if (!isInit)
isInit = true;
else
Invoke("ReturnObject", 2f);
}
void ReturnObject()
{
poolManager.pool.Release(gameObject);
}
}
: 객체의 상태(Behavior)를 클래스로 분리해서 관리하는 패턴
상태가 바뀌면 객체의 동작도 바뀜
if/else나 switch문으로 상태를 관리하는 대신, 상태별 클래스를 따로 두어 깔끔하게 관리
간단 예시
public interface IState { void Enter(); void Update(); void Exit(); }
public class IdleState : IState {
public void Enter() { Debug.Log("Idle 시작"); }
public void Update() { Debug.Log("Idle 중"); }
public void Exit() { Debug.Log("Idle 끝"); }
}
public class Player {
IState currentState;
public void ChangeState(IState newState) {
currentState?.Exit();
currentState = newState;
currentState.Enter();
}
}
MonoBehaviour가 없는 경우

IState 인터페이스가 MonoBehaviour를 매개변수로 받음
(StateEnter(MonoBehaviour mono) 이런 식)
IdleState, MoveState, AttackState는 일반 C# 클래스
StudyState(컨텍스트)가 상태 인스턴스를 new로 직접 생성
MonoBehaviour가 있는 경우

IState는 MonoBehaviour 상속 안 받음, 그러나 상태 클래스가 MonoBehaviour를 직접 상속
IdleState, MoveState, AttackState는 MonoBehaviour 컴포넌트
StudyState가 gameObject.AddComponent()처럼 상태를 GameObject에 붙여서 사용
+) Unity에서 MonoBehaviour는 일반 클래스가 아니라 Unity 엔진이 직접 관리하는 "컴포넌트"이기 때문에 "new"로 만들면 안된다.
: 객체가 게시(Publiish)/구독(Subscribe)할 수 있는 전역 이벤트를 관리하는 중앙 허브 방식 패턴
간단한 예시
public static class EventBus {
public static event Action OnGameStart;
public static void PublishGameStart() => OnGameStart?.Invoke();
}
public class GameUI {
void OnEnable() => EventBus.OnGameStart += ShowStartUI;
void ShowStartUI() => Debug.Log("게임 시작 UI 표시");
}
public class GameManager {
void StartGame() => EventBus.PublishGameStart();
}
추가 예시

: 주체(Subject)가 있고, 여러 관찰자(Observer)가 주체의 상태 변화를 감시하는 패턴
주체가 상태 변화를 알리면, 구독 중인 관찰자들이 반응
EventBus는 Observer를 일반화한 구현체 중 하나라고 볼 수 있음

간단한 예시
public interface IObserver { void UpdateState(int value); }
public class Subject {
List<IObserver> observers = new();
int state;
public void Attach(IObserver observer) => observers.Add(observer);
public void SetState(int value) {
state = value;
Notify();
}
void Notify() {
foreach (var o in observers) o.UpdateState(state);
}
}
public class HUD : IObserver {
public void UpdateState(int value) => Debug.Log($"HUD 업데이트: {value}");
}
추가 예시

: 동작을 캡슐화하여 런타임에 알고리즘을 쉽게 교체하는 패턴
상황에 맞는 전략(알고리즘)을 바꾸면, 동작 방식이 달라짐
State 패턴과 비슷하지만, State는 상태 변화에 따른 행동 변경, Strategy는 행동(알고리즘) 자체를 바꾸는 것이 목적
구조 예시
public interface IAttackStrategy {
void Attack();
}
public class SwordAttack : IAttackStrategy {
public void Attack() => Debug.Log("검 공격!");
}
public class BowAttack : IAttackStrategy {
public void Attack() => Debug.Log("활 공격!");
}
public class Character {
private IAttackStrategy attackStrategy;
public void SetAttackStrategy(IAttackStrategy strategy) {
attackStrategy = strategy;
}
public void PerformAttack() {
attackStrategy?.Attack();
}
}
사용
Character player = new Character();
player.SetAttackStrategy(new SwordAttack());
player.PerformAttack(); // 검 공격!
player.SetAttackStrategy(new BowAttack());
player.PerformAttack(); // 활 공격!
: 명령(행위)을 객체로 만들어 캡슐화 하는 것
구조 예시
public interface ICommand {
void Execute();
}
public class JumpCommand : ICommand {
public void Execute() => Debug.Log("점프!");
}
public class FireCommand : ICommand {
public void Execute() => Debug.Log("발사!");
}
public class InputHandler {
private ICommand command;
public void SetCommand(ICommand cmd) {
command = cmd;
}
public void HandleInput() {
command?.Execute();
}
}
사용
InputHandler handler = new InputHandler();
handler.SetCommand(new JumpCommand());
handler.HandleInput(); // 점프!
handler.SetCommand(new FireCommand());
handler.HandleInput(); // 발사!