나만의 작은 도서관
[TIL][C++] 250725 MMO 서버 개발 66일차: Delegate에 대해서…, 언리얼 Delegate 종류 및 사용법 본문
Today I Learn
[TIL][C++] 250725 MMO 서버 개발 66일차: Delegate에 대해서…, 언리얼 Delegate 종류 및 사용법
pledge24 2025. 7. 25. 21:48주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다.
Delegate에 대해서…
게임에서 발생하는 수많은 입력 이벤트들은 어떻게 감지할까?
- 게임에서 사용자는 원할 때 버튼을 클릭하거나 키보드를 누를 수 있다. 이러한 입력 이벤트들이 발생하면 게임은 이에 대응하는 기능을 실행하고, 사용자에게 실행의 결과를 보여줘야 한다.
- 현대의 게임 엔진들은 이러한 이벤트를 이벤트 기반 처리(Event-driven) 방식으로 처리한다. 이벤트 기반 처리 방식이란, 운영체제나 입력 시스템에서 이벤트 발생을 감지하고, 감지된 이벤트를 이벤트 큐에 push 하는 방식이다. 게임은 매 프레임마다 이벤트 큐를 확인하고, 쌓인 이벤트들을 처리한다.
감지한 다음에는? 미리 등록해 둔 Delegate를 실행!
- 이벤트가 감지되었다면, 해당 이벤트 발생 시 실행해야 하는 작업을 진행한다. 예를 들어, 아이템 구매 버튼 클릭 이벤트가 감지되었다면, 아이템 구매 프로세스를 실행한다.
- 그렇다면 아이템 구매 버튼 클릭 시 실행해야 할 프로세스는 게임 시스템이 어떻게 알려줄까? 바로 감지할 이벤트를 등록했을 때 같이 넘겨준 콜백 함수가 아이템 구매 프로세스가 된다.
- 콜백 함수는 함수 포인터 형태로 전달되며, 언리얼에선 이를 델리게이트(Delegate)라고 부른다.
이벤트는 입력만 있는 것이 아니다.
- 입력뿐만이 아니다. 시스템에서 조건이 만족했을 때 발동하는 기능인 경우 이벤트 방식을 활용할 수 있다. 즉,
- 감지할 이벤트를 정의하고,
- 이벤트가 감지되었을 때 실행할 프로세스도 정의한 다음,
- 이를 묶어서(바인딩해서) 이벤트를 등록한다.
- 이렇게 이벤트를 등록하고 나면, 이벤트 발동 조건을 만족해 트리거(trigger)된다면, 그대로 프로세스가 실행한다.
- (참고로, 운영체제나 시스템을 반드시 거쳐야만 이벤트를 트리거 시킬 수 있는 상황이 아니라면, 개발자가 임의로 조건을 확인한 다음 트리거 시킬 수도 있다(ex. Invoke함수)
언리얼 Delegate 종류
- 언리얼에서는 총 4가지 종류의 Delegate가 있다. 각각은 아래와 같다.
싱글 캐스트 가장 기본적인 Delegate로 함수 1개를 바인드 하여 사용합니다.
| 멀티 캐스트 | 싱글 케스트와 동일하지만 여러 함수를 바인드 할 수 있습니다. |
| 이벤트 | 멀티 케스트와 동일하지만 전역으로 설정할 수 없어 외부 클래스에서 추가 델리게이트 선언이 불가능합니다. |
| 다이나믹 | 다이나믹은 싱글과, 멀티 두개다 존재하며 다이나믹 델리게이트는 직렬화(Serialize)화 되어 블루프린트에서 사용 가능합니다. |
싱글 캐스트/멀티 캐스트
- 이벤트가 트리거 되면, 등록한 프로세스가 실행된다고 했다. 이때 프로세스를 구성하는 델리게이트가 하나면 싱글 캐스트, 여러 개면 멀티 캐스트라고 부른다.
- 예를 들어, 캐릭터가 사망했을 때 트리거되는 이벤트를 등록했고, 어떤 캐릭터가 사망했다고 가정해 보자. 이때 프로세스로 캐릭터 사망 모션 발생 함수만 등록해 두었으면 “싱글 캐스트”, 캐릭터 사망 모션 함수 → 캐릭터 사망 오디오 함수 → 아이템 드롭 함수와 같이 여러 함수를 등록해 두었다면 “멀티 캐스트”가 된다.
이벤트
- 멀티캐스트와 기능은 똑같지만, 접근 제어에 제한을 두어 사용처를 보다 확실하게 설정한 방식이다.
다이나믹
- 일반 델리게이트에 다이나믹 속성을 부여한 델리게이트. 싱글 캐스트/멀티 캐스트 둘 다 가능하다. 다이나믹 속성을 부여받은 델리게이트는 에디터에서 사용가능하며, 1) 블루프린트 연동, 2) 에디터에서 바인딩 설정 등이 가능해진다.
- 대충 UPROPERTY로 노출해야 하는 경우에 사용하면 된다.
언리얼 Delegate 사용법
언리얼에서 Delegate는 매크로 함수로 선언한다.
- 조금 특이하다고 생각한 부분인데, 델리케이트는 키워드가 따로 있는 게 아니라 매크로 함수로 선언한다. 선언 포맷은 아래와 같다.
싱글 캐스트 델리게이트 선언 - 딱 하나의 함수 포인트만!
- 참고로, 델리게이트 선언 시 함수 이름 인자는 앞에 접두사 ‘F’가 추가된 상태로 전달해야 한다.
// Num개의 인자를 가진 경우. (반환값 없음)
DECLARE_DELEGATE_<Num>Params( DeleagteName, Param1Type, Param2Type, ...)
// Num개의 인자를 가진 경우. (반환값 있음)
DECLARE_DELEGATE_<Num>Params( RetValType, DelegateName, Param1Type, Param2Type...)
// ex. 인자 0개, 반환값 X
// void (*Func1)();
DECLARE_DELEGATE(FFunc1);
// ex. 인자 1개, 반환값 X
// void (*Func2)(int x);
DECLARE_DELEGATE_OneParam(FFunc2, int)
// ex. 인자 2개(복수형이라 Param 뒤에 s가 붙음), 반환값 X
// void (*Func3)(int x, float y);
DECLARE_DELEGATE_TwoParams(FFunc3, int, float);
// ex. 인자 0개, 반환값 O
// bool (*Func1)();
DECLARE_DELEGATE(bool, FFunc1);
// ex. 인자 1개, 반환값 O
// bool (*Func2)(int x);
DECLARE_DELEGATE_OneParam(bool, FFunc2, int)
// ex. 인자 2개(복수형이라 Param 뒤에 s가 붙음), 반환값 O
// bool (*Func3)(int x, float y);
DECLARE_DELEGATE_TwoParams(bool, FFunc3, int, float);
멀티 캐스트 델리게이트 선언 - 여러 개의 함수 포인트 등록 가능!
- 싱글 캐스트와 포맷을 비슷하다. 매크로 함수 이름이 “DECLARE_DELEGATE”에서 중간에 “MULTICAST”가 들어간 “DECLARE_MULTICAST_DELEGATE”가 되었을 뿐이다.
// Num개의 인자를 가진 경우. (반환값 없음)
DECLARE_MULTICAST_DELEGATE<Num>Params( DeleagteName, Param1Type, Param2Type, ...)
// Num개의 인자를 가진 경우. (반환값 있음)
DECLARE_MULTICAST_DELEGATE<Num>Params( RetValType, DelegateName, Param1Type, Param2Type...)
// ex. 인자 0개, 반환값 X
// void (*Func1)();
DECLARE_MULTICAST_DELEGATE(FFunc1);
// ex. 인자 1개, 반환값 X
// void (*Func2)(int x);
DECLARE_MULTICAST_DELEGATE_OneParam(FFunc2, int)
// ex. 인자 2개(복수형이라 Param 뒤에 s가 붙음), 반환값 X
// void (*Func3)(int x, float y);
DECLARE_MULTICAST_DELEGATE_TwoParams(FFunc3, int, float);
// ex. 인자 0개, 반환값 O
// bool (*Func1)();
DECLARE_MULTICAST_DELEGATE(bool, FFunc1);
// ex. 인자 1개, 반환값 O
// bool (*Func2)(int x);
DECLARE_MULTICAST_DELEGATE_OneParam(bool, FFunc2, int)
// ex. 인자 2개(복수형이라 Param 뒤에 s가 붙음), 반환값 O
// bool (*Func3)(int x, float y);
DECLARE_MULTICAST_DELEGATE_TwoParams(bool, FFunc3, int, float);
다이나믹 델리게이트 선언 - 블루프린트에서 접근 가능!
- 다이나믹 속성이 추가된 델리게이트. 기존 싱글 캐스트, 멀티 캐스트의 매크로 함수 이름에서 DYNAMIC만 추가하면 된다.
- 다이나믹은 일반과 달리, 매개변수의 이름을 인자로 추가로 전달해야한다. 전달된 인자는 블루프린트의 노드의 매개변수 이름을 표시할때 사용한다.
DECLARE_DYNAMIC_DELEGATE()
DECLARE_DYNAMIC_MULTICAST_DELEGATE<Num>Params(DeleagteName, Param1Type, Param1Name, Param2Type, Param2Name, ...)
델리게이트에 함수 바인딩 방법
- 델리게이트는 그저 함수 시그니처를 정의했을 뿐, 등록을 안 하면 빈 껍데기에 불과하다. 함수 포인터를 쓸 때도 정의한 함수를 집어넣어야 비로소 의미가 생기니, 델리게이트도 함수를 집어넣는, 즉, 바인딩하는 작업을 해야 비로소 의미가 생겨난다. 바인딩하는 방법은 아래와 같다.
단일 캐스트 델리게이트
DECLARE_DELEGATE_OneParam(FMyDelegate, int32);
FMyDelegate MyDelegate;
// 바인딩 함수들
MyDelegate.BindUObject(this, &AMyActor::MyFunction); // UObject 멤버 함수를 바인딩
MyDelegate.BindStatic(&MyClass::StaticFunction); // 정적 함수를 바인딩
MyDelegate.BindLambda([](int32 Value) { /* 람다 함수 */ }); // 람다를 바인딩
MyDelegate.BindWeakLambda(this, [this](int32 Value) { }); // Weak 람다를 바인딩 (객체 안전)
MyDelegate.BindUFunction(this, FName("MyFunction")); // UFunction 이름으로 바인딩
MyDelegate.BindSP(SharedPtr, &MyClass::MyFunction); // SharedPtr 객체 함수로 바인딩
// 실행
MyDelegate.Execute(100);
MyDelegate.ExecuteIfBound(100); // 바인딩된 경우에만 실행
// 해제
MyDelegate.Unbind();
멀티 캐스트 델리게이트
DECLARE_MULTICAST_DELEGATE_OneParam(FMyMulticastDelegate, int32);
FMyMulticastDelegate MyMulticastDelegate;
// 바인딩 함수들 (모두 FDelegateHandle 반환)
FDelegateHandle Handle1 = MyMulticastDelegate.AddUObject(this, &AMyActor::MyFunction);
FDelegateHandle Handle2 = MyMulticastDelegate.AddStatic(&MyClass::StaticFunction);
FDelegateHandle Handle3 = MyMulticastDelegate.AddLambda([](int32 Value) { });
FDelegateHandle Handle4 = MyMulticastDelegate.AddWeakLambda(this, [this](int32 Value) { });
FDelegateHandle Handle5 = MyMulticastDelegate.AddUFunction(this, FName("MyFunction"));
FDelegateHandle Handle6 = MyMulticastDelegate.AddSP(SharedPtr, &MyClass::MyFunction);
// 실행
MyMulticastDelegate.Broadcast(100); // 모든 바인딩된 함수 실행
MyMulticastDelegate.BroadcastIfBound(100); // 바인딩된 것이 있을 때만 실행
// 개별 해제
MyMulticastDelegate.Remove(Handle1);
MyMulticastDelegate.RemoveAll(this); // 특정 객체의 모든 바인딩 해제
// 전체 해제
MyMulticastDelegate.Clear();
다이나믹 델리게이트
- 다이나믹 델리게이트의 경우, 일반 델리게이트에서 사용 가능한 몇몇 함수들을 사용할 수 없다.
// =======싱글 캐스트 다이나믹 델리게이트======
DECLARE_DYNAMIC_DELEGATE_OneParam(FMyDynamicDelegate, int32, Value);
FMyDynamicDelegate MyDynamicDelegate;
// 바인딩 함수들
MyDynamicDelegate.BindDynamic(this, &AMyActor::MyFunction); // 다이나믹 바인딩
MyDynamicDelegate.BindUFunction(this, FName("MyFunction")); // UFunction 이름으로
MyDynamicDelegate.BindUObject(this, &AMyActor::MyFunction); // UObject 바인딩
// 실행
MyDynamicDelegate.Execute(100);
MyDynamicDelegate.ExecuteIfBound(100);
// 해제
MyDynamicDelegate.Unbind();
// =======멀티 캐스트 다이나믹 델리게이트======
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyDynamicMulticastDelegate, int32, Value);
FMyDynamicMulticastDelegate MyDynamicMulticastDelegate;
// 바인딩 함수들
FDelegateHandle Handle1 = MyDynamicMulticastDelegate.AddDynamic(this, &AMyActor::MyFunction);
FDelegateHandle Handle2 = MyDynamicMulticastDelegate.AddUFunction(this, FName("MyFunction"));
FDelegateHandle Handle3 = MyDynamicMulticastDelegate.AddUObject(this, &AMyActor::MyFunction);
// 실행
MyDynamicMulticastDelegate.Broadcast(100);
MyDynamicMulticastDelegate.BroadcastIfBound(100);
// 해제
MyDynamicMulticastDelegate.RemoveDynamic(this, &AMyActor::MyFunction);
MyDynamicMulticastDelegate.Remove(Handle1);
MyDynamicMulticastDelegate.RemoveAll(this);
MyDynamicMulticastDelegate.Clear();
바인딩 함수 요약표
| 델리게이트 타입 | 바인딩 함수 접두사 | 실행 함수 | 해제 함수 |
| Single | Bind~ | Execute/ExecuteIfBound | Unbind |
| Multicast | Add~ | Broadcast/BroadcastIfBound | Remove/RemoveAll/Clear |
| Dynamic Single | BindDynamic/BindUFunction | Execute/ExecuteIfBound | Unbind |
| Dynamic Multicast | AddDynamic/AddUFunction | Broadcast/BroadcastIfBound | RemoveDynamic/Remove/Clear |
델리게이트 종류에 따른 각 바인딩 함수 사용 가능 여부 정리표
| 바인딩 함수 | Regular Delegate | Dynamic Delegate | 설명 |
| BindSP/AddSP | ✅ | ❌ | SharedPtr 객체 바인딩 |
| BindRaw/AddRaw | ✅ | ❌ | Raw 포인터 바인딩 |
| BindThreadSafeSP/AddThreadSafeSP | ✅ | ❌ | Thread-Safe SharedPtr |
| BindLambda/AddLambda | ✅ | ❌ | 람다 함수 |
| BindWeakLambda/AddWeakLambda | ✅ | ❌ | Weak 람다 |
| BindStatic/AddStatic | ✅ | ❌ | 정적 함수 |
| BindUObject/AddUObject | ✅ | ✅ | UObject 멤버 함수 |
| BindDynamic/AddDynamic | ❌ | ✅ | 다이나믹 바인딩 |