前言
消息系统允许对象之间进行通信而不需要知道对象的具体类型,只需要接受消息的对象完成注册即可接收从各个地方发送来的消息从而执行相关的逻辑。
与UE中的委托类似,但是使用委托在绑定时仍需要知道具体的对象,而消息则不需要,所以使用消息能更进一步完成解耦。
基本的实现方式
可以采取与委托类似的方式,不同的是可以将绑定的信息存储在一个容器中,当消息被发送时就从容器中查找相应的委托并执行逻辑。
定义最基本的结构体
首先在这个容器中存储了一个委托,其次需要一个唯一的标识符,所以相应的应该还有一个ID。
消息的最基本结构体定义完成后,还需要知道消息的具体类型,采用FGameplayTag作为消息类型的标识符。
定义委托:
1 | DECLARE_DYNAMIC_DELEGATE(FMessageDelegate); |
所以消息结构体的基本构成为:
1 | struct FGameplayMessageListenerHandle |
消息的存储容器
有了消息的基本结构体后,就需要一个容器来存放这些消息。之前定义了FGameplayTag作为消息类型,一个消息同时有一组接收者,因而采取map的形式维护消息类型与接收者之间的映射关系。
1 | struct FGameplayMessageHandleWarpper |
每个消息的唯一标识ID
使用一个静态成员变量作为标识ID,没当调用注册函数时就为其注册的Handle分配一个HandleID。
消息本身应提供的方法
同时消息本身应该具有合法性判断、解绑、委托执行的方法:
1 | struct FGameplayMessageListenerHandle |
消息系统的实现
消息系统在整个游戏中明显应该只存在一份,所以采用UE的子系统来实现,同时让消息结构体中持有一份该系统的弱引用:
1 | class UMessageSubsystem:public UGameInstanceSubsystem |
对于C++的实现
消息的注册
我们提供一个模板方法来完成对消息的注册,为了完成消息的注册,我们需要一些注册的基本信息:注册的消息类型、注册者以及注册的委托函数,所以需要三个基本参数:FGameplayTag、UserObject、Function。
最终提供一个静态方法完成注册:
1 | template<typename UserClass> |
消息的执行
在消息执行时只需要传入一个消息类型即可完成消息的发送,同样将其定义为静态方法:
1 | void BroadcastMessage(UObject* WorldContextObject, FGameplayTag Channel) |
消息的解绑
直接对对象持有的消息本身进行解绑,之前我们对消息结构体中定义了Unregister方法,现在只需要实现该方法即可:
1 | void FGameplayMessageListenerHandle::Unregister() |
对于蓝图实现
蓝图与C++实现最大的区别在于无法直接使用FGameplayMessageHandle这个结构体,在蓝图与C++的交互中,结构体会以值传递的方式传递,即使定义了以引用的方式传递,在蓝图中的结构体进入C++时仍然会拷贝出一个副本,此时无法保证操作的一致性;而类类型在蓝图与C++的交互中是以引用的方式传递,此时可以保证操作的一致性,所以需要对消息结构在做一层类封装。
1 | class UGameplayMessageAction: public UObject |
蓝图消息的注册
同样提供静态方法进行注册,不同的是这次不传入函数名而是直接传入一个事件引脚,当发送消息时直接通过蓝图事件来执行,此时便不再需要传入UserObject:
1 | UFUNCTION(BlueprintCallable, Category = "Message") |
蓝图消息的执行与C++消息的执行方式相同,直接调用BroadcastMessage即可。
蓝图消息的解绑
在注册消息时我们使用一个MessageAction接收返回值,并使用它的完成消息解绑:
1 | void UGameplayMessageAction::Unregister() |
完整的代码
头文件部分:
1 | DECLARE_DYNAMIC_DELEGATE(FMessageDelegate); |
源文件部分:
1 |
|
测试
使用默认的主角类进行测试,在C++中提供以下函数并绑定一些按键进行:
1 | FGameplayMessageListenerHandle TestListenerHandler; |
在蓝图中则直接可以搜索K2_RegisterGameplayMessageListerner和BroadcastMessage进行绑定和广播,直接调用Action的Unregister()函数即可取消监听。
后续
后续会增加消息的参数传递功能,使其能够携带任意数量的参数进行传递。
- 本文作者: KongXinQing
- 本文链接: https://13114987559.github.io/2025/03/31/essay/UE消息系统/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!