icon안동민 개발노트

델리게이트 (Delegate)의 개념과 구현


 언리얼 엔진의 델리게이트 시스템은 유연하고 효율적인 이벤트 기반 프로그래밍을 가능하게 합니다.

 이 절에서는 델리게이트의 개념, 유형, 그리고 C++에서의 구현 방법을 살펴보겠습니다.

델리게이트의 개념

 델리게이트는 함수 포인터의 객체 지향적 버전으로, 런타임에 동적으로 함수를 호출할 수 있게 해줍니다.

  1. 타입 안전성
  2. 단일 또는 다중 함수 바인딩
  3. 비동기 프로그래밍 지원
  4. 블루프린트 연동 가능

델리게이트 유형

  1. 단일 캐스트 델리게이트 : 단일 함수만 바인딩 가능
  2. 멀티 캐스트 델리게이트 : 여러 함수를 바인딩하고 순차적으로 호출
  3. 동적 멀티 캐스트 델리게이트 : 멀티 캐스트 + 블루프린트 노출 가능

C++에서 델리게이트 구현

 델리게이트 선언

DECLARE_DELEGATE(FMyDelegate);
DECLARE_DELEGATE_OneParam(FMyDelegateWithParam, int32);
DECLARE_MULTICAST_DELEGATE(FMyMulticastDelegate);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyDynamicMulticastDelegate);

 델리게이트 사용 예시

UCLASS()
class MYGAME_API AMyActor : public AActor
{
    GENERATED_BODY()
 
public:
    // 단일 캐스트 델리게이트
    FMyDelegate OnSingleEvent;
 
    // 멀티 캐스트 델리게이트
    FMyMulticastDelegate OnMultiEvent;
 
    // 동적 멀티 캐스트 델리게이트 (블루프린트 노출)
    UPROPERTY(BlueprintAssignable, Category="Events")
    FMyDynamicMulticastDelegate OnDynamicEvent;
 
    void SomeFunction();
};

 델리게이트 바인딩

void AMyActor::SomeFunction()
{
    // 멤버 함수 바인딩
    OnSingleEvent.BindUObject(this, &AMyActor::HandleSingleEvent);
 
    // 람다 함수 바인딩
    OnMultiEvent.AddLambda([this]() 
    {
        UE_LOG(LogTemp, Log, TEXT("Lambda function called"));
    });
 
    // 정적 함수 바인딩
    OnMultiEvent.AddStatic(&StaticHandlerFunction);
}
 
void AMyActor::HandleSingleEvent()
{
    UE_LOG(LogTemp, Log, TEXT("Single event handled"));
}
 
static void StaticHandlerFunction()
{
    UE_LOG(LogTemp, Log, TEXT("Static function called"));
}

 델리게이트 실행

// 단일 캐스트 델리게이트 실행
if (OnSingleEvent.IsBound())
{
    OnSingleEvent.Execute();
}
 
// 멀티 캐스트 델리게이트 실행
OnMultiEvent.Broadcast();
 
// 동적 멀티 캐스트 델리게이트 실행
OnDynamicEvent.Broadcast();

 언바인딩

// 특정 함수 언바인딩
OnMultiEvent.Remove(this, &AMyActor::HandleMultiEvent);
 
// 모든 바인딩 제거
OnMultiEvent.Clear();

델리게이트와 이벤트의 차이점

  1. 델리게이트 : 클래스 외부에서 호출 가능
  2. 이벤트 : 선언된 클래스 내부에서만 호출 가능 (캡슐화 강화)
DECLARE_EVENT(FMyClass, FMyEvent);
 
class FMyClass
{
public:
    FMyEvent& OnMyEvent() { return MyEvent; }
 
private:
    FMyEvent MyEvent;
};

템플릿 기반 델리게이트

 템플릿을 사용하여 다양한 시그니처의 델리게이트를 생성할 수 있습니다.

template<typename T>
class TMyTemplateDelegate
{
public:
    DECLARE_DELEGATE_RetVal_OneParam(bool, FTemplateDelegate, T);
    FTemplateDelegate OnTemplateEvent;
};
 
