上节写好了 DDWealth 里的创建同种类名资源对象的方法,这集开头先来补充完整 DDWealth – DDModule – DDOO – 对象 这条调用链。
DDModule.h
public:
// 创建同资源种类名的对象实例,同种类名下的每个资源链接创建一个对象实例
void BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName ObjectName, FName FunName, TArray<FTransform> SpawnTransforms);
DDModule.cpp
void UDDModule::BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName ObjectName, FName FunName, TArray<FTransform> SpawnTransforms)
{
Wealth->BuildKindClassWealth(WealthType, WealthKind, ObjectName, FunName, SpawnTransforms);
}
对于 DDOO 来说,要根据生成对象的种类类型来区分调用方法。并且也不需要传 ObjectName,因为它自带有。
DDOO.h
protected:
// 创建同资源种类名的对象实例,同种类名下的每个资源链接创建一个对象实例
void BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName FunName);
void BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName FunName, FTransform SpawnTransform);
void BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName FunName, TArray<FTransform> SpawnTransforms);
DDOO.cpp
// 区分点在于传入多少个 FTransform
// 对 Widget 类型
void IDDOO::BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName FunName)
{
IModule->BuildKindClassWealth(WealthType, WealthKind, GetObjectName(), FunName, TArray<FTransform>{ FTransform::Identity });
}
// 对 Object 类型
void IDDOO::BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName FunName, FTransform SpawnTransform)
{
IModule->BuildKindClassWealth(WealthType, WealthKind, GetObjectName(), FunName, TArray<FTransform>{ SpawnTransform });
}
// 对 Actor 类型
void IDDOO::BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName FunName, TArray<FTransform> SpawnTransforms)
{
IModule->BuildKindClassWealth(WealthType, WealthKind, GetObjectName(), FunName, SpawnTransforms);
}
接下来到 WealthCallObject 里进行验证。我们打算生成 3 个同种类名的对象到场景中,并且让它们一直旋转。
WealthCallObject.h
public:
// 回调方法
UFUNCTION()
void BuildActorKind(TArray<FName> BackNames, TArray<AActor*> BackActors);
public:
// 存储生成的 Actor
TArray<AActor*> KindActors;
WealthCallObject.cpp
void UWealthCallObject::DDLoading()
{
// 生成 3 个偏移的位置
TArray<FTransform> SpawnTransforms;
for (int i = 0; i < 3; ++i) {
SpawnTransforms.Push(FTransform(ViewTrans.GetLocation() + FVector(OffsetValue * i, 0.f, 0.f)));
}
// 测试完毕后注释掉这句
BuildKindClassWealth(EWealthType::Actor, "ViewActor", "BuildActorKind", SpawnTransforms);
}
void UWealthCallObject::DDTick(float DeltaSeconds)
{
for (int i = 0; i < KindActors.Num(); ++i)
KindActors[i]->AddActorWorldRotation(FRotator(1.f, 0.f, 0.f));
}
void UWealthCallObject::BuildActorKind(TArray<FName> BackNames, TArray<AActor*> BackActors)
{
for (int i = 0; i < BackNames.Num(); ++i)
DDH::Debug() << BackNames[i] << DDH::Endl();
KindActors = BackActors;
}
编译后,因为我们在 PlayerData 里已经配置好了 3 个 ViewActor 的数据,所以直接运行游戏,可以看到左上角输出了三个对象的名字,并且场景中它们 3 个在旋转。说明批量生成同种类名 Actor 对象的逻辑写好了。
前面实现了创建同种类名(WealthKind)的多个资源对象,现在我们来实现创建多个同资源名(WealthName)的资源对象。
DDWealth.h
// 加载批量同名 Class
struct ClassMultiLoadNode;
UCLASS()
class DATADRIVEN_API UDDWealth : public UObject, public IDDMM
{
GENERATED_BODY()
public:
// 创建多个同资源名的对象实例
void BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName ObjectName, FName FunName, TArray<FTransform> SpawnTransforms);
protected:
// 处理创建多个对象的方法
void DealClassMultiLoadStack();
protected:
TArray<ClassMultiLoadNode*> ClassMultiLoadStack;
protected:
// 批量生成同名 Object 对象的回调函数
DDOBJFUNC_TWO(BackObjectMulti, FName, BackName, TArray<UObject*>, BackObjects);
// 批量生成同名 Actor 对象的回调函数
DDOBJFUNC_TWO(BackActorMulti, FName, BackName, TArray<AActor*>, BackActors);
// 批量生成同名 Widget 对象的回调函数
DDOBJFUNC_TWO(BackWidgetMulti, FName, BackName, TArray<UUserWidget*>, BackWidgets);
};
DDWealth.cpp
struct ClassMultiLoadNode
{
// 加载句柄
TSharedPtr<FStreamableHandle> WealthHandle;
// 资源结构体
FClassWealthEntry* WealthEntry;
// 请求对象名
FName ObjectName;
// 回调方法名
FName FunName;
// 生成数量
int32 Amount;
// 多个生成位置
TArray<FTransform> SpawnTransforms;
// 保存生成的对象与名字
TArray<UObject*> ObjectGroup;
TArray<AActor*> ActorGroup;
TArray<UUserWidget*> WidgetGroup;
// 构造函数,未加载时使用
ClassMultiLoadNode(TSharedPtr<FStreamableHandle> InWealthHandle, FClassWealthEntry* InWealthEntry, int32 InAmount, FName InObjectName, FName InFunName, TArray<FTransform> InSpawnTransforms)
{
WealthHandle = InWealthHandle;
WealthEntry = InWealthEntry;
Amount = InAmount;
ObjectName = InObjectName;
FunName = InFunName;
SpawnTransforms = InSpawnTransforms;
}
// 构造函数,已加载时使用
ClassMultiLoadNode(FClassWealthEntry* InWealthEntry, int32 InAmount, FName InObjectName, FName InFunName, TArray<FTransform> InSpawnTransforms)
{
WealthEntry = InWealthEntry;
Amount = InAmount;
ObjectName = InObjectName;
FunName = InFunName;
SpawnTransforms = InSpawnTransforms;
}
};
void UDDWealth::WealthTick(float DeltaSeconds)
{
DealClassMultiLoadStack(); // 加入到 Tick()
}
void UDDWealth::BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName ObjectName, FName FunName, TArray<FTransform> SpawnTransforms)
{
// 获取对应的资源结构体
FClassWealthEntry* WealthEntry = GetClassSingleEntry(WealthName);
// 如果为空
if (!WealthEntry) {
DDH::Debug() << ObjectName << " Get Null Wealth : " << WealthName << DDH::Endl();
return;
}
// 如果资源不可用
if (!WealthEntry->WealthPtr.ToSoftObjectPath().IsValid()) {
DDH::Debug() << ObjectName << " Get UnValid Wealth : " << WealthName << DDH::Endl();
return;
}
// 资源类型是否匹配
if (WealthEntry->WealthType != WealthType) {
DDH::Debug() << ObjectName << " Get Error Type : " << DDH::Endl();
return;
}
// 验证 Transform 数组的数量是否为 1 或者为 Amount,或者 Amount = 0
if ((WealthType == EWealthType::Actor && SpawnTransforms.Num() != 1 && SpawnTransforms.Num() != Amount) || Amount == 0) {
DDH::Debug() << ObjectName << " Send Error Spawn Count : " << WealthName << DDH::Endl();
return;
}
// 如果已经加载资源
if (WealthEntry->WealthClass)
ClassMultiLoadStack.Push(new ClassMultiLoadNode(WealthEntry, Amount, ObjectName, FunName, SpawnTransforms));
else {
// 异步加载
TSharedPtr<FStreamableHandle> WealthHandle = WealthLoader.RequestAsyncLoad(WealthEntry->WealthPtr.ToSoftObjectPath());
// 添加新节点
ClassMultiLoadStack.Push(new ClassMultiLoadNode(WealthHandle, WealthEntry, Amount, ObjectName, FunName, SpawnTransforms));
}
}
void UDDWealth::DealClassMultiLoadStack()
{
// 定义完成的节点
TArray<ClassMultiLoadNode*> CompleteStack;
for (int i = 0; i < ClassMultiLoadStack.Num(); ++i) {
// 如果没有加载 UClass, 说明加载句柄有效
if (!ClassMultiLoadStack[i]->WealthEntry->WealthClass) {
// 如果加载句柄加载完毕
if (ClassMultiLoadStack[i]->WealthHandle->HasLoadCompleted())
ClassMultiLoadStack[i]->WealthEntry->WealthClass = Cast<UClass>(ClassMultiLoadStack[i]->WealthHandle->GetLoadedAsset());
}
// 再次判断 WealthClass 是否存在,如果存在进入生成对象阶段
if (ClassMultiLoadStack[i]->WealthEntry->WealthClass) {
// 区分类型生成对象
if (ClassMultiLoadStack[i]->WealthEntry->WealthType == EWealthType::Object) {
UObject* InstObject = NewObject<UObject>(this, ClassMultiLoadStack[i]->WealthEntry->WealthClass);
InstObject->AddToRoot();
ClassMultiLoadStack[i]->ObjectGroup.Push(InstObject);
// 如果生成完毕
if (ClassMultiLoadStack[i]->ObjectGroup.Num() == ClassMultiLoadStack[i]->Amount) {
// 返回对象给请求者
BackObjectMulti(ModuleIndex, ClassMultiLoadStack[i]->ObjectName, ClassMultiLoadStack[i]->FunName, ClassMultiLoadStack[i]->WealthEntry->WealthName, ClassMultiLoadStack[i]->ObjectGroup);
// 添加到完成序列
CompleteStack.Push(ClassMultiLoadStack[i]);
}
}
else if (ClassMultiLoadStack[i]->WealthEntry->WealthType == EWealthType::Actor) {
// 获取生成位置
FTransform SpawnTransform = ClassMultiLoadStack[i]->SpawnTransforms.Num() == 1 ? ClassMultiLoadStack[i]->SpawnTransforms[0] : ClassMultiLoadStack[i]->SpawnTransforms[ClassMultiLoadStack[i]->ActorGroup.Num()];
// 生成对象
AActor* InstActor = GetDDWorld()->SpawnActor<AActor>(ClassMultiLoadStack[i]->WealthEntry->WealthClass, SpawnTransform);
// 添加参数数组
ClassMultiLoadStack[i]->ActorGroup.Push(InstActor);
// 判断是否生成了全部的对象
if (ClassMultiLoadStack[i]->ActorGroup.Num() == ClassMultiLoadStack[i]->Amount) {
// 给请求者传递生成的对象
BackActorMulti(ModuleIndex, ClassMultiLoadStack[i]->ObjectName, ClassMultiLoadStack[i]->FunName, ClassMultiLoadStack[i]->WealthEntry->WealthName, ClassMultiLoadStack[i]->ActorGroup);
// 添加到完成序列
CompleteStack.Push(ClassMultiLoadStack[i]);
}
}
else if (ClassMultiLoadStack[i]->WealthEntry->WealthType == EWealthType::Widget) {
UUserWidget* InstWidget = CreateWidget<UUserWidget>(GetDDWorld(), ClassMultiLoadStack[i]->WealthEntry->WealthClass);
// 避免回收
GCWidgetGroup.Push(InstWidget);
// 添加参数数组
ClassMultiLoadStack[i]->WidgetGroup.Push(InstWidget);
// 判断是否生成了全部的对象
if (ClassMultiLoadStack[i]->WidgetGroup.Num() == ClassMultiLoadStack[i]->Amount) {
// 给请求者传递生成的对象
BackWidgetMulti(ModuleIndex, ClassMultiLoadStack[i]->ObjectName, ClassMultiLoadStack[i]->FunName, ClassMultiLoadStack[i]->WealthEntry->WealthName, ClassMultiLoadStack[i]->WidgetGroup);
// 添加到完成序列
CompleteStack.Push(ClassMultiLoadStack[i]);
}
}
}
}
// 清空已完成节点
for (int i = 0; i < CompleteStack.Num(); ++i) {
ClassMultiLoadStack.Remove(CompleteStack[i]);
delete CompleteStack[i];
}
}
补全调用链、测试环节我们留到下一节课。
补全 DDWealth – DDModule – DDOO – 对象 调用链。
DDModule.h
public:
// 创建多个同资源名的对象实例
void BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName ObjectName, FName FunName, TArray<FTransform> SpawnTransforms);
DDModule.cpp
void UDDModule::BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName ObjectName, FName FunName, TArray<FTransform> SpawnTransforms)
{
Wealth->BuildMultiClassWealth(WealthType, WealthName, Amount, ObjectName, FunName, SpawnTransforms);
}
对于 DDOO 来说,依旧需要根据资源种类来区分不同方法。也不需要传入 ObjectName。
DDOO.h
protected:
// 创建多个同资源名的对象实例
void BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName FunName);
void BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName FunName, FTransform SpawnTransform);
void BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName FunName, TArray<FTransform> SpawnTransforms);
DDOO.cpp
void IDDOO::BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName FunName)
{
IModule->BuildMultiClassWealth(WealthType, WealthName, Amount, GetObjectName(), FunName, TArray<FTransform>{ FTransform::Identity });
}
void IDDOO::BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName FunName, FTransform SpawnTransform)
{
IModule->BuildMultiClassWealth(WealthType, WealthName, Amount, GetObjectName(), FunName, TArray<FTransform>{ SpawnTransform });
}
void IDDOO::BuildMultiClassWealth(EWealthType WealthType, FName WealthName, int32 Amount, FName FunName, TArray<FTransform> SpawnTransforms)
{
IModule->BuildMultiClassWealth(WealthType, WealthName, Amount, GetObjectName(), FunName, SpawnTransforms);
}
我们打算生成 3 个 ViewActor2 对象,然后让它们在场景里旋转。
WealthCallObject.h
public:
// 回调方法
UFUNCTION()
void BuildActorMulti(FName BackName, TArray<AActor*> BackActors);
WealthCallObject.cpp
void UWealthCallObject::DDLoading()
{
// 测试完毕后记得注释掉
BuildMultiClassWealth(EWealthType::Actor, "ViewActor2", 3, "BuildActorMulti", SpawnTransforms);
}
void UWealthCallObject::BuildActorMulti(FName BackName, TArray<AActor*> BackActors)
{
DDH::Debug() << BackName << DDH::Endl();
KindActors = BackActors; // 复用 KindActors
}
编译后运行游戏,可以看到左上角输出 ViewActor2,并且 3 个 ViewActor2 对象在场景里旋转。说明创建多个同名 Actor 资源对象的逻辑写好了。
前面我们测试都是生成 Actor 类型的资源对象,所以我们还需要测试下 Widget 和 Object 类型的资源对象生成逻辑是否正常。
在 Blueprint 文件夹下新建一个名为 ViewWidget 的文件夹。然后在里面创建 3 个 Widget Blueprint,分别取名为 ViewWidget1 ~ 3。
在这 3 个蓝图界面里用 Image 取代原本的 Canvas Panel。并给 3 个 Image 分别赋予 3 张比较明显的笔刷图片(随便选)。
来到 HUDData,给 ClassWealthData 添加 3 个元素如下:
来到 LoadWealthWidget_BP,将原本的 ViewImage 控件缩小一点,然后修改界面如下(注意要给 SizeBox 改名如图所示):
来到 LoadWealthWidget,我们打算利用协程系统来分别运行 3 种生成 Widget 资源对象的方法。
LoadWealthWidget.h
// 提前声明
class USizeBox;
UCLASS()
class RACECARFRAME_API ULoadWealthWidget : public UDDUserWidget
{
GENERATED_BODY()
public:
// 生成单个 Widget 的回调函数
UFUNCTION()
void BuildSingleWidget(FName BackName, UUserWidget* BackWidget);
// 生成多个同种类 Widget 的回调函数
UFUNCTION()
void BuildKindWidget(TArray<FName> BackNames, TArray<UUserWidget*> BackWidgets);
// 生成多个同名 Widget 的回调函数
UFUNCTION()
void BuildMultiWidget(FName BackName, TArray<UUserWidget*> BackWidgets);
// 协程方法
DDCoroTask* BuildWidgetTest();
public:
// 对应刚刚在 LoadWealthWidget_BP 里添加的 3 个 SizeBox
UPROPERTY(Meta = (BindWidget))
USizeBox* SizeBox_1;
UPROPERTY(Meta = (BindWidget))
USizeBox* SizeBox_2;
UPROPERTY(Meta = (BindWidget))
USizeBox* SizeBox_3;
};
LoadWealthWidget.cpp
// 引入头文件
#include "Components/SizeBox.h"
void ULoadWealthWidget::DDLoading()
{
// 开启协程,测试完毕后记得注释掉
StartCoroutine("BuildWidgetTest", BuildWidgetTest());
}
void ULoadWealthWidget::BuildSingleWidget(FName BackName, UUserWidget* BackWidget)
{
DDH::Debug() << "BuildSingleWidget --> " << BackName << DDH::Endl();
SizeBox_1->ClearChildren();
SizeBox_1->AddChild(BackWidget);
}
void ULoadWealthWidget::BuildKindWidget(TArray<FName> BackNames, TArray<UUserWidget*> BackWidgets)
{
for (int i = 0; i < BackWidgets.Num(); ++i) {
DDH::Debug() << "BuildKindWidget --> " << BackNames[i] << DDH::Endl();
}
SizeBox_1->ClearChildren();
SizeBox_2->ClearChildren();
SizeBox_3->ClearChildren();
SizeBox_1->AddChild(BackWidgets[0]);
SizeBox_2->AddChild(BackWidgets[1]);
SizeBox_3->AddChild(BackWidgets[2]);
}
void ULoadWealthWidget::BuildMultiWidget(FName BackName, TArray<UUserWidget*> BackWidgets)
{
DDH::Debug() << "BuildMultiWidget --> " << BackName << DDH::Endl();
SizeBox_1->ClearChildren();
SizeBox_2->ClearChildren();
SizeBox_3->ClearChildren();
SizeBox_1->AddChild(BackWidgets[0]);
SizeBox_2->AddChild(BackWidgets[1]);
SizeBox_3->AddChild(BackWidgets[2]);
}
DDCoroTask* ULoadWealthWidget::BuildWidgetTest()
{
DDCORO_PARAM(ULoadWealthWidget)
#include DDCORO_BEGIN()
// D 的声明在 DDDefine.h,指向调用协程方法的这个 UserClass 本身
D->BuildSingleClassWealth(EWealthType::Widget, "ViewWidget2", "BuildSingleWidget");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(10.f); // 挂起 10 秒
D->BuildKindClassWealth(EWealthType::Widget, "ViewWidget", "BuildKindWidget");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(10.f);
D->BuildMultiClassWealth(EWealthType::Widget, "ViewWidget3", 3, "BuildMultiWidget");
#include DDCORO_END()
}
编译后运行游戏,可以看到左上角每隔 10 秒(下面 GIF 图为了缩小图片大小,我调成了间隔 4 秒)就会使用不同的生成方式生成 Widget。一开始是生成单个 Widget 资源对象,后来是生成多个同类种名的 Widget 资源对象,最后是生成多个同名的 Widget 资源对象。
生成多个同种类名的 Widget 资源对象时, ViewWidget2 排在第一是因为他在生成单个 Widget 资源对象时已经加载出来了。
Object 由于没有实体所以无法直接在场景内显示,我们打算让将其蓝图对象生成出来并注册到框架,通过在蓝图里运行 Print String 蓝图节点来证明它生成成功。
基于 DDObject 创建一个 C++ 类,目标模组为项目,命名为 ViewObject,路径为默认路径。
ViewObject.h
UCLASS(Blueprintable, BlueprintType) // 可生成蓝图
class RACECARFRAME_API UViewObject : public UDDObject
{
GENERATED_BODY()
public:
virtual void DDEnable() override;
protected:
// 实际上用 UFUNCTION + BlueprintImplement 来创建蓝图可运行节点更省资源,此处为了方便,用了反射事件系统
DDOBJFUNC(EchoSelfInfo);
};
ViewObject.cpp
void UViewObject::DDEnable()
{
Super::DDEnable();
// 反射事件自动调用名为 EchoInfo 的方法
EchoSelfInfo(ModuleIndex, GetObjectName(), "EchoInfo");
}
让 WealthCallObject 来使用协程系统,测试 Object 的三种生成方法。
WealthCallObject.h
public:
// 生成单个 Object 的回调函数
UFUNCTION()
void BuildSingleObject(FName BackName, UObject* BackObject);
// 生成多个同种类名 Object 的回调函数
UFUNCTION()
void BuildKindObject(TArray<FName> BackNames, TArray<UObject*> BackObjects);
// 生成多个同名 Object 的回调函数
UFUNCTION()
void BuildMultiObject(FName BackName, TArray<UObject*> BackObjects);
// 协程方法
DDCoroTask* BuildObjectTest();
WealthCallObject.cpp
void UWealthCallObject::DDLoading()
{
// 测试完毕后记得注释掉
StartCoroutine("BuildObjectTest", BuildObjectTest());
}
void UWealthCallObject::BuildSingleObject(FName BackName, UObject* BackObject)
{
DDH::Debug() << "BuildSingleObject --> " << BackName << DDH::Endl();
IDDOO* InstPtr = Cast<IDDOO>(BackObject);
if (InstPtr)
InstPtr->RegisterToModule(ModuleIndex);
}
void UWealthCallObject::BuildKindObject(TArray<FName> BackNames, TArray<UObject*> BackObjects)
{
for (int i = 0; i < BackObjects.Num(); ++i) {
DDH::Debug() << "BuildKindObject --> " << BackNames[i] << DDH::Endl();
IDDOO* InstPtr = Cast<IDDOO>(BackObjects[i]);
if (InstPtr)
InstPtr->RegisterToModule(ModuleIndex);
}
}
void UWealthCallObject::BuildMultiObject(FName BackName, TArray<UObject*> BackObjects)
{
DDH::Debug() << "BuildMultiObject --> " << BackName << DDH::Endl();
for (int i = 0; i < BackObjects.Num(); ++i) {
IDDOO* InstPtr = Cast<IDDOO>(BackObjects[i]);
if (InstPtr)
InstPtr->RegisterToModule(ModuleIndex);
}
}
DDCoroTask* UWealthCallObject::BuildObjectTest()
{
DDCORO_PARAM(UWealthCallObject)
#include DDCORO_BEGIN()
D->BuildSingleClassWealth(EWealthType::Object, "ViewObject2", "BuildSingleObject");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(10.f);
D->BuildKindClassWealth(EWealthType::Object, "ViewObject", "BuildKindObject");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(10.f);
D->BuildMultiClassWealth(EWealthType::Object, "ViewObject3", 3, "BuildMultiObject");
#include DDCORO_END()
}
编译后,在 Blueprint 下创建一个名为 ViewObject 的文件夹。在里面基于 ViewObject 创建 3 个蓝图,分别命名为 ViewObject1 ~ 3
在这 3 个蓝图里面都创建一个名为 EchoInfo 的函数,往里面添加 Pring String 的节点。
打开 PlayerData,给 ClassWealthData 添加 3 个元素如下:
运行游戏,可以看到每隔 10 秒左上角就输出了不同生成 Object 资源对象方式的 Debug 语句。初始是生成单个 Object 资源对象,随后是生成 3 个同种类名的 Object 资源对象,最后是生成 3 个同名 Object 资源对象。