unreal / 2024.09.01

Unreal Engine の 委托

「委托」是一种泛型但类型安全的方式,可在 C++ 对象上调用成员函数。可使用委托动态绑定到任意对象的成员函数,之后在该对象上调用函数,即使调用程序不知对象类型也可进行操作。——虚幻官方文档 委托本质上是基于「观察者模式」的,也就是有「订阅」的行为,在委托中称为「绑定」。

委托」是一种泛型但类型安全的方式,可在 C++ 对象上调用成员函数。可使用委托动态绑定到任意对象的成员函数,之后在该对象上调用函数,即使调用程序不知对象类型也可进行操作。——虚幻官方文档

委托本质上是基于「观察者模式」的,也就是有「订阅」的行为,在委托中称为「绑定」。

要使用委托,首先要用 DECLARE* 宏进行特定委托的类型声明,之后才允许用 Bind*()/Add*() 函数将「订阅者」(一个可调用对象)绑定到委托对象上。这样一旦某些事件触发,就可以通过 Execute()/BroadCast() 通知这些订阅者。

委托的基本类型有三种。

  1. 单播委托
  2. 多播委托
  3. 动态委托

单播委托 & 动态单播委托

单播,顾名思义就是最多只能绑定一个可调用对象,并且不支持反射和序列化。优点是支持返回值。

而「动态」的意思是会在执行时实时根据给定的「函数名」去查找对应的函数(需要用 UFUNCTION() 修饰),因此执行速度很慢。此外,动态单播并不支持反射和序列化。

 1/**
 2 * 声明一个名为 DelegateType 的委托类型
 3 * 允许绑定的函数类型为 void(*)(ParamType1, ParamType2, ...)
 4 * 相当于 using DelegateType = TDelegate<void(ParamType1, ParamType2, ...)>;
 5 * 无返回值
 6 */
 7#define DECLARE_DELEGATE*(DelegateType, ParamType1, ParamType2, ...)
 8
 9/**
10 * 声明动态委托类型
11 * 允许绑定的函数类型为 void(*)(ParamType1 ParamName1, ...)
12 * 相当于定义了一个继承自 TBaseDynamicDelegate 的类
13 * 无返回值
14 */
15#define DECLARE_DYNAMIC_DELEGATE*(DelegateType, ParamType1, ParamName1, ...)
16
17/**
18 * 允许绑定的函数类型为 RetValType(*)(ParamType1, ParamType2, ...)
19 * 相当于 using DelegateType = TDelegate<RetValType(ParamType1, ParamType2, ...)>;
20 * 有返回值
21 */
22#define DECLARE_DELEGATE_RetVal*(RetValType, DelegateType, ParamType1, ParamType2, ...)
23
24/**
25 * 允许绑定的函数类型为 RetValType(*)(ParamType1 ParamName1, ...)
26 * 有返回值
27 */
28#define DECLARE_DYNAMIC_DELEGATE_RetVal*(DelegateType, ParamType1, ParamName1, ...)

多播委托 & 动态多播委托

多播则是允许绑定多个可调用对象,但不支持返回值和反射。

动态多播同样需要用 UFUNCTION() 修饰,但支持反射以及序列化,也就是可以在蓝图中进行绑定,此时需要使用 BlueprintAssignable 修饰符。

 1/**
 2 * 多播委托类型
 3 * 相当于 using DelegateType = TMulticastDelegate<void(ParamType1, ParamType2, ...)>;
 4 */
 5#define DECLARE_MULTICAST_DELEGATE*(DelegateType, ParamType1, ParamType2, ...)
 6
 7/**
 8 * 线程安全多播类型
 9 * 相当于 using DelegateType = TMulticastDelegate<RetValType(ParamType1, ParamType2, ...), FDefaultTSDelegateUserPolicy>;
10 */
11#define DECLARE_TS_MULTICAST_DELEGATE*(DelegateType, ParamType1, ParamType2, ...)
12
13/**
14 * 动态多播类型
15 * 相当于定义了一个继承自 TBaseDynamicMulticastDelegate 的类
16 */
17#define DECLARE_DYNAMIC_MULTICAST_DELEGATE*(DelegateType, ParamType1, ParamName1, ...)

事件

除了上面 7 个宏族,UE 里还有一种声明方式是 DECLARE_EVENT*()。它和多播很像,但指定了 Owner,即只能在 Owner 类的成员函数中调用 BroadCast()

