Async Message · C++ 收发与解绑

按取得系统、登记监听、读取载荷、发送与解绑的顺序,查阅原生 C++ 接口用法。

本页内容7 节

模块与头文件

在使用这些接口的模块中加入 AsyncMessageSystem 依赖;StarryGame 已具备。当前 UE 5.8 的 FInstancedStruct 位于 CoreUObject,不需要再按图中旧写法添加 StructUtils 模块。

PublicDependencyModuleNames.AddRange(new string[]
{
	"AsyncMessageSystem"
});

相关头文件为:

#include "AsyncGameplayMessageSystem.h"
#include "AsyncMessage.h"
#include "AsyncMessageBindingOptions.h"
#include "AsyncMessageHandle.h"
#include "AsyncMessageWorldSubsystem.h"
#include "GameplayTagContainer.h"
#include "StructUtils/InstancedStruct.h"

下面按图示展示接口片段。AMyClass、FMyCustomStruct、PlayerName、Level 与 Message.Player.LevelUp 都沿用图中的示例名称,不是 Starry 已有类型、字段或消息;使用时替换为实际内容,不需要另建消息组件。项目已有消息与载荷见项目消息。

取得系统

以下两种方式任选一种,均取得当前 World 的 FAsyncGameplayMessageSystem。调用时需要有效的 World,取得后检查系统是否有效。

// 方式一:使用静态辅助函数。
TSharedPtr<FAsyncGameplayMessageSystem> GameplayMessageSystem =
	UAsyncMessageWorldSubsystem::GetSharedMessageSystem<FAsyncGameplayMessageSystem>(GetWorld());
// 方式二:先取得 World Subsystem。
auto* Subsystem = GetWorld()->GetSubsystem<UAsyncMessageWorldSubsystem>();
TSharedPtr<FAsyncGameplayMessageSystem> GameplayMessageSystem =
	Subsystem->GetSharedMessageSystem<FAsyncGameplayMessageSystem>();

设置消息与接收选项

保存系统和返回的 ListenerHandle,用于之后配对解绑。示例消息 Message.Player.LevelUp 需要先注册为 Gameplay Tag;用名称构造 Id 也会查找这个标签。

FAsyncMessageHandle ListenerHandle;
FAsyncMessageBindingOptions Options;

// 默认在游戏线程 TG_PostUpdateWork 接收。
// Options.SetTickGroup(TG_PostUpdateWork);

// 也可指定后台接收;该回调必须遵守相应线程约束。
// Options.SetNamedThreads(ENamedThreads::AnyBackgroundThread);

const FAsyncMessageId MessageId(TEXT("Message.Player.LevelUp"));
// 也可直接传入已有的 Gameplay Tag:FAsyncMessageId MessageId(GameplayTag);

监听:成员函数

将 AMyClass 替换为当前类名,在该类声明 void OnMessageReceived(const FAsyncMessage& Message);,然后登记:

ListenerHandle = GameplayMessageSystem->BindListener(
	MessageId,
	MakeWeakObjectPtr(this),
	&AMyClass::OnMessageReceived,
	Options);

void AMyClass::OnMessageReceived(const FAsyncMessage& Message)
{
	if (const FMyCustomStruct* Payload =
		Message.GetPayloadView().GetPtr<const FMyCustomStruct>())
	{
		UE_LOG(LogTemp, Log, TEXT("%s Level up! New value: %d"),
			*Payload->PlayerName, Payload->Level);
	}
}

绑定代码放在现有宿主的初始化逻辑中,成员函数实现放在对应 .cpp。图中接收指针与 GetPtr 的结构体名称不一致,这里统一为 FMyCustomStruct。GetPtr 在类型不匹配时返回空;载荷指针只在当前回调内使用。

监听:Lambda

也可以使用 Lambda,与上面的成员函数方式二选一。需要访问宿主时,用捕获的弱引用检查其是否仍有效。

ListenerHandle = GameplayMessageSystem->BindListener(
	MessageId,
	[WeakThis = MakeWeakObjectPtr(this)](const FAsyncMessage& Message)
	{
		if (!WeakThis.IsValid())
		{
			return;
		}

		if (const FMyCustomStruct* Payload =
			Message.GetPayloadView().GetPtr<const FMyCustomStruct>())
		{
			// 读取实际载荷字段;通过 WeakThis 访问宿主。
		}
	},
	Options);

两种绑定形式均可在最后额外传入 BindingEndpoint;省略时使用默认 World 范围。路由与接收线程见标签、时机与生命周期。

发送

先填写自己的结构体,再用 FInstancedStruct::Make 复制为载荷并入队。发送与接收需要约定同一个消息 Id 和载荷类型。

FMyCustomStruct Data;
Data.PlayerName = TEXT("Jack");
Data.Level = 3;

FInstancedStruct Payload = FInstancedStruct::Make(Data);
GameplayMessageSystem->QueueMessageForBroadcast(MessageId, Payload);

FInstancedStruct::Make 创建结构体副本。系统在处理对应接收队列时分发,不在发送这一行同步执行监听回调。

