언리얼 엔진 5

[UE5] 델리게이트

언린이 2026. 2. 28. 15:11
반응형

1. 델리게이트가 필요한 이유

게임 개발에서는 서로 다른 오브젝트 간에 이벤트를 전달해야 하는 상황이 매우 빈번합니다. 캐릭터의 체력이 변경되면 UI가 갱신되어야 하고, 적이 사망하면 퀘스트 시스템이 이를 감지해야 하며, 문이 열리면 사운드와 애니메이션이 동시에 재생되어야 합니다. 이러한 상호작용을 구현할 때 각 오브젝트가 서로의 구체적인 타입을 알아야 한다면, 코드의 결합도(coupling)가 높아져 유지보수가 어려워집니다.

델리게이트(Delegate)는 이 문제를 해결하는 핵심 메커니즘입니다. 델리게이트를 사용하면 호출하는 측은 응답하는 측의 구체적인 타입을 알 필요 없이, 특정 시그니처(반환형과 파라미터 목록)에 맞는 함수를 등록해 두고 나중에 호출할 수 있습니다. 이를 통해 오브젝트 간의 결합도를 낮추면서도 유연한 이벤트 기반 설계가 가능합니다.

언리얼 엔진의 델리게이트 시스템은 두 가지 축의 조합으로 네 가지 유형을 제공합니다. 바인딩할 수 있는 함수의 수에 따라 싱글캐스트와 멀티캐스트로 나뉘고, 블루프린트 연동 및 시리얼라이제이션 지원 여부에 따라 일반(Non-Dynamic)과 다이나믹(Dynamic)으로 나뉩니다. 이 글에서는 각 유형의 구조와 사용법을 코드 예제와 함께 자세히 살펴보고, 어떤 상황에서 어떤 유형을 선택해야 하는지 판단 기준을 제공합니다.

2. 델리게이트의 핵심 구조와 분류

2-1. 네 가지 유형 개요

델리게이트는 두 가지 기준에 따라 네 가지로 분류됩니다.

첫 번째 기준은 바인딩 수입니다. 싱글캐스트(Single-cast)는 하나의 함수만 바인딩할 수 있고, 멀티캐스트(Multi-cast)는 여러 함수를 동시에 바인딩할 수 있습니다.

두 번째 기준은 리플렉션 연동 여부입니다. 일반 델리게이트(Non-Dynamic)는 C++ 전용이며 리플렉션 시스템을 거치지 않아 성능이 빠릅니다. 다이나믹 델리게이트(Dynamic)는 리플렉션 시스템과 통합되어 블루프린트에서 바인딩이 가능하고 시리얼라이제이션을 지원하지만, 함수를 이름(FName)으로 검색하는 과정이 필요하여 상대적으로 느립니다.

이 두 기준을 조합하면 다음 네 가지 유형이 됩니다.

유형 선언 매크로 바인딩 수 블루프린트 반환값 성능
싱글캐스트 DECLARE_DELEGATE 1개 불가 가능 빠름
멀티캐스트 DECLARE_MULTICAST_DELEGATE 여러 개 불가 불가 빠름
다이나믹 싱글캐스트 DECLARE_DYNAMIC_DELEGATE 1개 가능 가능 느림
다이나믹 멀티캐스트 DECLARE_DYNAMIC_MULTICAST_DELEGATE 여러 개 가능 불가 느림

2-2. 선언 매크로의 파라미터 규칙

델리게이트 선언 매크로는 파라미터 수에 따라 접미사가 달라집니다. 파라미터가 없으면 접미사 없이 사용하고, 1개면 _OneParam, 2개면 _TwoParams, 최대 8개(_EightParams)까지 지원합니다.

다이나믹 델리게이트는 일반 델리게이트와 달리 파라미터 선언 방식이 다릅니다. 일반 델리게이트는 타입만 나열하지만, 다이나믹 델리게이트는 타입과 파라미터 이름을 함께 명시해야 합니다. 이는 리플렉션 시스템이 파라미터 이름을 통해 블루프린트에서 핀 이름을 표시하기 때문입니다.

다음은 두 방식의 차이를 보여주는 예제입니다.

// 일반 델리게이트 — 타입만 나열합니다
DECLARE_DELEGATE_TwoParams(FOnHealthChanged, float, float);

