스토리텔링 개발자

[UE4] 델리게이트(Delegate) 본문

개발/언리얼 엔진

[UE4] 델리게이트(Delegate)

김디트 2021. 6. 7. 16:29
728x90

델리게이트란?

자주 쓰는 프로그래밍적 기법 중에는 콜백이라는 것이 있죠.

어떤 함수를 직접 호출하는 게 아니라, 로직에 따라 원하는 타이밍에 호출할 수 있게 하는 기법입니다.

 

사실 C++는 언어적으로 깔끔하게 콜백을 지원하지 않습니다.

물론 '함수 포인터'를 사용한다면 콜백을 처리할 수 있습니다.

하지만 이는 함수의 포인터를 직접적으로 다루므로 댕글링 포인터 등의 문제에서 자유롭지 못합니다.

즉, 안전성이 많이 떨어집니다.

 

그래서 보통 C++ 베이스의 엔진들은 '델리게이트'라는 개념을 따로 구현하고 있습니다.

이는 콜백을 간단하고 안정적인 방법으로 사용할 수 있게 해줍니다.

 

아마 C# 등의 언어를 이미 접했다면 델리게이트의 개념에 익숙하시겠죠.

 

델리게이트(delegate)는 위임이라는 뜻을 가지고 있습니다.

그렇다면 아래와 같이 정리할 수 있을까요.

 

'함수의 기능특정 객체에게 위임한다.'

 

델리게이트에 함수의 기능을 위임한다.

 

그렇다면 델리게이트를 사용하면 함수 포인터에 비해 어떤 점이 유리할까요?

 

델리게이트는 객체로 구현되므로 객체가 가지는 장점들이 그대로 장점이 되죠.

즉,  일반적인 객체를 다루듯이 콜백을 처리할 수 있게 됩니다.

 

 

 

델리게이트의 종류

언리얼에서는 아래와 같은 종류의 델리게이트들을 제공합니다.

 

델리게이트

 

    1) 싱글 캐스트 : 하나의 함수를 바인딩 할 수 있습니다.


    2) 멀티 캐스트 : 여러 개의 함수를 바인딩 할 수 있습니다.

         2-1) 이벤트 : 멀티 캐스트와 동일하지만 특정 함수들은 델리게이트의 소유주만 사용할 수 있습니다.

 

- 직렬화를 지원하지 않습니다.

- 그러므로 직렬화 기반인 블루프린트 오브젝트 함수를 바인딩 할 수 없습니다.

- C++의 함수 포인터를 기반으로 바인딩합니다.

 

 

다이나믹 델리게이트

 

    1) 싱글 캐스트 : 하나의 함수를 바인딩 할 수 있습니다.

 

    2) 멀티 캐스트 : 여러 개의 함수를 바인딩 할 수 있습니다.

 

- 직렬화(Serialize)가 가능합니다.

- 그러므로 블루프린트 오브젝트의 함수를 바인딩 할 수 있습니다.

- 함수의 이름을 기반으로 바인딩합니다.

 

 

 

델리게이트 선언

 

델리게이트

 

델리게이트를 사용하기 위해서는 매크로를 사용한 선언이 필요합니다.

언리얼 엔진은 이를 위해 다양한 형태의 매크로를 제공하고 있습니다.

 

매크로가 지원하는 요소

  • 리턴이 가능한 함수 형태 바인딩 가능한 델리게이트 선언
  • 함수 파라미터(최대 8개까지만)를 가지는 함수 형태 바인딩 가능한 델리게이트 선언
  • 호출 시 "페이로드"(payload, 유상) 변수를 최대 4개까지 전달
  • 'const' 로 선언된 함수 형태(DECLARE_DYNAMIC_DELEGATE_Const 형태로 지원했으나 지원하지 않게 된 것 같네요.)
void Function()
DECLARE_DELEGATE( DelegateName )
void Function( <Param1> )
DECLARE_DELEGATE_OneParam( DelegateName, Param1Type )
void Function( <Param1>, <Param2> )
DECLARE_DELEGATE_TwoParams( DelegateName, Param1Type, Param2Type )
void Function( <Param1>, <Param2>, ... )
DECLARE_DELEGATE_<Num>Params( DelegateName, Param1Type, Param2Type, ... )
<RetVal> Function()
DECLARE_DELEGATE_RetVal( RetValType, DelegateName )
<RetVal> Function( <Param1> )
DECLARE_DELEGATE_RetVal_OneParam( RetValType, DelegateName, Param1Type )
<RetVal> Function( <Param1>, <Param2> )
DECLARE_DELEGATE_RetVal_TwoParams( RetValType, DelegateName, Param1Type, Param2Type )
<RetVal> Function( <Param1>, <Param2>, ... )
DECLARE_DELEGATE_RetVal_<Num>Params( RetValType, DelegateName, Param1Type, Param2Type, ... )

 

다이나믹 델리게이트

 

위 매크로에서 다음 부분을 치환하여 사용합니다.

DECLARE_DELEGATE -> DECLARE_DYNAMIC_DELEGATE

 

 

멀티캐스트 델리게이트

 

각각을 치환하여 사용합니다.

 