解绑

对登记时的系统调用 UnbindListener,再清空保存的句柄。只把句柄设为无效不会移除监听。

if (ListenerHandle.IsValid())
{
	GameplayMessageSystem->UnbindListener(ListenerHandle);
	ListenerHandle = FAsyncMessageHandle::Invalid;
}

这段代码在功能关闭或宿主退出时执行,前提是登记时的系统仍有效。重复开始前先解绑已有监听,避免覆盖旧句柄;弱对象检查不能代替主动解绑。蓝图任务的保存和停止接线见蓝图收发与解绑。

实现入口:引擎插件的 AsyncMessageSystemBase.h、AsyncGameplayMessageSystem.h、AsyncMessageWorldSubsystem.h、AsyncMessage.h、AsyncMessageHandle.h 与 AsyncMessageBindingOptions.h。以上为原生接口片段,未向项目新增类型或运行 PIE。

Starry手册导览从模块进入子模块与具体功能,查阅功能用途、配置步骤、技术架构和扩展方法。工程分层与系统协作了解 Framework、Gameplay 与通用插件的依赖方向,以及配置装配和运行状态的边界。模块总览与接入从行为系统、执行系统与机会调度了解 AstraAI,再为角色配置第一套组合决策。组合与覆盖正确区分优先级、行为分数与 Stack 顺序,按完整路径组织可撤销的配置。评分与抢占沿着条件、优先级、分数、持续有效性与冷却,解释一个行为为何开始或退出。Logic 与生命周期把动作接入 Start、Cancel、Cleanup,正确处理共享模板、蓝图实例与过期异步回调。BT、StateTree 与 HTN复用控制器上的树组件,正确传入数据、报告整树结果并等待安全退出。Objective 与机会生成把领域事实整理成当前机会,让评分只读数据,让生成器独立管理订阅。命令发布与领取发布一条命令后,经机会评估、Take、执行与 EndUse 完成一次受控使用。行为区域用可挂载的 Zone 组件分配行为层,并把回区机会与实际导航分开。调试与排查从初始化到候选、机会、应用层与后端,沿真实运行路径定位 AI 没有行动的原因。模块总览与使用了解通用调试工具的操作、远程连接与窗口扩展,再由各模块的排查说明定位具体问题。netImgui 远程面板在独立桌面程序查看调试窗口,区分远程 UI、游戏视口绘制与玩法网络权限。窗口扩展与属性绘制复用窗口生命周期、反射 Inspector 和场景绘制,避免为每个调试页重做基础设施。模块总览与接入从配置绑定、动作处理、通道缓存与玩家改键了解 Input,再接入角色的输入配置。绑定与配置生命周期理解跨 IMC 的共享绑定、配置所有权、延迟初始化,以及切换角色时的清理。Handler 与能力分派选择正确的输入阶段,把输入交给移动、GAS、GameplayEvent 或 UI,保持共享 Handler 无玩家状态。修饰器与双击选择输入值变换所在层,理解玩家设置修饰器与 Double Tap 的时序规则。通道输入缓存按 GameplayTag 通道开窗,保留首批或末批输入,正常关闭执行,中断取消丢弃。改键与玩家设置通过 GameSettings 统一编辑与应用,区分 SharedSettings 偏好和 Enhanced Input 键映射数据。调试与排查使用 Gamepad 与 Actions 观察和模拟输入,沿设备、映射、绑定、缓存与能力定位问题。模块总览与接入使用 UE 5.8 原生 Async Message System,把事件通知与接收方解耦,查阅消息收发、路由和 Starry 的实际接入。蓝图收发与解绑通过消息 Id、监听、载荷封装与解包的核心连线,掌握蓝图收发和任务解绑。C++ 收发与解绑按取得系统、登记监听、读取载荷、发送与解绑的顺序,查阅原生 C++ 接口用法。标签、时机与生命周期理解消息的标签层级、World 和 Endpoint 路由、游戏线程派发、载荷快照与 C++ 监听清理。项目消息与排查查阅 Starry 已接入的消息标签、载荷和触发点,区分本地通知、状态复制与 RPC 转发,并沿真实链路排查。模块总览与项目接入管理软引用加载、有序回调与任务作用域,了解指示器画布的实际接入,以及加载完成后的资源和宿主生命周期。软引用加载与 C++ 用法用组件加载软对象和控件类,保存加载结果,理解路径数组、同步回调、失败结果,以及重载与退出时的取消清理。主资产与资源驻留按主资产 ID 和 Bundle 预加载资源,理解句柄保留、取消与 GC 引用的关系,以及 AssetManager 和打包配置的职责。回调顺序与独立作用域理解并行加载与有序回调的区别,使用 FAsyncScope 分开取消任务,了解 AsyncEvent、开始完成通知和等待条件的当前边界。在文档中说明蓝图用法将真实蓝图节点嵌入正文,结合步骤、引脚和连线说明功能,节点文本随文章保存。维护这本手册本地文件与网页编辑共用一份 MDX,日常只改内容与关系,沿用共享版式和图表。