// 다이나믹 델리게이트 — 타입과 파라미터 이름을 함께 명시합니다
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnHealthChangedDynamic, float, CurrentHealth, float, MaxHealth);

위의 코드에서 다이나믹 버전의 CurrentHealth, MaxHealth는 블루프린트 에디터에서 해당 핀의 이름으로 표시됩니다. 파라미터 이름을 생략하면 컴파일 에러가 발생합니다.

델리게이트 시그니처 선언은 글로벌 스코프, 네임스페이스, 클래스 선언 내부에 위치할 수 있습니다. 단, 함수 본문 내부에서는 선언할 수 없습니다.

3. 싱글캐스트 델리게이트

3-1. 개념과 특징

싱글캐스트 델리게이트는 하나의 함수만 바인딩할 수 있는 가장 기본적인 형태입니다. 1대1 콜백 패턴에 적합하며, 네 가지 유형 중 유일하게 반환값을 지원합니다. 새로운 함수를 바인딩하면 기존에 바인딩된 함수는 자동으로 해제됩니다.

3-2. 선언과 바인딩

다음은 싱글캐스트 델리게이트를 선언하고 사용하는 전체 흐름 예제입니다.

// 헤더 파일 — 글로벌 스코프에서 델리게이트 시그니처를 선언합니다
DECLARE_DELEGATE_OneParam(FOnItemPickedUp, int32 /* ItemId */);

UCLASS()
class AItemSpawner : public AActor
{
    GENERATED_BODY()

public:
    // 아이템이 수집되었을 때 호출될 델리게이트입니다
    FOnItemPickedUp OnItemPickedUp;
};
// 수신측 — 바인딩하는 클래스
void AMyPlayerController::BeginPlay()
{
    Super::BeginPlay();

    AItemSpawner* Spawner = FindSpawnerInWorld();
    if (Spawner)
    {
        // UObject의 멤버 함수를 바인딩합니다
        Spawner->OnItemPickedUp.BindUObject(this, &AMyPlayerController::HandleItemPickedUp);
    }
}

void AMyPlayerController::HandleItemPickedUp(int32 ItemId)
{
    // 아이템 수집 처리 로직
    AddItemToInventory(ItemId);
}
// 발신측 — 델리게이트를 실행하는 코드
void AItemSpawner::OnPlayerOverlap(AActor* OverlappingActor)
{
    // 바인딩 여부를 확인한 뒤 실행합니다
    if (OnItemPickedUp.IsBound())
    {
        OnItemPickedUp.Execute(CurrentItemId);
    }

    // 또는 한 줄로 안전하게 실행할 수 있습니다
    OnItemPickedUp.ExecuteIfBound(CurrentItemId);
}

위의 코드에서 BindUObjectUObject 파생 클래스의 멤버 함수를 바인딩하는 함수입니다. Execute()는 바인딩된 함수가 없으면 크래시가 발생할 수 있으므로, IsBound() 체크 후 호출하거나 ExecuteIfBound()를 사용하는 것이 안전합니다.

3-3. 다양한 바인딩 방식

싱글캐스트 델리게이트는 바인딩 대상에 따라 여러 바인딩 함수를 제공합니다.

BindUObjectUObject 파생 클래스의 멤버 함수를 바인딩합니다. 가비지 컬렉션과 연동되어 오브젝트가 소멸되면 자동으로 바인딩이 무효화됩니다. 가장 안전한 바인딩 방식이므로 UObject를 다루는 상황에서는 이 방식을 우선적으로 사용해야 합니다.

BindRaw는 일반 C++ 객체(UObject가 아닌)의 멤버 함수를 바인딩합니다. GC와 연동되지 않으므로 객체의 수명을 개발자가 직접 관리해야 합니다. 바인딩된 객체가 먼저 소멸되면 댕글링 포인터 문제가 발생할 수 있어 주의가 필요합니다.

BindSPTSharedPtr로 관리되는 객체의 멤버 함수를 바인딩합니다. 공유 포인터의 참조 카운트와 연동되어 약한 참조(weak reference)로 유효성을 자동 확인합니다.

BindLambda는 람다 함수를 바인딩합니다. 간단한 콜백에 유용하지만, 캡처한 변수의 수명을 개발자가 보장해야 합니다.

BindUFunctionUFUNCTION 매크로가 붙은 함수를 이름(FName)으로 바인딩합니다. 리플렉션을 통해 함수를 찾으므로 다이나믹 델리게이트와 유사하게 동작합니다.

