赞
踩
上节写好了 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 资源对象。
至此,整个资源加载系统的编写以及测试都完成了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。