TMyTemplateDelegate<int32> IntDelegate;
TMyTemplateDelegate<FString> StringDelegate;

블루프린트 연동

 동적 멀티 캐스트 델리게이트를 사용하여 블루프린트와 연동할 수 있습니다.

UCLASS()
class MYGAME_API AMyActor : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(BlueprintAssignable, Category="Events")
    FMyDynamicMulticastDelegate OnBlueprintEvent;
 
    UFUNCTION(BlueprintCallable, Category="Events")
    void TriggerEvent();
};
 
void AMyActor::TriggerEvent()
{
    OnBlueprintEvent.Broadcast();
}

 블루프린트에서는 이 이벤트를 구독하고 처리할 수 있습니다.

느슨한 결합 설계

 델리게이트를 사용하면 시스템 간의 결합도를 낮출 수 있습니다.

UCLASS()
class MYGAME_API AWeapon : public AActor
{
    GENERATED_BODY()
 
public:
    FOnWeaponFiredSignature OnWeaponFired;
 
    void Fire();
};
 
void AWeapon::Fire()
{
    // 발사 로직
    OnWeaponFired.Broadcast();
}
 
// 다른 클래스에서
WeaponInstance->OnWeaponFired.AddUObject(this, &AMyCharacter::HandleWeaponFired);

 이 방식으로 Weapon 클래스는 Character 클래스에 대해 알 필요가 없습니다.

성능 고려사항

  1. 델리게이트 호출은 직접 함수 호출보다 약간의 오버헤드가 있습니다.
  2. 자주 호출되는 핫 경로에서는 직접 함수 호출을 고려하세요.
  3. 멀티 캐스트 델리게이트의 경우, 등록된 함수 수에 비례하여 실행 시간이 증가합니다.

메모리 관리 주의점

  1. 객체 소멸 시 델리게이트 언바인딩 처리
AMyActor::~AMyActor()
{
    if (OnMultiEvent.IsBound())
    {
        OnMultiEvent.Clear();
    }
}
  1. 약한 포인터 사용으로 댕글링 포인터 방지
FWeakObjectPtr WeakThis(this);
OnMultiEvent.AddLambda([WeakThis]()
{
    if (WeakThis.IsValid())
    {
        WeakThis->SomeFunction();
    }
});

Best Practices

  1. 명확한 명명 규칙 사용
DECLARE_DELEGATE(FOnHealthChangedDelegate);
  1. 델리게이트 시그니처를 위한 typedef 사용
DECLARE_DELEGATE_TwoParams(FOnItemAddedSignature, UItem*, int32);
typedef FOnItemAddedSignature::FDelegate FOnItemAddedDelegate;
  1. 안전한 실행을 위한 헬퍼 함수 사용
template<typename T>
void SafeExecuteDelegate(T& Delegate)
{
    if (Delegate.IsBound())
    {
        Delegate.Execute();
    }
}
  1. 이벤트 기반 시스템 설계 시 델리게이트 활용
UCLASS()
class MYGAME_API UEventManager : public UObject
{
    GENERATED_BODY()
 
public:
    DECLARE_MULTICAST_DELEGATE_OneParam(FOnLevelCompleteSignature, int32);
    FOnLevelCompleteSignature OnLevelComplete;
};

 델리게이트는 언리얼 엔진에서 유연하고 강력한 이벤트 처리 메커니즘을 제공합니다. 적절히 사용하면 코드의 모듈성과 확장성을 크게 향상시킬 수 있습니다. 그러나 과도한 사용은 코드의 복잡성을 증가시킬 수 있으므로, 상황에 맞게 적절히 사용하는 것이 중요합니다.

 델리게이트를 통한 느슨한 결합 설계는 특히 대규모 프로젝트에서 유지보수성을 크게 개선할 수 있습니다. 성능과 메모리 관리에 주의를 기울이면서, 델리게이트를 활용하여 더 유연하고 확장 가능한 게임 시스템을 구축할 수 있습니다.