다음은 람다를 사용한 바인딩 예제입니다.

void AMyActor::SetupCallback()
{
    FOnItemPickedUp ItemDelegate;

    // 람다를 바인딩합니다
    ItemDelegate.BindLambda([this](int32 ItemId)
    {
        UE_LOG(LogTemp, Log, TEXT("아이템 %d 을(를) 수집했습니다."), ItemId);
        ProcessItem(ItemId);
    });

    ItemDelegate.ExecuteIfBound(42);
}

위 코드처럼 BindLambda는 별도의 멤버 함수를 선언하지 않고도 인라인으로 콜백을 정의할 수 있어, 간단한 처리에 매우 편리합니다.

3-4. 반환값이 있는 델리게이트

싱글캐스트 델리게이트는 DECLARE_DELEGATE_RetVal 매크로를 사용하여 반환값을 가질 수 있습니다. 이 기능은 네 가지 유형 중 싱글캐스트에서만 지원됩니다. 멀티캐스트는 여러 함수가 각각 다른 값을 반환할 수 있어 어떤 값을 사용할지 모호해지므로, 반환값을 지원하지 않습니다.

다음은 반환값 델리게이트의 예제입니다.

// 반환값이 있는 델리게이트 선언 — 첫 번째 인자가 반환형입니다
DECLARE_DELEGATE_RetVal_OneParam(bool, FCanInteract, AActor* /* TargetActor */);

UCLASS()
class AInteractableObject : public AActor
{
    GENERATED_BODY()

public:
    FCanInteract CanInteractCheck;

    void AttemptInteraction(AActor* Interactor)
    {
        if (CanInteractCheck.IsBound())
        {
            // 바인딩된 함수의 반환값을 사용합니다
            bool bCanInteract = CanInteractCheck.Execute(Interactor);
            if (bCanInteract)
            {
                PerformInteraction(Interactor);
            }
        }
    }
};

위의 코드에서 DECLARE_DELEGATE_RetVal_OneParam의 첫 번째 인자가 반환형(bool)이고, 두 번째가 델리게이트 이름, 세 번째부터가 파라미터입니다. 이 패턴은 특정 조건의 판단을 외부에 위임할 때 유용합니다.

4. 멀티캐스트 델리게이트

4-1. 개념과 특징

멀티캐스트 델리게이트는 여러 함수를 동시에 바인딩하고, Broadcast() 한 번의 호출로 등록된 모든 함수를 실행할 수 있는 1대N 통신 방식입니다. 옵저버 패턴(Observer Pattern)의 구현에 가장 적합한 유형입니다.

멀티캐스트 델리게이트의 핵심 특성 두 가지는 다음과 같습니다. 첫째, 반환값을 사용할 수 없습니다. 여러 함수가 각각 다른 값을 반환하면 어떤 값을 채택해야 할지 모호해지기 때문입니다. 둘째, Broadcast()는 아무것도 바인딩되어 있지 않아도 안전하게 호출할 수 있습니다. 싱글캐스트의 Execute()가 바인딩 없이 호출하면 크래시가 발생할 수 있는 것과 대조적입니다.

4-2. 선언과 사용법

다음은 캐릭터의 체력 변경 이벤트를 여러 시스템에 전파하는 예제입니다.

// 체력 변경을 알리는 멀티캐스트 델리게이트를 선언합니다
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float /* CurrentHealth */, float /* MaxHealth */);

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    FOnHealthChanged OnHealthChanged;

    void SetHealth(float NewHealth)
    {
        CurrentHealth = FMath::Clamp(NewHealth, 0.f, MaxHealth);

        // 등록된 모든 함수에 체력 변경을 알립니다
        OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
    }

private:
    float CurrentHealth = 100.f;
    float MaxHealth = 100.f;
};
// UI 시스템 — 체력 바 갱신을 위해 바인딩합니다
void UHealthBarWidget::SetupBinding(AMyCharacter* Character)
{
    if (Character)
    {
        Character->OnHealthChanged.AddUObject(this, &UHealthBarWidget::UpdateHealthBar);
    }
}