일반 멀티캐스트 델리게이트

DECLARE_DELEGATE -> DECLARE_MULTICAST_DELEGATE

 

다이나믹 멀티캐스트 델리게이트

DECLARE_DYNAMIC_DELEGATE -> DECLARE_DYNAMIC_MULTICAST_DELEGATE

 

 

 

델리게이트 바인드

멀티 캐스트의 경우 'bind'를 'add'로 치환하여 사용하면 됩니다.

BindStatic()
raw C++ static 함수를 바인딩합니다.
BindRaw()
Raw C++ 객체와 함수를 델리게이트에 바인딩합니다.
날 포인터는 어떠한 종류의 레퍼런스도 사용하지 않아, 호출 시 안전성이 보장되지 않습니다.
즉, 참조한 raw 객체가 올바르지 않은 경우 크래시가 발생합니다.
BindSP()
공유 포인터-기반 멤버 함수 델리게이트에 바인딩합니다.
공유 포인터 델리게이트는 약한 레퍼런스를 유지합니다.
ExecuteIfBound()로 레퍼런스가 유효한지 확인 후 실행할 수 있습니다.
BindUObject()
UObject 기반 멤버 함수를 델리게이트에 바인딩합니다. UObject 델리게이트는 약한 레퍼런스를 유지합니다. ExecuteIfBound()로 레퍼런스가 유효한지 확인 후 실행할 수 있습니다.
UnBind()
델리게이트 바인딩을 해제합니다.

 

 

 

다이내믹 델리게이트 바인딩

BindDynamic()
직렬화를 지원하는 함수를 바인딩합니다.

 

 

 

델리게이트 예제

선언
// 델리게이트 선언
DECLARE_DELEGATE(FOnEvent);
DECLARE_DELEGATE_OneParams(FOnOneParamEvent, UWidget /*Widget*/);
DECLARE_DYNAMIC_DELEGATE(FOnDynamicEvent);
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnDynamicOneParamEvent, UWidget /*Widget*/);

// 선언
class FTestClass
{
public:
    void OnTestFunc() {}
};

UCLASS()
class UTestObject : public UObject
{
    GENERATED_BODY()
public:
    void BindTest();
    void ExcuteTest();
    
public:
    // 바인드 타겟 함수들 선언
    static void OnTestFuncStatic() {}
    UFUNCTION() void OnTestFuncWithBP() {}
    void OnTestFunc() {}
    
    UFUNCTION() void OnOneParamTestFunc(UWidget In) {}
    
public:
    FOnEvent OnTest;
    FOnOneParamEvent OnOneParamTest;
    
    FOnDynamicEvent OnDynamicTest;
    FOnDynamicOneParamEvent OnDynamicOneParamTest;
    
private:
	TSharedPtr Test;
}
델리게이트 바인딩
// 바인딩
void UTestObject::BindTest()
{
    Test = MakeShared();
    
    //// 델리게이트 종류 별 Bind
    // UObject 함수 바인딩
    OnTest.BindUObject(this, &UTestObject::OnTestFuncWithBP);
    // 다이내믹 델리게이트가 아니면 직렬화 되지 않은 함수도 Bind 가능하다
    OnTest.BindUObject(this, &UTestObject::OnTestFunc);
    // static 함수 바인딩
    OnTest.BindStatic(&ULBuffComponent::OnTestFuncStatic);
    // 이름 탐색이 가능한 직렬화 된 함수 바인딩
    OnTest.BindUFunction(this, FName(TEXT("OnTestFuncWithBP"))); 
    // Raw 오브젝트 바인딩
    OnTest.BindRaw(&Test.Get(), &FTestClass::OnTestFunc); 
    // SharedPtr를 통해 안정성을 더한 Raw 오브젝트 바인딩
    OnTest.BindSP(Test, &FTestClass::OnTestFunc);
    
    //// 다이내믹 델리게이트 종류 별 Bind
    // 직렬화된 UObject 함수 바인딩
    OnDynamicTest.BindDynamic(this, &UTestObject::OnTestFuncWithBP);
    // 바인딩 함수가 직렬화를 지원하지 않으면 Assert가 발생한다.
    // OnDynamicTest.BindDynamic(this, &UTestObject::OnTestFunc);
    // 이름 탐색이 가능한 UFUNCTION() 함수 바인딩
    OnDynamicTest.BindUFunction(this, FName(TEXT("OnTestFuncWithBP")));
    
    // 멀티 캐스트 델리게이트들 Bind
    OnOneParamTest.BindUObject(this, &UTestObject::OnOneParamTestFunc);
    OnDynamicOneParamTest.BindDynamic(this, &UTestObject::OnOneParamTestFunc);
}
델리게이트 사용
// 호출
void UTestObject::ExcuteTest()
{
    UWidget* PassingWidget = nullptr;
    
    // 일반 델리게이트
    OnTest.Broadcast();
    OnOneParamTest.Broadcast(PassingWidget);
    
    // 다이나믹 델리게이트
    OnDynamicTest.ExecuteIfBound();
    OnDynamicOneParamTest.ExecuteIfBound(PassingWidget);
}
728x90
Comments