UE4运用C++和框架开发坦克大战教程笔记(十六)(第49~50集)

news2024/11/17 13:38:21

UE4运用C++和框架开发坦克大战教程笔记(十六)(第49~50集)

  • 49. 创建多个资源对象
    • 补全调用链并测试生成多个同种类名资源对象
    • 实现创建多个同资源名的对象实例
  • 50. 资源加载系统测试
    • 补全调用链并测试生成多个同名资源对象
    • 测试生成 Widget 资源对象
    • 测试生成 Object 资源对象

49. 创建多个资源对象

补全调用链并测试生成多个同种类名资源对象

上节写好了 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];
	}
}

补全调用链、测试环节我们留到下一节课。

50. 资源加载系统测试

补全调用链并测试生成多个同名资源对象

补全 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 资源对象的逻辑写好了。

在这里插入图片描述

测试生成 Widget 资源对象

前面我们测试都是生成 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 资源对象

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 资源对象。

在这里插入图片描述
至此,整个资源加载系统的编写以及测试都完成了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1410362.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【mongoDB】集合的创建和删除

目录 1.集合的创建 2. 查看所有集合 3.删除集合 1.集合的创建 格式&#xff1a; db.createCollection ( name ) 例如创建一个名为 bbb 的集合 还可以通过传递一个选项对象来指定集合的属性&#xff0c;例如最大文档的大小&#xff0c;索引选项等 例如 这样创建了一个名为 cc…

[极客大挑战 2019]BabySQL1

发现union select被过滤了&#xff0c;双写绕过 or、from被过滤 where被过滤 在b4bysql中找到flag

【pdf技巧】pdf无法编辑的原因是什么?如何编辑pdf?

打开PDF文件之后发现没有办法编辑PDF文件&#xff0c;都有哪些原因呢&#xff1f; 首先我们可以考虑一下&#xff0c;PDF文件中的内容是否是图片&#xff0c;如果确认是图片文件&#xff0c;那么我们想要编辑&#xff0c;就可以先使用PDF编辑器中的OCR扫描功能&#xff0c;将图…

【c语言】三子棋

前言&#xff1a; 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏规则是双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#xff0c;率先将自己的三个棋子走成一条线就视为胜利。但因棋盘太小&#xff0c;三子棋在很多时候会出现和…

springboot实现aop

目录 AOP(术语)引入依赖实现步骤测试验证感谢阅读 AOP(术语) 连接点 类里面哪些方法可以增强&#xff0c;这些点被称为连接点 切入点 实际被真正增强的方法 通知&#xff08;增强&#xff09; 实际增强的逻辑部分称为通知&#xff08;增强&#xff09; 通知&#xff08;增强&…

系统登录的时候的密码如何做到以加密的形式进行登录【java.security包下的api】工具类。

/** description: 将普通的publicKey转化得到一个RSAPublicKey* author: zkw* date: 2024/1/24 16:17* param: publicKey 普通的publicKey* return: RSAPublicKey 得到一个新的RSAPublicKey**/public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorit…

数据结构——静态链表

1.定义&#xff1a; &#xff08;1&#xff09;单链表&#xff1a;各个结点散落在内存中的各个角落&#xff0c;每个结点有指向下一个节点的指针(下一个结点在内存 中的地址); &#xff08;2&#xff09;静态链表&#xff1a;用数组的方式来描述线性表的链式存储结构: 分配一…

RUST笔记 FireDBG| Rust 代码调试器

安装https://firedbg.sea-ql.org/blog/2023-12-12-introducing-firedbg/ 更新VSCODE sudo dpkg -i code_1.85.2-1705561292_amd64.deb 安装FireDBG binaries (base) pddpdd-Dell-G15-5511:~$ curl https://raw.githubusercontent.com/SeaQL/FireDBG.for.Rust/main/install.sh …

java——逻辑控制

这里学过c语言这一节还是很简单的&#xff0c;只是我们这里利用的是java的语法知识。这里我们重点是熟练运用java 的语法&#xff0c;结合前面学的知识。 这一章更多的是利用java语法知识来编程。 &#x1f6a9;if 语句 if(布尔表达式){ // 语句 }if(布尔表达式){ // 语句1…

Unity动画桢事件