void UHealthBarWidget::UpdateHealthBar(float Current, float Max)
{
    float Percent = (Max > 0.f) ? Current / Max : 0.f;
    HealthProgressBar->SetPercent(Percent);
}
// 사운드 시스템 — 피격 사운드 재생을 위해 바인딩합니다
void UDamageSoundComponent::Initialize(AMyCharacter* Character)
{
    if (Character)
    {
        Character->OnHealthChanged.AddUObject(this, &UDamageSoundComponent::PlayDamageSound);
    }
}

void UDamageSoundComponent::PlayDamageSound(float Current, float Max)
{
    if (Current < Max)
    {
        UGameplayStatics::PlaySoundAtLocation(this, DamageSound, GetOwner()->GetActorLocation());
    }
}

위의 코드에서 AMyCharacter는 UI나 사운드 시스템의 구체적인 타입을 전혀 알지 못합니다. Broadcast()만 호출하면 등록된 모든 수신자에게 이벤트가 전달됩니다. 이것이 델리게이트를 통한 느슨한 결합(loose coupling)의 핵심입니다.

4-3. 바인딩 추가와 해제

멀티캐스트 델리게이트는 싱글캐스트의 Bind 접두사 대신 Add 접두사를 사용합니다. 여러 함수를 추가할 수 있으므로 "바인딩"이 아닌 "추가"라는 의미입니다.

AddUObject는 함수를 추가하고 FDelegateHandle을 반환합니다. 이 핸들은 나중에 특정 바인딩을 해제할 때 사용됩니다.

다음은 바인딩 해제 패턴을 보여주는 예제입니다.

// 바인딩 추가 시 핸들을 저장합니다
FDelegateHandle HealthChangedHandle;

void UHealthBarWidget::SetupBinding(AMyCharacter* Character)
{
    if (Character)
    {
        // AddUObject는 FDelegateHandle을 반환합니다
        HealthChangedHandle = Character->OnHealthChanged.AddUObject(
            this, &UHealthBarWidget::UpdateHealthBar);
    }
}

void UHealthBarWidget::CleanupBinding(AMyCharacter* Character)
{
    if (Character)
    {
        // 저장해 둔 핸들로 특정 바인딩만 해제합니다
        Character->OnHealthChanged.Remove(HealthChangedHandle);
    }
}

위 코드처럼 FDelegateHandle을 멤버 변수로 저장해 두면, 위젯이 소멸되거나 더 이상 이벤트를 받을 필요가 없을 때 정확히 해당 바인딩만 해제할 수 있습니다. 핸들 없이 RemoveAll(this)를 호출하면 해당 오브젝트가 바인딩한 모든 함수가 일괄 해제됩니다.

5. 다이나믹 델리게이트

5-1. 개념과 특징

다이나믹 델리게이트는 언리얼의 리플렉션 시스템과 통합된 델리게이트입니다. 일반 델리게이트와의 가장 큰 차이점은 두 가지입니다. 첫째, 바인딩된 함수를 FName(문자열 이름)으로 저장하므로 시리얼라이제이션(저장/로드)이 가능합니다. 둘째, 블루프린트에서 바인딩할 수 있습니다.

이러한 유연성의 대가로 성능이 느립니다. 함수 호출 시 이름으로 함수를 검색하는 과정이 필요하기 때문입니다. 따라서 블루프린트 연동이나 시리얼라이제이션이 필요하지 않다면 일반 델리게이트를 사용하는 것이 성능상 유리합니다.

5-2. 다이나믹 싱글캐스트 델리게이트

다이나믹 싱글캐스트 델리게이트는 하나의 함수만 바인딩할 수 있으면서 블루프린트 연동이 가능한 유형입니다. 바인딩에는 BindDynamic 매크로를 사용합니다.

다음은 비동기 로딩 완료 시 콜백을 받는 예제입니다.

// 다이나믹 싱글캐스트 선언 — 파라미터 이름을 반드시 명시합니다
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnAssetLoaded, UObject*, LoadedAsset);

UCLASS()
class UAssetLoader : public UObject
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable)
    void LoadAssetAsync(TSoftObjectPtr<UObject> AssetPath, FOnAssetLoaded OnLoaded);
};

위의 코드에서 FOnAssetLoaded를 함수의 파라미터로 사용하면, 블루프린트에서 이 함수를 호출할 때 콜백 핀이 자동으로 표시됩니다. 이 패턴은 비동기 작업의 완료 알림에 자주 사용됩니다.

5-3. 다이나믹 멀티캐스트 델리게이트

