일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- virtual function
- operator new
- 다형성
- 스마트 포인터
- implicit conversion
- 암시적 변환
- c++
- 티스토리챌린지
- 영화
- effective stl
- lua
- 참조자
- 오블완
- resource management class
- reference
- 반복자
- 메타테이블
- UE4
- 비교 함수 객체
- Vector
- 언리얼
- Effective c++
- exception
- more effective c++
- 루아
- 게임
- 예외
- 상속
- 영화 리뷰
- Smart Pointer
- Today
- Total
스토리텔링 개발자
[UE4] 델리게이트(Delegate) 본문
델리게이트란?
자주 쓰는 프로그래밍적 기법 중에는 콜백이라는 것이 있죠.
어떤 함수를 직접 호출하는 게 아니라, 로직에 따라 원하는 타이밍에 호출할 수 있게 하는 기법입니다.
사실 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);
}
'개발 > 언리얼 엔진' 카테고리의 다른 글
[UE4] 컴포넌트(Component) (0) | 2021.07.01 |
---|---|
[UE4] 콜리전(Collision) (0) | 2021.06.24 |
[UE4] 트레이스(Trace) (0) | 2021.06.22 |
[UE4] 순운동학, 역운동학(Forward Kinematics, Inverse Kinematics) (0) | 2021.06.21 |
[UE4] 직렬화(Serialization) (0) | 2021.06.08 |