1#define FUNC_DECLARE_EVENT( OwningType, EventName, ReturnType, ... ) \
2	class EventName : public TMulticastDelegate<ReturnType(__VA_ARGS__)> \
3	{ \
4		friend class OwningType; \
5	};

可以看到用了友元类来进行访问权限的控制。

绑定 & 解绑

非动态委托

Bind*() 系用于单播绑定,Add*() 系用于多播绑定。

 1/* 绑定类的静态函数 */
 2MyUnicastDelegate.BindStatic(/* A static function pointer */, /* Parameters */);
 3MyMulticastDelegate.AddStatic(/* &MyClass::MyStaticFunc */, /* ... */);
 4
 5
 6/* 绑定 lambda 函数,可能出现悬垂引用的问题 */
 7BindLambda(/* Lambda */, /* Parameters */);
 8AddLambda(/* [this](...) {...} */, /* ... */);
 9
10
11/* 绑定 Weak lambda 函数,区别在于如果 this 无效不会执行 lambda */
12BindWeakLambda(/* User Object */, /* Lambda */, /* Parameters */);
13AddWeakLambda(/* this */, /* [this](...) {...} */, /* ... */);
14
15
16/* 绑定到继承自 UObject 对象上的某个函数 */
17BindUObject(/* UObject* */, /* A function pointer */, /* Parameters */);
18AddUObject(/* this */, /* &MyClass::MyFunc */, /* ... */);
19
20
21/* 绑定到不继承自 UObject 的对象上的函数,同样可能出现悬垂引用的问题 */
22BindRaw(/* Raw Pointer */, /* A function pointer */, /* Parameters */);
23AddRaw(/* Other */, /* &OtherClass::OtherFunc */, /* ... */);
24
25
26/* 在上面的基础上,传入对象为 Shared Pointer */
27BindSP(/* Shared Pointer */, /* A function pointer */, /* Parameters */);
28AddSP(/* Other.ToSharedRef() */, /* &OtherClass::OtherFunc */, /* ... */);
29
30
31/* 上面的线程安全版本 */
32BindThreadSafeSP(...);
33AddThreadSafeSP(...);
34
35
36/* 基于函数名进行绑定,需要用 UFUNCTION() 修饰 !!开销很大!! */
37BindUFunction(/* User Object */, /* Function name */, /* Parameters */);
38AddUFunction(/* this */, /* "MyFunction" */, /* ... */);
39
40
41/* 单播/动态单播 解除绑定 */
42UnBind();
43
44
45/* 多播/动态多播 解除某个绑定 */
46Remove(/* this */, /* &MyClass::MyFunc */);
47Remove(/* Delegate Handle */);
48
49
50/* 多播/动态多播 解除所有绑定 */
51RemoveAll();
52Clear();

对于单播而言,后续的绑定会覆盖之前的。

动态委托

动态委托绑定的函数必须用 UFUNCTION() 修饰,否则无效。

 1/* 动态单播 */
 2#define BindDynamic( UserObject, FuncName ) \
 3        __Internal_BindDynamic( UserObject, FuncName, \
 4                                STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
 5
 6BindDynamic(/* User Object */, /* A function poiter */);
 7
 8
 9/* 动态多播 */
10#define AddDynamic( UserObject, FuncName ) \
11        __Internal_AddDynamic( UserObject, FuncName, \
12                               STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
13
14AddDynamic(/* this */, /* &MyClass::MyFunc */);
15
16
17/* 动态多播 且去重 */
18#define AddUniqueDynamic( UserObject, FuncName ) \
19        __Internal_AddUniqueDynamic( UserObject, FuncName, \
20                                     STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
21
22AddUniqueDynamic(...);
23
24
25/* 移除 */
26#define RemoveDynamic( UserObject, FuncName ) \
27        __Internal_RemoveDynamic( UserObject, FuncName, \
28                                  STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
29
30RemoveDynamic(...);

这几个其实都是宏定义,而不是类的成员函数(所以 intellisense 没法识别)。

通知

 1/* 单播/动态单播 通知,可能存在函数指针无效的问题,此时会报错 */
 2Execute(/* Parameters */);
 3
 4
 5/* 上面的安全版本,如果函数指针无效则不执行 */
 6ExecuteIfBound(/* Parameters */);
 7
 8
 9/* 多播/动态多播 通知所有订阅者 */
10BroadCast(/* Parameters */);

注意 Execute*() 都可以有返回值,但 BroadCast() 不行。