다이나믹 멀티캐스트 델리게이트는 실무에서 가장 많이 사용되는 유형입니다. 여러 함수를 바인딩할 수 있고 블루프린트에서도 사용 가능하여, 블루프린트의 이벤트 디스패처(Event Dispatcher)에 대응합니다.

다음은 다이나믹 멀티캐스트 델리게이트의 전체 사용 흐름 예제입니다.

// 다이나믹 멀티캐스트 선언
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreChanged, int32, NewScore);

UCLASS()
class AMyGameState : public AGameStateBase
{
    GENERATED_BODY()

public:
    // BlueprintAssignable — 블루프린트에서 이벤트를 바인딩할 수 있습니다
    // BlueprintCallable — 블루프린트에서 Broadcast를 호출할 수 있습니다
    UPROPERTY(BlueprintAssignable, BlueprintCallable, Category = "Score")
    FOnScoreChanged OnScoreChanged;

    UFUNCTION(BlueprintCallable)
    void AddScore(int32 Points)
    {
        TotalScore += Points;
        OnScoreChanged.Broadcast(TotalScore);
    }

private:
    int32 TotalScore = 0;
};
// C++에서 다이나믹 멀티캐스트에 바인딩합니다
void UScoreWidget::SetupBinding(AMyGameState* GameState)
{
    if (GameState)
    {
        // AddDynamic 매크로를 사용합니다
        GameState->OnScoreChanged.AddDynamic(this, &UScoreWidget::OnScoreUpdated);
    }
}

// 바인딩되는 함수는 반드시 UFUNCTION이어야 합니다
UFUNCTION()
void UScoreWidget::OnScoreUpdated(int32 NewScore)
{
    ScoreText->SetText(FText::AsNumber(NewScore));
}

위의 코드에서 주목해야 할 점이 세 가지 있습니다. 첫째, UPROPERTYBlueprintAssignable 지정자를 붙여야 블루프린트에서 이벤트를 바인딩할 수 있습니다. 둘째, C++에서 바인딩할 때는 AddDynamic 매크로를 사용합니다(일반 멀티캐스트의 AddUObject가 아닙니다). 셋째, AddDynamic으로 바인딩되는 함수는 반드시 UFUNCTION() 매크로가 선언되어 있어야 합니다. 리플렉션을 통해 이름으로 함수를 찾기 때문입니다.

5-4. 페이로드 데이터

일반 델리게이트(Non-Dynamic)에서는 바인딩 시점에 추가 데이터(Payload)를 함께 전달할 수 있습니다. 페이로드 변수는 바인딩 시 델리게이트 자체에 저장되어, 함수가 호출될 때 자동으로 전달됩니다. 이 기능은 동일한 콜백 함수를 서로 다른 맥락에서 재사용할 때 매우 유용합니다.

다만, 다이나믹 델리게이트에서는 페이로드 데이터를 지원하지 않습니다. 이는 다이나믹 델리게이트가 함수 이름을 기반으로 호출하는 구조이기 때문입니다.

6. 네 가지 유형의 선택 기준

어떤 델리게이트를 사용해야 할지 판단할 때, 다음 세 가지 질문을 순서대로 따라가면 됩니다.

"블루프린트에서 바인딩하거나 시리얼라이제이션이 필요한가?" — 필요하다면 다이나믹 델리게이트를 선택합니다. 필요하지 않다면 일반 델리게이트를 선택합니다. 블루프린트 연동이 불필요한 상황에서 다이나믹을 사용하면 불필요한 성능 오버헤드가 발생합니다.

"여러 수신자가 동시에 응답해야 하는가?" — 체력 변경 시 UI, 사운드, 이펙트가 동시에 반응해야 하는 것처럼 여러 오브젝트가 하나의 이벤트에 응답해야 한다면 멀티캐스트를 선택합니다. 단일 콜백만 필요하다면 싱글캐스트를 선택합니다.

"반환값이 필요한가?" — 반환값이 필요하다면 싱글캐스트만 선택할 수 있습니다. 멀티캐스트는 반환값을 지원하지 않습니다.

실무에서 가장 자주 사용되는 조합은 두 가지입니다. C++ 전용 시스템 내부에서는 멀티캐스트 델리게이트(DECLARE_MULTICAST_DELEGATE)를, 블루프린트 노출이 필요한 게임플레이 이벤트에는 다이나믹 멀티캐스트 델리게이트(DECLARE_DYNAMIC_MULTICAST_DELEGATE)를 사용하는 것이 일반적입니다.

