2025/08/01 TIL

민트맛치킨·2025년 8월 1일

Unreal

목록 보기
21/26

디자인 패턴

옵저버 패턴

  • 객체의 상태 변화를 관찰하던 객체들에게 자동으로 알림을 보내는 패턴
  • 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가 거리·상황따라 근접/원거리/마법/회피 등 공격정책을 런타임 교체
    • 주의: 너무 단순한 알고리즘이면 별도 전략화가 불필요, 전략 객체 남발시 관리 어려움

0개의 댓글