1&#xff0c;使用原因 在新项目内部审核的时候&#xff0c;说什么动画节奏不匹配&#xff0c;所以决定用动画桢事件来处理技能释放。当释放技能的时候&#xff0c;先播放技能动画&#xff0c;然后再动画桢所在的时间戳执行技能的逻辑。 2&#xff0c;具体实现 1&#xff0c;…

gin中使用限流中间件

限流又称为流量控制&#xff08;流控&#xff09;&#xff0c;通常是指限制到达系统的并发请求数&#xff0c;本文列举了常见的限流策略&#xff0c;并以gin框架为例演示了如何为项目添加限流组件。 限流 限流又称为流量控制&#xff08;流控&#xff09;&#xff0c;通常是指…

【前端基础--4】

定位属性 position 可以将元素定位到你想要放到位置&#xff0c;使用方位值来进行移动(top,left,right,bottom)。 1.相对定位 position: relative; top: 20px; left: 20px; 以自身为定点进行移动&#xff0c;不会脱离文档流。 不会影响元素本身的性质&#xff1b;块级…

Kali如何启动SSH服务并实现无公网ip环境远程连接

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过[cpolar 内网穿透](cpolar官网-安全的内网穿透工具 | 无需公网ip | 远程访问 | 搭建网站)软件实现ssh 远程连接kali! …

谷歌公布一个可以让 AI 进行自我判断输出内容正确性的模型训练框架 ASPIRE

谷歌开发了一款名为 ASPIRE 的训练框架&#xff0c;旨在增强人工智能&#xff08;AI&#xff09;模型的选择性预测能力。这款框架为模型引入了 “可信度” 机制&#xff0c;即模型会输出一系列答案&#xff0c;并为每个答案赋予一个正确概率评分。通过这种方式&#xff0c;ASPI…

鲜花商城,Java项目、前端vue

系统架构 后台&#xff1a; SpringBoot Mybatis-plus Mybatis Hutool工具包 lombok插件 前台&#xff1a;Vue Vue Router ELementUI Axios 系统简介 功能&#xff1a;首页推荐&#xff08;默认根据用户买过的商品进行推荐&#xff0c;如果没买过则根据商品销量推荐&…

Sybase PowerDesigner15安装配置

1,软件介绍 ​ Power Designer 是Sybase公司的CASE工具集,使用它可以方便地对管理信息系统进行分析设计,他几乎包括了数据库模型设计的全过程。利用Power Designer可以制作数据流程图、概念数据模型、物理数据模型,还可以为数据仓库制作结构模型,也能对团队设计模型进行控…

RocketMQ源码阅读-十-事务消息

RocketMQ源码阅读-十-事务消息 交互流程事务消息发送Producer发送事务消息Broker处理结束事务请求Broker 生成 ConsumeQueue 事务消息回查Broker发起回查Producer 接收回查 总结 交互流程 事务消息交互流程图如下&#xff1a;事务消息发送步骤如下&#xff1a; 生产者将半事务…

redis-持久化-1

Redis 提供了2个不同形式的持久化方式。 RDB&#xff08;Redis DataBase&#xff09; AOF&#xff08;Append Of File&#xff09; 一、Redis持久化之RDB 1.什么是RDB 在指定的时间间隔内将内存中的数据集快照写入磁盘&#xff0c; 也就是行话讲的Snapshot快照&#xff0c…

爬虫是什么 怎么预防

爬虫是一种自动化程序&#xff0c;用于从网页或网站中提取数据。它们通过模拟人类用户的行为&#xff0c;发送HTTP请求并解析响应&#xff0c;以获取所需的信息。 爬虫可以用于各种合法用途&#xff0c;如搜索引擎索引、数据采集和监测等。然而&#xff0c;有些爬虫可能是恶意的…

HarmonyOS4.0系统性深入开发26方舟开发框架(ArkUI)概述

方舟开发框架&#xff08;ArkUI&#xff09;概述 方舟开发框架&#xff08;简称ArkUI&#xff09;为HarmonyOS应用的UI开发提供了完整的基础设施&#xff0c;包括简洁的UI语法、丰富的UI功能&#xff08;组件、布局、动画以及交互事件&#xff09;&#xff0c;以及实时界面预览…