AsyncMixin · 软引用加载与 C++ 用法

用组件加载软对象和控件类,保存加载结果,理解路径数组、同步回调、失败结果,以及重载与退出时的取消清理。

本页内容6 节

选择加载入口

宿主继承 FAsyncMixin 后,在成员函数中调用受保护的 AsyncLoad 和 StartAsyncLoading。这些操作在游戏线程执行。需要组合而不继承时,使用成员形式的 FAsyncScope,它公开同一组接口。

输入回调选择适用情况
TSoftObjectPtr<T>T*、无参数或 FSimpleDelegate纹理、数据资产等对象资源
TSoftClassPtr<T>TSubclassOf<T>、无参数或 FSimpleDelegate控件类等类资源
FSoftObjectPathFSimpleDelegate类型在使用方确定的单个路径
TArray<FSoftObjectPath>无参数或 FSimpleDelegate一组路径作为一个步骤,整组完成后再回调

回调执行说明该步骤已经结束,不保证每个资源加载成功。类型化回调最终读取软引用的 Get(),必须处理 nullptr 或空类。空路径、失效引用、类型不符和没有进入打包产物的资产都应从资源配置排查。

建立加载组件

下面的 UAsyncLoadExampleComponent 是需要自己创建的文档示例,工程没有预置这个类型。它在开始游戏时加载一张图标和一个控件类,将结果保存给使用方;重新加载前清理旧结果,结束游戏时取消。本例只演示资源接入,具体界面如何创建和显示由业务负责。

放入 StarryGame 时使用已有的 Core、CoreUObject、Engine、UMG 和 AsyncMixin 依赖。在 AsyncLoadExampleComponent.h 中声明:

#pragma once

#include "CoreMinimal.h"
#include "AsyncMixin.h"
#include "Components/ActorComponent.h"

#include "AsyncLoadExampleComponent.generated.h"

class UTexture2D;
class UUserWidget;

/** 加载本组件配置的预览资源,并在退出时撤销仍未完成的步骤。 */
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class STARRYGAME_API UAsyncLoadExampleComponent
	: public UActorComponent, public FAsyncMixin
{
	GENERATED_BODY()

public:
	/** 替换当前加载批次;允许图标或控件类为空。 */
	void ReloadResources();

	/** 取消本组件的步骤并释放持有结果,不撤回使用方已经创建的界面。 */
	void ClearResources();

protected:
	// UActorComponent interface
	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
	//~ End of UActorComponent interface

	// FAsyncMixin interface
	virtual void OnStartedLoading() override;
	virtual void OnFinishedLoading() override;
	//~ End of FAsyncMixin interface

	/** 待加载图标;软引用本身不保证对象驻留。 */
	UPROPERTY(EditAnywhere, Category="Preview")
	TSoftObjectPtr<UTexture2D> IconAsset;

	/** 待加载的控件类,不在本组件中实例化。 */
	UPROPERTY(EditAnywhere, Category="Preview")
	TSoftClassPtr<UUserWidget> WidgetAsset;

	/** 接住加载结果,供界面读取并保持图标的 GC 引用。 */
	UPROPERTY(Transient, BlueprintReadOnly, Category="Preview")
	TObjectPtr<UTexture2D> LoadedIcon;

	/** 保存已加载的类;为空表示未配置或未成功取得。 */
	UPROPERTY(Transient, BlueprintReadOnly, Category="Preview")
	TSubclassOf<UUserWidget> LoadedWidgetClass;

	/** 表示当前批次是否尚未结束,不表示加载成功率。 */
	UPROPERTY(Transient, BlueprintReadOnly, Category="Preview")
	bool bLoadingResources = false;
};

排入请求并保存结果

在同名 AsyncLoadExampleComponent.cpp 中实现:

#include "AsyncLoadExampleComponent.h"

#include "Blueprint/UserWidget.h"
#include "Engine/Texture2D.h"

void UAsyncLoadExampleComponent::ReloadResources()
{
	ClearResources();

	if (!IconAsset.IsNull())
	{
		AsyncLoad<UTexture2D>(IconAsset, [this](UTexture2D* Texture)
		{
			LoadedIcon = Texture;
		});
	}

	if (!WidgetAsset.IsNull())
	{
		AsyncLoad<UUserWidget>(WidgetAsset, [this](TSubclassOf<UUserWidget> WidgetClass)
		{
			LoadedWidgetClass = WidgetClass;
		});
	}

	StartAsyncLoading();
}

void UAsyncLoadExampleComponent::ClearResources()
{
	CancelAsyncLoading();
	LoadedIcon = nullptr;
	LoadedWidgetClass = nullptr;
	bLoadingResources = false;
}

void UAsyncLoadExampleComponent::BeginPlay()
{
	Super::BeginPlay();
	ReloadResources();
}

void UAsyncLoadExampleComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	ClearResources();
	Super::EndPlay(EndPlayReason);
}

void UAsyncLoadExampleComponent::OnStartedLoading()
{
	bLoadingResources = true;
}

void UAsyncLoadExampleComponent::OnFinishedLoading()
{
	bLoadingResources = false;
}

带类型参数的 lambda 使用 AsyncLoad<UTexture2D> / AsyncLoad<UUserWidget> 显式指定模板类型,便于匹配 TFunction<void(T*)> 或 TFunction<void(TSubclassOf<T>)> 重载。无参数 lambda 可以在回调中读取请求时捕获的软引用,项目画布采用的就是这种方式。

LoadedIcon 和 LoadedWidgetClass 使用 UPROPERTY 接住结果。普通加载完成后,Mixin 会在不再需要时释放内部加载状态;只保存 TSoftObjectPtr 或 lambda 中的裸指针不能代替 GC 引用。使用方取结果创建界面前仍需判断是否为空。

这个组件在 EndPlay 取消,ClearResources 也显式恢复状态。取消不会调用 OnFinishedLoading,因此只在完成钩子里关闭加载提示,会让主动退出的界面留下错误状态。Mixin 析构的自动清理是最终保障,不能替代业务退出清理。

一组路径作为一个步骤

例如需要两张纹理都结束加载后再刷新,在现有宿主成员函数中将路径组成数组:

const TArray<FSoftObjectPath> Paths =
{
	PrimaryIcon.ToSoftObjectPath(),
	SecondaryIcon.ToSoftObjectPath()
};

AsyncLoad(Paths, [this, PrimaryIcon = PrimaryIcon, SecondaryIcon = SecondaryIcon]()
{
	// 两个软引用都是调用方已有配置;在此接住结果并决定失败时的显示。
	LoadedPrimaryIcon = PrimaryIcon.Get();
	LoadedSecondaryIcon = SecondaryIcon.Get();
});
StartAsyncLoading();

这段代码需要宿主已有 TSoftObjectPtr<UTexture2D> 配置 PrimaryIcon / SecondaryIcon,以及 UPROPERTY 管理的 LoadedPrimaryIcon / LoadedSecondaryIcon 成员。lambda 用初始化捕获保存请求时的软引用,避免读取后来改动的配置。数组不会把结果作为参数传回;其中一个路径失败时,回调仍可能执行,各结果要分别检查。

单个路径使用 FSimpleDelegate,例如 AsyncLoad(Path, FSimpleDelegate::CreateWeakLambda(this, []() { /* 处理结果 */ }));。弱委托检查的是所绑定的 UObject;捕获的其他对象仍需自己保证有效。

启动、完成与取消的时机

AsyncLoad 入队时就会向 StreamableManager 申请加载,StartAsyncLoading() 开始推进这组步骤和通知,不会阻塞等待磁盘加载结束。已经完成的前序步骤可以在 StartAsyncLoading() 返回前执行回调;空队列也会立即调用开始与完成钩子。需要使用的成员和界面状态应在调用前准备好。

若省略显式启动,Mixin 会通过 CoreTicker 自动启动;不应依赖固定帧延迟。各资源的实际加载与回调顺序区别见任务编排。

CancelAsyncLoading() 只作用于当前 Mixin 的整组任务。它解除完成绑定并释放自己持有的句柄,不回滚已执行回调,不代表停止其他使用者对同一资源的请求,也不保证立刻停止所有底层 I/O。已创建的控件、委托与业务状态要由宿主另外清理。

配置与排查

  1. 创建示例组件并挂到自己的测试 Actor;在详细信息中指定真实图标和控件类。开始游戏后检查加载标记恢复、结果是否非空,再由自己的 UI 使用这些结果。
  2. 换到尚未加载的资源,快速退出 Actor 或再次调用 ReloadResources,确认旧批次不会在后续重新写入结果;已驻留资源也要检查同步完成的路径。
  3. 结果为空时先确认软路径、资源类型、重定向和打包配置;编辑器里已加载过的资源可能掩盖打包遗漏。需要主资产扫描与 Bundle 时继续读资源驻留。
  4. 资源完成却不回调时,检查本作用域更早的步骤是否仍未完成;界面取消后仍显示旧内容时,检查那是不是已经执行的业务操作,以及是否忘记拆除旧界面。

上面是读者创建样例后的检查步骤,本轮没有创建该组件、编译或运行 PIE。实现入口:Plugins/EngineExtension/AsyncMixin/Source/Public/AsyncMixin.h 的加载重载,以及同插件 Source/Private/AsyncMixin.cpp 的请求、启动和取消实现;实际项目用法见指示器接入。

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,日常只改内容与关系,沿用共享版式和图表。