参考:
单播
虚幻的委托分为三类,分别为单播、多播和动态多播。单播顾名思义就是一次只能绑定一个函数的委托,多播能一次性绑定多个,动态多播即可以在蓝图中进行动态的绑定且可以绑定多个。

单播委托

单播委托是“一对一”的。 它只能绑定一个函数,如果你绑定了第二个,第一个就会被覆盖。

  • 特点: 效率最高,支持返回值(这是多播做不到的)。
  • 适用场景: 需要从一个函数获取结果,或者明确只需要一个回调。
  • 注意: 不能在蓝图中使用(即不能加 BlueprintAssignable)。

实现

新建一个Actor类的DelegateActor类进行测试

定义类型

单播可以定义带参数、返回值和参数+返回值三类委托,如下代码所示,分别定义了这三类单播委托,所有的说明见注释部分,定义的位置在头文件和类之间

1
DECLARE_DELEGATE_[RetVal]_[Param个数]([返回值类型], 委托名, [形参1类型], [形参2类型],...)

添加参数RetVal可以有返回值,只有添加这个参数才能有返回值类型,也只有单播可以加这个
添加Param个数可以指定被委托函数的形参个数,比如一个OneParam,上限为9个,比如NineParams,不写即没有形参

声明委托类型变量

上述定义的可以将其看作是一个类型定义,要真正使用还需要在类中声明该类型的变量,格式为

1
委托名 变量名 // 这里变量名甚至可以和委托名相同,但不要这样,会出问题

定义委托需要绑定的函数(被委托的函数)

这里没什么好强调的,就是定义的函数要和对应的委托保持一致的返回值和参数,没有返回值的委托用void就好
比如

1
2
3
4
void AClassB::HandleScore(int32 NewScore) 
{
// 处理逻辑
}

在构造函数或其他初始函数中进行绑定

有很多种绑定函数的方法,这里说常用的BindUObject
使用委托变量.BindUObject(ObjectPointer, &ClassName::FunctionName)
其中
ObjectPointer为被委托函数的对象的指针
&ClassName::FunctionName:为目标函数的成员指针。必须包含类名空间(例如 &AMyActor::MyHandler)
比如在AClassA 中绑定 AClassB 的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在 ClassA.cpp 中
void AClassA::BeginPlay()
{
Super::BeginPlay();

// 假设 ClassBInstance 是一个指向 AClassB 实例的指针
if (ClassBInstance)
{
// 参数 1: ClassBInstance (执行函数的对象)
// 参数 2: &AClassB::HandleScore (函数地址)
ScoreDelegate.BindUObject(ClassBInstance, &AClassB::HandleScore);
}
}

HandleScore()就要求其形参和返回值与ScoreDelegate的类型定义时一样

执行

执行时,有返回值和没返回值使用的执行函数不同
没返回值使用ExecuteIfBound()

1
委托变量.ExecuteIfBound(实参1, 实参2,...)

而有返回值使用Execute()

1
委托返回值类型 a = 委托变量.Execute(实参1, 实参2,...)

解绑与绑定新的函数

解绑:委托变量.Unbind();
绑定新函数和绑定函数一样用BindUObject

其他绑定函数方法


示例参考这个文章

多播委托

参考

多播委托是“一对多”的。 它可以同时绑定多个函数,触发时所有函数按顺序执行

  • 特点: 没有返回值(void),因为无法确定该返回哪一个函数的结果
  • 适用场景: 广播事件,比如“玩家升级了”,这时 UI、音效、特效系统都需要同时收到通知

实现

定义多播委托

1
DECLARE_MULTICAST_DELEGATE_[Param数量](委托名, [形参1类型], [形参2类型],...)

声明委托类型变量、绑定被委托函数

1
2
3
4
5
6
//多播代理声明
MulDelegate_OneParam MulDelegate_OneParam1;

//多播委托绑定函数定义
void MulDelegate_OneParamFunc1(FString strVal);
void MulDelegate_OneParamFunc2(FString strVal);

绑定&执行

绑定使用委托变量.AddUObject(ObjectPointer, &ClassName::FunctionName)函数可以添加绑定函数,多播函数的特点就如此,可以绑定多个函数,接着按队列触发(先进先出)
执行使用委托变量.Broadcast()

动态多播委托

在 UE5 开发(尤其是涉及到 C++ 与蓝图混编)时,它最常用,这里的“动态”不是指内存分配,而是指 “动态反射”。

  • 普通多播:在编译后,它就是一串硬编码的函数指针地址。
  • 动态多播:它存储的是函数的名字(字符串)。在运行时,它会去 UClass 的元数据(函数列表)里查找这个名字。

实现

定义动态多播委托

1
DECLARE_DYNAMIC_MULTICAST_DELEGATE_[Param个数](委托名, 形参1类型, 形参1名称, 形参2类型, 形参2名称)
  • 必须以 F 开头:委托类型名必须符合 Unreal 命名规范。
  • 必须带参数名:如前所述,宏里必须写成 (类型, 名字)。
  • 变量必须在UPROPERTY里加上 BlueprintAssignable:如果你想在蓝图里看到它,这样可以用bind节点绑定事件
  • 在UPROPERTY里加上BlueprintCallable可以使委托在蓝图里用call节点调用和解绑
    1
    2
    //动态多播委托,区别在于它可以暴露给蓝图,在蓝图中进行事件的绑定
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDynamicMulDelegate, FString, param);//一个参数的动态多播委托,名称一定要F开头不然会编译报错“Delegate type declarations must start with F”
    1
    2
    3
    //动态多播代理变量声明
    UPROPERTY(BlueprintAssignable, BlueprintCallable)
    FDynamicMulDelegate DynamicMulDelegate;

绑定函数/事件

蓝图

动态多播最大的特点就是可以在蓝图中使用BindAssign节点绑定事件,使用Unbind节点解绑事件,当然,被绑定事件也需要有和委托定义时一样的形参

还可以使用Call节点执行委托

这样,将Actor放入场景后运行便会打印文本Hello

cpp内

动态多播只可以用AddDynamicAddUniqueDynamic来绑定函数
使用Broadcast()来执行委托
AddUniqueDynamic将会判断当前函数是否绑定过,若绑定过将不会再次绑定

1
2
DynamicMulDelegate.AddDynamic(this, &ThisClass::MyFunction);
DynamicMulDelegate.AddUniqueDynamic(this, &ThisClass::MySecFunction);

接着使用Broadcast()执行委托

1
DynamicMulDelegate.Broadcast(TEXT("Hello"));

绑定和执行cpp与蓝图可以互通,这就是动态绑定的强大之处