디자인 패턴
옵저버 패턴
- 객체의 상태 변화를 관찰하던 객체들에게 자동으로 알림을 보내는 패턴
- 1:N 의존 관계를 정의하여 한 객체의 상태가 변하면 모든 의존 객체들이 알림을 받고 자동으로 업데이트 됨
#include "CoreMinimal.h" #include "GameFramework/Actor.h" DECLARE_MULTICAST_DELEGATE(FOnHealthChangedDelegate); // 상태 변경 이벤트 델리게이트 정의 class AMyCharacter : public AActor { public: // 체력 변경 이벤트 델리게이트 FOnHealthChangedDelegate OnHealthChanged; private: float Health = 100.f; public: void TakeDamage(float Damage) { Health -= Damage; // 체력 감소 if (Health < 0) Health = 0; OnHealthChanged.Broadcast(); // 체력 변화 모든 옵저버에게 알림 } float GetHealth() const { return Health; } }; class UHealthUI { public: // UI 갱신 바인딩 함수 void UpdateUI() { UE_LOG(LogTemp, Warning, TEXT("Health UI가 갱신되었습니다.")); } }; void ObserverPatternExample() { AMyCharacter* Char = new AMyCharacter(); UHealthUI* UI = new UHealthUI(); // 델리게이트에 UI 갱신 함수 바인딩 (옵저버 등록) Char->OnHealthChanged.AddLambda([UI]() { UI->UpdateUI(); // 체력 변화 시 UI 갱신 }); Char->TakeDamage(30.f); // 체력 변경, UI 자동 업데이트 호출 }커맨드 패턴
- 요청을 객체로 캡슐화하여 매개변수로 전달
- 요청을 큐에 저장하거나 로그를 남기고 실행 취소를 지원할 수 있게 하는 패턴
#include "CoreMinimal.h" class ICommand { public: virtual ~ICommand() {} virtual void Execute() = 0; // 명령 실행 메서드 virtual void Undo() {} // 명령 취소(옵션) }; class APlayerCharacter { public: void Jump() { UE_LOG(LogTemp, Warning, TEXT("플레이어 점프 실행")); } }; class JumpCommand : public ICommand { private: APlayerCharacter* Player; public: JumpCommand(APlayerCharacter* InPlayer) : Player(InPlayer) {} virtual void Execute() override { if(Player) Player->Jump(); // 실제 행동 실행 위임 } }; class InputHandler { private: ICommand* ButtonCommand = nullptr; public: void SetCommand(ICommand* Cmd) { ButtonCommand = Cmd; // 명령 등록 } void PressButton() { if(ButtonCommand) ButtonCommand->Execute(); // 명령 실행 } }; void CommandPatternExample() { APlayerCharacter* Player = new APlayerCharacter(); JumpCommand* JumpCmd = new JumpCommand(Player); // 점프 명령 객체 생성 InputHandler Input; Input.SetCommand(JumpCmd); // 입력에 명령 등록 Input.PressButton(); // 명령 실행 → 플레이어 점프 }상태 패턴
- 객체의 내부 상태가 변할 때 객체의 행동도 함께 변하도록 하는 패턴
- 상태를 별도의 클래스로 캡슐화하고 위임을 통해 행동을 변경함
#include "CoreMinimal.h" class AMyCharacter; class State { public: virtual ~State() {} virtual void Handle(AMyCharacter* Character) = 0; // 상태별 행동 처리 }; class IdleState : public State { public: virtual void Handle(AMyCharacter* Character) override { UE_LOG(LogTemp, Warning, TEXT("캐릭터가 대기 상태입니다.")); } }; class RunState : public State { public: virtual void Handle(AMyCharacter* Character) override { UE_LOG(LogTemp, Warning, TEXT("캐릭터가 달리는 중입니다.")); } }; class AMyCharacter { private: State* CurrentState = nullptr; public: void SetState(State* NewState) { CurrentState = NewState; // 상태 교체 } void Update() { if(CurrentState) CurrentState->Handle(this); // 상태별 행동 수행 } }; void StatePatternExample() { AMyCharacter* Char = new AMyCharacter(); IdleState* Idle = new IdleState(); RunState* Run = new RunState(); Char->SetState(Idle); Char->Update(); // 대기 상태 행동 출력 Char->SetState(Run); Char->Update(); // 달리는 상태 행동 출력 }전략 패턴
- 동일한 문제를 해결하는 여러 알고리즘을 캡슐화하고 런타임에 알고리즘을 선택할 수 있게 하는 패턴
#include "CoreMinimal.h" class IAttackStrategy { public: virtual ~IAttackStrategy() {} virtual void Attack() = 0; // 공격 알고리즘 인터페이스 }; class MeleeAttack : public IAttackStrategy { public: virtual void Attack() override { UE_LOG(LogTemp, Warning, TEXT("근접 공격 수행")); } }; class RangedAttack : public IAttackStrategy { public: virtual void Attack() override { UE_LOG(LogTemp, Warning, TEXT("원거리 공격 수행")); } }; class AEnemyCharacter { private: IAttackStrategy* CurrentStrategy = nullptr; public: void SetAttackStrategy(IAttackStrategy* Strategy) { CurrentStrategy = Strategy; // 전략 교체 } void PerformAttack() { if(CurrentStrategy) CurrentStrategy->Attack(); // 현재 전략 기반 공격 수행 } }; void StrategyPatternExample() { AEnemyCharacter* Enemy = new AEnemyCharacter(); MeleeAttack* Melee = new MeleeAttack(); RangedAttack* Ranged = new RangedAttack(); Enemy->SetAttackStrategy(Melee); Enemy->PerformAttack(); // 근접 공격 출력 Enemy->SetAttackStrategy(Ranged); Enemy->PerformAttack(); // 원거리 공격 출력 }
패턴 언제 쓰기 좋은가 언제 쓰지 않는 게 좋은가 옵저버 - 한 이벤트/상태 변화가 여러 오브젝트(시스템)에 동시 알림 필요할 때
- UI, 로그, 업적, 사운드 등 다수 시스템 자동갱신- 옵저버 수가 과도하게 많아지면 복잡도 급상승
- 일괄처리(for문 등)가 더 빠를 때커맨드 - 행동 요청을 큐잉/로그/취소/재실행해야 할 때
- 버튼 입력, 플레이 기록, 복잡한 시나리오 이벤트- 단순 호출이나 즉시 실행만 필요할 때
- 명령 객체가 불필요하게 많아질 때상태 - 객체의 상태 변화에 따라 동작이 명확히 달라질 때
- 캐릭터/적의 FSM, 단계별 로직 분리- 상태별 행동 차이가 적고, 단순 조건이면 if문이 더 명료할 때 전략 - 다양한 알고리즘/정책을 런타임에 쉽게 교체해야 할 때
- AI 행동 방식, 경로탐색, 이펙트 적용 등- 전략이 하나거나, 분기만으로 충분할 때
- 지나치게 사소한 알고리즘을 전략화할 때
옵저버
- 예시: 플레이어 체력 변동 시 UI, 사운드, 경고, 업적, 로그 등 여러 컴포넌트가 자동 반응
- 주의: 구독/해지 누락에 의한 메모리릭, 이벤트 전파 과도시 성능 저하, 의존성·참조관계 관리 주의
커맨드
- 예시: "실행 취소/재실행", 플레이 동작 시나리오 자동화, 네트워크 동기화 명령 큐
- 주의: 너무 세분화된 명령 생성은 관리 난이도↑, 단순 호출에선 오히려 오버엔지니어링
상태
- 예시: NPC가 순찰→추격→공격→사망 등 각 상태에 따라 행동/AI 전환
- 주의: 상태 클래스 많아질 때 복잡성↑, 단순 FSM은 switch-case문이 더 쉽기도
전략
- 예시: 플레이어/적 AI가 거리·상황따라 근접/원거리/마법/회피 등 공격정책을 런타임 교체
- 주의: 너무 단순한 알고리즘이면 별도 전략화가 불필요, 전략 객체 남발시 관리 어려움