7. 일반 C++과의 비교

일반 C++에서는 std::functionstd::bind를 사용하여 콜백 패턴을 구현합니다. 언리얼의 델리게이트도 본질적으로 동일한 개념으로, 함수 포인터를 타입 안전하게 캡슐화하여 나중에 호출할 수 있게 합니다.

그러나 언리얼 델리게이트는 게임 엔진 특유의 요구사항에 맞춰 여러 측면에서 확장되어 있습니다.

첫째, std::function은 단일 콜백만 저장할 수 있지만, 언리얼의 멀티캐스트 델리게이트는 하나의 델리게이트에 여러 함수를 등록하고 Broadcast()로 일괄 호출할 수 있습니다. 표준 C++에서 동일한 기능을 구현하려면 std::vector<std::function<...>>을 직접 관리해야 합니다.

둘째, std::function은 바인딩된 객체의 수명을 추적하지 않습니다. 객체가 소멸된 후 콜백이 호출되면 정의되지 않은 동작이 발생합니다. 반면 언리얼의 BindUObject/AddUObject는 가비지 컬렉션 시스템과 연동되어, UObject가 소멸되면 바인딩이 자동으로 무효화됩니다.

// 일반 C++ — 객체 소멸 시 댕글링 위험이 있습니다
std::function<void(int32)> Callback;
{
    MyClass Obj;
    Callback = std::bind(&MyClass::OnEvent, &Obj, std::placeholders::_1);
} // Obj 소멸 — Callback 호출 시 정의되지 않은 동작 발생

// 언리얼 — UObject 소멸 시 바인딩이 자동 무효화됩니다
FOnItemPickedUp Delegate;
Delegate.BindUObject(SomeUObject, &USomeClass::HandleEvent);
// SomeUObject가 GC에 의해 소멸되면 바인딩이 안전하게 무효화됩니다

위 코드처럼 일반 C++에서는 개발자가 객체의 수명과 콜백의 유효성을 직접 관리해야 하지만, 언리얼의 델리게이트 시스템은 엔진의 GC와 통합되어 이 문제를 자동으로 처리합니다.

셋째, 다이나믹 델리게이트는 리플렉션 시스템을 통해 함수를 이름으로 검색하고 시리얼라이제이션까지 지원합니다. 이는 표준 C++에는 존재하지 않는 기능으로, 블루프린트 비주얼 스크립팅과의 통합을 가능하게 합니다.

8. 유니티 엔진과의 비교

유니티에서는 이벤트 처리를 위해 C# 언어 수준의 delegate, event, Action, Func 키워드와 유니티 고유의 UnityEvent를 사용합니다.

유니티의 C# delegate/event는 언리얼의 일반 델리게이트에 대응하고, UnityEvent는 인스펙터(에디터)에서 바인딩이 가능하다는 점에서 언리얼의 다이나믹 멀티캐스트 델리게이트에 대응합니다.

언리얼의 델리게이트가 갖는 고유한 강점은 세 가지입니다.

첫째, 바인딩 대상에 따른 세분화된 바인딩 방식을 제공합니다. BindUObject, BindRaw, BindSP, BindLambda, BindUFunction 등 바인딩 대상의 특성(GC 관리 여부, 스마트 포인터 여부 등)에 맞는 전용 함수가 있어, 각 상황에서 최적의 안전성과 성능을 보장합니다. 유니티의 delegate는 이러한 구분 없이 하나의 방식으로 바인딩합니다.

둘째, 멀티캐스트 델리게이트는 C++ 코드와 블루프린트 양쪽에서 동시에 바인딩할 수 있습니다. 하나의 이벤트에 C++로 작성된 핵심 로직과 블루프린트로 작성된 게임플레이 로직을 함께 등록할 수 있어, 프로그래머와 디자이너가 동일한 이벤트 시스템을 공유하면서 각자의 도구로 작업할 수 있습니다.

셋째, 언리얼의 일반(Non-Dynamic) 델리게이트는 리플렉션을 거치지 않으므로, 성능이 중요한 시스템 내부에서는 다이나믹 델리게이트보다 빠른 호출이 가능합니다. 유니티의 UnityEvent는 항상 리플렉션 기반이므로 이러한 선택지가 없습니다.

9. 주의사항

9-1. 싱글캐스트 델리게이트의 Execute() 호출 전 바인딩 유무를 확인합니다

싱글캐스트 델리게이트의 Execute()를 바인딩되지 않은 상태에서 호출하면 크래시가 발생할 수 있습니다. 반드시 IsBound()로 확인한 뒤 호출하거나, ExecuteIfBound()를 사용해야 합니다. 멀티캐스트의 Broadcast()는 바인딩이 없어도 안전하게 호출할 수 있으므로 이 주의사항은 싱글캐스트에만 해당합니다.

9-2. 다이나믹 델리게이트에 바인딩하는 함수에는 반드시 UFUNCTION이 필요합니다

AddDynamic이나 BindDynamic으로 바인딩하는 함수는 반드시 UFUNCTION() 매크로가 선언되어 있어야 합니다. 이 매크로가 없으면 리플렉션 시스템이 함수를 찾을 수 없어 바인딩이 실패합니다. 컴파일 에러가 아닌 런타임 경고로 나타나는 경우가 있어, 실제로 사용해보면 이 실수를 발견하기 어려운 점이 중요합니다.

9-3. 멀티캐스트 델리게이트의 Broadcast() 함수는 실행 순서를 보장하지 않습니다

Broadcast() 호출 시 바인딩된 함수들의 실행 순서는 보장되지 않습니다. 등록 순서대로 실행될 것이라고 가정하면 안 됩니다. 실행 순서에 의존하는 로직이 필요하다면, 델리게이트 대신 명시적인 호출 체인을 구성해야 합니다.

9-4. BindRaw 사용 시 객체 수명에 주의합니다

BindRaw로 바인딩된 일반 C++ 객체는 GC의 추적 대상이 아닙니다. 객체가 소멸된 후에도 델리게이트가 여전히 해당 주소를 참조하고 있으므로, 반드시 객체 소멸 전에 Unbind() 또는 Remove()를 호출하여 바인딩을 해제해야 합니다. 그렇지 않으면 댕글링 포인터로 인한 크래시가 발생합니다.

9-5. 멀티캐스트 델리게이트의 바인딩 해제를 위해 FDelegateHandle을 보관합니다

멀티캐스트 델리게이트에서 특정 바인딩을 해제하려면 Add 계열 함수가 반환하는 FDelegateHandle이 필요합니다. 핸들을 저장하지 않으면 나중에 해당 바인딩만 정확히 해제할 방법이 없습니다. 위젯이나 컴포넌트의 BeginDestroy 또는 EndPlay에서 핸들을 사용해 바인딩을 해제하는 패턴을 습관화해야 합니다.

9-6. 블루프린트 연동이 필요 없다면 일반 델리게이트를 사용합니다

다이나믹 델리게이트는 함수를 이름으로 검색하는 오버헤드가 있으므로, 블루프린트 연동이나 시리얼라이제이션이 필요하지 않은 C++ 내부 시스템에서는 일반 델리게이트를 사용하는 것이 성능상 유리합니다. 특히 매 프레임 호출되는 빈번한 콜백에서는 이 차이가 의미 있을 수 있습니다.

9-7. 멀티캐스트 델리게이트에서 참조 파라미터를 사용하지 않습니다

멀티캐스트 델리게이트에서 참조 파라미터를 사용하여 출력 값을 채우는 패턴은 권장되지 않습니다. 여러 바인딩된 함수가 동일한 참조를 서로 다른 값으로 덮어쓸 수 있어, 최종 결과가 어떤 함수의 값인지 예측할 수 없기 때문입니다.

 

 

델리게이트는 싱글캐스트(1대1 콜백), 멀티캐스트(1대N 이벤트), 다이나믹 싱글캐스트(블루프린트 콜백), 다이나믹 멀티캐스트(블루프린트 이벤트 디스패처)의 네 가지로 구분되며, 각각의 용도가 명확합니다. C++ 내부 시스템에서는 성능이 빠른 일반 델리게이트를, 블루프린트 연동이 필요한 게임플레이 이벤트에는 다이나믹 멀티캐스트 델리게이트를 선택하면 됩니다. 델리게이트의 적절한 활용은 오브젝트 간의 결합도를 낮추고, 유연하게 확장 가능한 이벤트 기반 아키텍처를 구축하는 핵심 기반이 됩니다.

반응형