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

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

  • 43. 单个加载 UObject 功能
    • 获取资源 URL 链接
    • 实现异步加载单个 UObject 类型资源
  • 44. 批量加载 UObject 功能
    • 测试加载单个 UObject 资源
    • 批量加载多个同类的 UObject 资源
  • 45. 单个加载 UClass 功能
    • 测试加载多个 UObject 资源
    • 异步加载单个 UClass 类型的资源

43. 单个加载 UObject 功能

获取资源 URL 链接

继续来补充根据资源类型生成资源的逻辑,在 DDWealth 里添加获取 URL 的方法。

DDWealth.h

public:

	// 外部方法单纯获取资源链接
	// 返回单个 URL
	FWealthURL* GainWealthURL(FName WealthName);
	// 返回一个种类的资源的 URL
	void GainWealthURL(FName WealthKind, TArray<FWealthURL*>& OutURL);

DDWealth.cpp

FWealthURL* UDDWealth::GainWealthURL(FName WealthName)
{
	// 从 DataAsset 里遍历获取对应名字的资源的 URL
	for (int i = 0; i < WealthData.Num(); ++i) {
		for (int j = 0; j < WealthData[i]->WealthURL.Num(); ++j) {
			if (WealthData[i]->WealthURL[j].WealthName.IsEqual(WealthName))
				return &WealthData[i]->WealthURL[j];
		}
	}
	return NULL;
}

void UDDWealth::GainWealthURL(FName WealthKind, TArray<FWealthURL*>& OutURL)
{
	// 从 DataAsset 里遍历获取对应种类名字的全部资源的 URL
	for (int i = 0; i < WealthData.Num(); ++i) {
		for (int j = 0; j < WealthData[i]->WealthURL.Num(); ++j) {
			if (WealthData[i]->WealthURL[j].WealthKind.IsEqual(WealthKind))
				OutURL.Push(&WealthData[i]->WealthURL[j]);
		}
	}
}

建立 DDWealth – DDModule – DDOO – 对象 的调用链。

DDModule.h

public:

	// 外部方法单纯获取资源链接
	FWealthURL* GainWealthURL(FName WealthName);
	
	void GainWealthURL(FName WealthKind, TArray<FWealthURL*>& OutURL);

DDModule.cpp

FWealthURL* UDDModule::GainWealthURL(FName WealthName)
{
	return Wealth->GainWealthURL(WealthName);
}

void UDDModule::GainWealthURL(FName WealthKind, TArray<FWealthURL*>& OutURL)
{
	Wealth->GainWealthURL(WealthKind, OutURL);
}

DDOO.h

protected:

	// 外部方法单纯获取资源链接
	FWealthURL* GainWealthURL(FName WealthName);
	
	void GainWealthURL(FName WealthKind, TArray<FWealthURL*>& OutURL);

DDOO.cpp

FWealthURL* IDDOO::GainWealthURL(FName WealthName)
{
	return IModule->GainWealthURL(WealthName);
}

void IDDOO::GainWealthURL(FName WealthKind, TArray<FWealthURL*>& OutURL)
{
	IModule->GainWealthURL(WealthKind, OutURL);
}

如果编译通过则说明写好了,现在所有的对象都可以通过这两个方法获取目标资源的 URL。刚刚写的这些代码结构也比较简单,此处就跳过验证环节。

实现异步加载单个 UObject 类型资源

异步加载需要用到引擎提供的 StreamableManager,所以我们声明一个存放 Object 资源数据的结构体,里面还包含着 FStreamableHandle 句柄,以便参与异步加载。

在 DDWealth 内的逻辑如下:结构体作为加载节点,并且声明一个它的数组作为加载节点队列。Tick() 内检测到队列内的节点是否已经加载完毕,是则将其从队列里删除。对象只需要调用 LoadObjectWealth() 就可以开始异步加载,并且生成目标 Object 资源的加载节点后将其放入队列。

DDWealth.h

#include "Engine/StreamableManager.h"		// 引入头文件
#include "DDWealth.generated.h"

// 加载单个 Object 资源的节点
struct ObjectSingleLoadNode;

UCLASS()
class DATADRIVEN_API UDDWealth : public UObject, public IDDMM
{
	GENERATED_BODY()

public:

	// 加载 Object 类型资源接口
	void LoadObjectWealth(FName WealthName, FName ObjectName, FName FunName);

	// 加载 Object 类型的同种类的所有资源
	void LoadObjectWealthKind(FName WealthKind, FName ObjectName, FName FunName);

protected:

	// 获取 Object 资源结构体
	FObjectWealthEntry* GetObjectSingleEntry(FName WealthName);
	
	TArray<FObjectWealthEntry*> GetObjectKindEntry(FName WealthKind);

	// 处理加载单个 Object 节点的方法,放在 Tick() 里
	void DealObjectSingleLoadStack();

protected:

	// 加载器,用于异步加载
	FStreamableManager WealthLoader;


	// 加载节点队列
	TArray<ObjectSingleLoadNode*> ObjectSingleLoadStack;

protected:

	// 加载 UObject 反射回调函数,方便返回已经生成的资源
	DDOBJFUNC_TWO(BackObjectWealth, FName, BackName, UObject*, BackWealth);

	DDOBJFUNC_TWO(BackObjectWealthKind, TArray<FName>, BackNames, TArray<UObject*>, BackWealths);
};

DDWealth.cpp

struct ObjectSingleLoadNode
{
	// 加载句柄
	TSharedPtr<FStreamableHandle> WealthHandle;
	// 资源结构体
	FObjectWealthEntry* WealthEntry;
	// 请求对象名
	FName ObjectName;
	// 回调方法名
	FName FunName;
	// 构造函数
	ObjectSingleLoadNode(TSharedPtr<FStreamableHandle> InWealthHandle, FObjectWealthEntry* InWealthEntry, FName InObjectName, FName InFunName)
	{
		WealthHandle = InWealthHandle;
		WealthEntry = InWealthEntry;
		ObjectName = InObjectName;
		FunName = InFunName;
	}
};


void UDDWealth::WealthTick(float DeltaSeconds)
{
	// 在 Tick() 里检查队列中的加载节点是否完成
	DealObjectSingleLoadStack();
}


void UDDWealth::LoadObjectWealth(FName WealthName, FName ObjectName, FName FunName)
{
	// 获取资源结构体
	FObjectWealthEntry* WealthEntry = GetObjectSingleEntry(WealthName);
	// 如果没有这个名字对应的资源结构体
	if (!WealthEntry) {
		DDH::Debug() << ObjectName << " Get Null Wealth : " << WealthName << DDH::Endl();
		return;
	}
	// 如果资源不可用
	if (!WealthEntry->WealthPath.IsValid()) {
		DDH::Debug() << ObjectName << " Get UnValid Wealth : " << WealthName << DDH::Endl();
		return;
	}
	// 如果资源已经加载
	if (WealthEntry->WealthObject) {
		// 直接返回已经存在的资源给对象(整个 BackObjectWealth 方法已经由反射系统的宏生成)
		BackObjectWealth(ModuleIndex, ObjectName, FunName, WealthName, WealthEntry->WealthObject);
	}
	else {
		// 进行异步加载
		TSharedPtr<FStreamableHandle> WealthHandle = WealthLoader.RequestAsyncLoad(WealthEntry->WealthPath);
		// 添加新节点到加载序列
		ObjectSingleLoadStack.Push(new ObjectSingleLoadNode(WealthHandle, WealthEntry, ObjectName, FunName));
	}
}

// 批量加载同种类 UObject 暂时先不写,留到下一节课
void UDDWealth::LoadObjectWealthKind(FName WealthKind, FName ObjectName, FName FunName)
{
}

FObjectWealthEntry* UDDWealth::GetObjectSingleEntry(FName WealthName)
{
	for (int i = 0; i < WealthData.Num(); ++i) {
		for (int j = 0; j < WealthData[i]->ObjectWealthData.Num(); ++j) {
			if (WealthData[i]->ObjectWealthData[j].WealthName.IsEqual(WealthName))
				return &(WealthData[i]->ObjectWealthData[j]);
		}
	}
	return NULL;
}
	
TArray<FObjectWealthEntry*> UDDWealth::GetObjectKindEntry(FName WealthKind)
{
	TArray<FObjectWealthEntry*> WealthGroup;
	for (int i = 0; i < WealthData.Num(); ++i) {
		for (int j = 0; j < WealthData[i]->ObjectWealthData.Num(); ++j) {
			if (WealthData[i]->ObjectWealthData[j].WealthKind.IsEqual(WealthKind))
				WealthGroup.Push(&(WealthData[i]->ObjectWealthData[j]));
		}
	}
	return WealthGroup;
}

void UDDWealth::DealObjectSingleLoadStack()
{
	// 定义加载完成的序列
	TArray<ObjectSingleLoadNode*> CompleteStack;
	for (int i = 0; i < ObjectSingleLoadStack.Num(); ++i) {
		// 判断是否已经加载完成
		if (ObjectSingleLoadStack[i]->WealthHandle->HasLoadCompleted()) {
			// 设置对应资源赋值给 WealthObject
			ObjectSingleLoadStack[i]->WealthEntry->WealthObject = ObjectSingleLoadStack[i]->WealthEntry->WealthPath.ResolveObject();
			// 返回资源给对象
			BackObjectWealth(ModuleIndex, ObjectSingleLoadStack[i]->ObjectName, ObjectSingleLoadStack[i]->FunName, ObjectSingleLoadStack[i]->WealthEntry->WealthName, ObjectSingleLoadStack[i]->WealthEntry->WealthObject);
			// 添加已经加载完成的节点到临时序列
			CompleteStack.Push(ObjectSingleLoadStack[i]);
		}
	}
	// 销毁已经完成的节点
	for (int i = 0; i < CompleteStack.Num(); ++i) {
		// 移除出节点序列
		ObjectSingleLoadStack.Remove(CompleteStack[i]);
		// 释放内存
		delete CompleteStack[i];
	}
}

剩余部分我们留到下一节课来实现。

44. 批量加载 UObject 功能

测试加载单个 UObject 资源

依旧是建立 DDWealth – DDModule – DDOO – 对象 的调用链。

虽然我们还没写批量加载同种类 UObject 资源的逻辑,但是也可以顺便补充上这个调用链。

DDModule.h

public:

	// 加载 Object 类型资源接口
	void LoadObjectWealth(FName WealthName, FName ObjectName, FName FunName);

	void LoadObjectWealthKind(FName WealthKind, FName ObjectName, FName FunName);

DDModule.cpp

void UDDModule::LoadObjectWealth(FName WealthName, FName ObjectName, FName FunName)
{
	Wealth->LoadObjectWealth(WealthName, ObjectName, FunName);
}

void UDDModule::LoadObjectWealthKind(FName WealthKind, FName ObjectName, FName FunName)
{
	Wealth->LoadObjectWealthKind(WealthKind, ObjectName, FunName);
}

DDOO 不需要传 ObjectName,因为它本身保存着这个变量。

DDOO.h

protected:

	// 加载 Object 类型资源接口
	void LoadObjectWealth(FName WealthName, FName FunName);

	void LoadObjectWealthKind(FName WealthKind, FName FunName);

DDOO.cpp

void IDDOO::LoadObjectWealth(FName WealthName, FName FunName)
{
	IModule->LoadObjectWealth(WealthName, GetObjectName(), FunName);
}

void IDDOO::LoadObjectWealthKind(FName WealthKind, FName FunName)
{
	IModule->LoadObjectWealthKind(WealthKind, GetObjectName(), FunName);
}

在项目的 .Bulid.cs 文件里添加对 UMG 的依赖。因为我们打算在 Widget 里放一个 Image 控件来让其显示加载的图片资源,作为功能的验证过程。

RaceCarFrame.Build.cs

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", 
	"Engine", "InputCore", 
	"PhysXVehicles", "HeadMountedDisplay", 
	"DataDriven", "UMG" });		// 添加依赖

打开项目,准备测试上面写的异步加载单个 UObject 功能。我们打算异步加载一张图片资源,并将其应用到一个 Image 控件上。

创建一个以 DDUserWidget 为基类的 C++ 类,目标模组为项目,命名为 LoadWealthWidget。创建完毕后进行代码编译。

在 Blueprint 文件夹下,基于 LoadWealthWidget 创建一个蓝图,命名为 LoadWealthWidget_BP

把 HUDData 里,之前配置的目标 Widget 换成 LoadWealthWidget_BP,Object Name 改成 LoadWealthWidget。

将 LoadWealthWidget_BP 修改成如下图:

在这里插入图片描述
随后在 LoadWealthWidget 里添加加载 UObject 资源的逻辑,并且将加载到的图片资源放进 Image 里。

LoadWealthWidget.h

class UImage;

UCLASS()
class RACECARFRAME_API ULoadWealthWidget : public UDDUserWidget
{
	GENERATED_BODY()

public:

	virtual void DDInit() override;

	virtual void DDLoading() override;

	UFUNCTION()
	void LoadSingleTexture(FName BackName, UObject* BackWealth);

public:

	UPROPERTY(Meta = (BindWidget))
	UImage* ViewImage;
};

LoadWealthWidget.cpp

#include "Components/Image.h"	// 引入头文件

void ULoadWealthWidget::DDInit()
{
	Super::DDInit();

	AddToViewport(0);
}

void ULoadWealthWidget::DDLoading()
{
	Super::DDLoading();

	// 调用加载资源的方法,并且回调函数会被调用
	LoadObjectWealth("ViewImage1", "LoadSingleTexture");
}

void ULoadWealthWidget::LoadSingleTexture(FName BackName, UObject* BackWealth)
{
	ViewImage->SetBrushFromTexture(Cast<UTexture2D>(BackWealth));
}

编译后运行,可看见左上角显示如下图:

在这里插入图片描述
这是因为我们没有设置这个 “ViewImage1” 名称对应的目标图片资源。再次打开 HUDData,设置如下:

在这里插入图片描述
再次运行,可见左上角的 Image 配置上了相应的图片。说明异步加载单个 UObject 资源的逻辑写好了。

在这里插入图片描述

批量加载多个同类的 UObject 资源

逻辑其实跟加载单个 UObject 资源差不多,只是读取和加载利用 for 循环执行多次。

新声明一个 UObject 种类加载节点,里面要声明存放多个资源结构体的数组。

DDWealth.h

struct ObjectKindLoadNode;	// 添加结构体声明

UCLASS()
class DATADRIVEN_API UDDWealth : public UObject, public IDDMM
{
	GENERATED_BODY()
	
protected:

	// 处理批量加载 Object 节点的方法
	void DealObjectKindLoadStack();

protected:

	TArray<ObjectKindLoadNode*> ObjectKindLoadStack;
};

DDWealth.cpp


struct ObjectKindLoadNode
{
	// 加载句柄
	TSharedPtr<FStreamableHandle> WealthHandle;
	// 没有加载的资源
	TArray<FObjectWealthEntry*> UnLoadWealthEntry;
	// 已经加载的资源的数组
	TArray<FObjectWealthEntry*> LoadWealthEntry;
	// 请求对象名
	FName ObjectName;
	// 回调方法名
	FName FunName;
	// 构造函数
	ObjectKindLoadNode(TSharedPtr<FStreamableHandle> InWealthHandle, TArray<FObjectWealthEntry*> InUnLoadWealthEntry, TArray<FObjectWealthEntry*>& InLoadWealthEntry, FName InObjectName, FName InFunName)
	{
		WealthHandle = InWealthHandle;
		UnLoadWealthEntry = InUnLoadWealthEntry;
		LoadWealthEntry = InLoadWealthEntry;
		ObjectName = InObjectName;
		FunName = InFunName;
	}
};

void UDDWealth::WealthTick(float DeltaSeconds)
{

	DealObjectKindLoadStack();	// 添加到 Tick()
}

void UDDWealth::LoadObjectWealthKind(FName WealthKind, FName ObjectName, FName FunName)
{
	TArray<FObjectWealthEntry*> WealthEntryGroup = GetObjectKindEntry(WealthKind);
	// 如果数量为 0
	if (WealthEntryGroup.Num() == 0) {
		DDH::Debug() << ObjectName << " Get Null WealthKind : " << WealthKind << DDH::Endl();
		return;
	}
	// 判断资源可用性
	for (int i = 0; i < WealthEntryGroup.Num(); ++i) {
		if (!WealthEntryGroup[i]->WealthPath.IsValid()) {
			DDH::Debug() << ObjectName << " Get Not Valid in Kind : " << WealthKind << " For Name : " << WealthEntryGroup[i]->WealthName << DDH::Endl();
			return;
		}
	}
	// 还没有加载的资源
	TArray<FObjectWealthEntry*> UnLoadWealthEntry;
	// 已经加载的资源
	TArray<FObjectWealthEntry*> LoadWealthEntry;
	// 资源加载与否归类
	for (int i = 0; i< WealthEntryGroup.Num(); ++i) {
		if (WealthEntryGroup[i]->WealthObject)
			LoadWealthEntry.Push(WealthEntryGroup[i]);
		else
			UnLoadWealthEntry.Push(WealthEntryGroup[i]);
	}
	// 如果未加载的资源为 0
	if (UnLoadWealthEntry.Num() == 0) {
		// 直接获取所有资源给请求对象
		TArray<FName> NameGroup;
		TArray<UObject*> WealthGroup;
		for (int i = 0; i < LoadWealthEntry.Num(); ++i) {
			NameGroup.Push(LoadWealthEntry[i]->WealthName);
			WealthGroup.Push(LoadWealthEntry[i]->WealthObject);
		}
		BackObjectWealthKind(ModuleIndex, ObjectName, FunName, NameGroup, WealthGroup);
	}
	else {
		// 获取资源路径
		TArray<FSoftObjectPath> WealthPaths;
		for (int i = 0; i < UnLoadWealthEntry.Num(); ++i)
			WealthPaths.Push(UnLoadWealthEntry[i]->WealthPath);
		// 进行异步加载获取句柄
		TSharedPtr<FStreamableHandle> WealthHandle = WealthLoader.RequestAsyncLoad(WealthPaths);
		// 生成加载节点
		ObjectKindLoadStack.Push(new ObjectKindLoadNode(WealthHandle, UnLoadWealthEntry, LoadWealthEntry, ObjectName, FunName));
	}
}

void UDDWealth::DealObjectKindLoadStack()
{
	// 定义加载完成的序列
	TArray<ObjectKindLoadNode*> CompleteStack;
	for (int i = 0; i < ObjectKindLoadStack.Num(); ++i) {
		// 判断是否已经加载完成
		if (ObjectKindLoadStack[i]->WealthHandle->HasLoadCompleted()) {
			// 返回资源参数
			TArray<FName> NameGroup;
			TArray<UObject*> WealthGroup;
			// 填充已加载资源
			for (int j = 0; j < ObjectKindLoadStack[i]->LoadWealthEntry.Num(); ++j) {
				NameGroup.Push(ObjectKindLoadStack[i]->LoadWealthEntry[j]->WealthName);
				WealthGroup.Push(ObjectKindLoadStack[i]->LoadWealthEntry[j]->WealthObject);
			}
			// 遍历设置所有未加载资源结构体为已加载状态
			for (int j = 0; j < ObjectKindLoadStack[i]->UnLoadWealthEntry.Num(); ++j) {
				ObjectKindLoadStack[i]->UnLoadWealthEntry[j]->WealthObject = ObjectKindLoadStack[i]->UnLoadWealthEntry[j]->WealthPath.ResolveObject();
				// 填充已加载资源
				NameGroup.Push(ObjectKindLoadStack[i]->UnLoadWealthEntry[j]->WealthName);
				WealthGroup.Push(ObjectKindLoadStack[i]->UnLoadWealthEntry[j]->WealthObject);
			}
			// 返回数据给请求对象
			BackObjectWealthKind(ModuleIndex, ObjectKindLoadStack[i]->ObjectName, ObjectKindLoadStack[i]->FunName, NameGroup, WealthGroup);
			// 添加节点到已完成序列
			CompleteStack.Push(ObjectKindLoadStack[i]);
		}
	}
	// 销毁已经完成的节点
	for (int i = 0; i < CompleteStack.Num(); ++i) {
		// 移除出节点序列
		ObjectKindLoadStack.Remove(CompleteStack[i]);
		// 释放内存
		delete CompleteStack[i];
	}
}

验证部分我们留到下一节课。

45. 单个加载 UClass 功能

测试加载多个 UObject 资源

来到 LoadWealthWidget,我们打算异步加载多张图片资源,然后用之前的延时系统将图片每秒一张地换到 Image 控件上。

LoadWealthWidget.h

class UTexture2D;

UCLASS()
class RACECARFRAME_API ULoadWealthWidget : public UDDUserWidget
{
	GENERATED_BODY()
	
public:

	// 资源加载的回调函数
	UFUNCTION()
	void LoadKindTexture(TArray<FName> BackNames, TArray<UObject*> BackWealths);

	// 供延时系统使用的切换图片方法
	void ChangeImage();

public:

	int32 ImageIndex;

	TArray<UTexture2D*> TextureGroup;
};

LoadWealthWidget.cpp

void ULoadWealthWidget::DDLoading()
{
	Super::DDLoading();

	//LoadObjectWealth("ViewImage1", "LoadSingleTexture");

	// 测试完记得注释掉
	LoadObjectWealthKind("ViewImage", "LoadKindTexture");
}

void ULoadWealthWidget::LoadKindTexture(TArray<FName> BackNames, TArray<UObject*> BackWealths)
{
	for (int i = 0; i < BackWealths.Num(); ++i) {
		// 输出所有获取到的资源的名字
		DDH::Debug() << BackNames[i] << DDH::Endl();
		TextureGroup.Push(Cast<UTexture2D>(BackWealths[i]));
	}

	ImageIndex = 0;

	InvokeRepeat("ChangeImage", 1.f, 1.f, this, &ULoadWealthWidget::ChangeImage);
}

void ULoadWealthWidget::ChangeImage()
{
	ViewImage->SetBrushFromTexture(TextureGroup[ImageIndex]);

	ImageIndex = ImageIndex + 1 >= TextureGroup.Num() ? 0 : ImageIndex + 1;
}

编译后,给 HUDData 中配置更多的图片,并且将它们的 WealthKind 设置成同名,老师配置了 11 张,此处就截图 4 张以作示例:

在这里插入图片描述
运行游戏,可以看见左上角正在每秒一张地轮播刚刚配置的图片,并且输出了 11 张图片的 Wealth Name。我们还可以发现它需要一定的时间(一开始 Image 控件为空白)来进行异步加载。

在这里插入图片描述

异步加载单个 UClass 类型的资源

加载 UClass 类型资源的逻辑跟加载 UObject 差不多,区别在于:

UObject 的资源链接用的是 FStringAssetReference;UClass 的是 TSoftClassPtr,它需要通过 ToSoftObjectPath() 转换成 FSoftObjectPath 才能参与到 UClass 类型的异步加载中。

DDWealth.h

// 加载单个 Class
struct ClassSingleLoadNode;

UCLASS()
class DATADRIVEN_API UDDWealth : public UObject, public IDDMM
{
	GENERATED_BODY()

public:

	// 加载 Class 类型资源接口
	void LoadClassWealth(FName WealthName, FName ObjectName, FName FunName);

	void LoadClassWealthKind(FName WealthKind, FName ObjectName, FName FunName);

protected:

	
	// 获取 Class 资源结构体
	FClassWealthEntry* GetClassSingleEntry(FName WealthName);

	TArray<FClassWealthEntry*> GetClassKindEntry(FName WealthKind);



	// 处理加载单个 Class 节点的方法
	void DealClassSingleLoadStack();

protected:

	TArray<ClassSingleLoadNode*> ClassSingleLoadStack;

protected:

	// 加载 UClass 反射回调函数
	DDOBJFUNC_TWO(BackClassWealth, FName, BackName, UClass*, BackWealth);
	
	DDOBJFUNC_TWO(BackClassWealthKind, TArray<FName>, BackNames, TArray<UClass*>, BackWealths);
};

DDWealth.cpp

struct ClassSingleLoadNode
{
	// 加载句柄
	TSharedPtr<FStreamableHandle> WealthHandle;
	// 资源结构体
	FClassWealthEntry* WealthEntry;
	// 请求对象名
	FName ObjectName;
	// 回调方法名
	FName FunName;
	// 构造函数
	ClassSingleLoadNode(TSharedPtr<FStreamableHandle> InWealthHandle, FClassWealthEntry* InWealthEntry, FName InObjectName, FName InFunName)
	{
		WealthHandle = InWealthHandle;
		WealthEntry = InWealthEntry;
		ObjectName = InObjectName;
		FunName = InFunName;
	}
};

void UDDWealth::WealthTick(float DeltaSeconds)
{
	DealObjectSingleLoadStack();
	DealObjectKindLoadStack();
	DealClassSingleLoadStack();		// 每帧调用
}

void UDDWealth::LoadClassWealth(FName WealthName, FName ObjectName, FName FunName)
{
	// 获取资源结构体
	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->WealthClass) {
		// 直接把资源返回给申请对象
		BackClassWealth(ModuleIndex, ObjectName, FunName, WealthName, WealthEntry->WealthClass);
	}
	else {
		// 进行异步加载
		TSharedPtr<FStreamableHandle> WealthHandle = WealthLoader.RequestAsyncLoad(WealthEntry->WealthPtr.ToSoftObjectPath());
		// 添加节点
		ClassSingleLoadStack.Push(new ClassSingleLoadNode(WealthHandle, WealthEntry, ObjectName, FunName));
	}
}

// 批量加载 UClass 资源的逻辑先不写
void UDDWealth::LoadClassWealthKind(FName WealthKind, FName ObjectName, FName FunName)
{
}

FClassWealthEntry* UDDWealth::GetClassSingleEntry(FName WealthName)
{
	for (int i = 0; i < WealthData.Num(); ++i) {
		for (int j = 0; j < WealthData[i]->ClassWealthData.Num(); ++j) {
			if (WealthData[i]->ClassWealthData[j].WealthName.IsEqual(WealthName))
				return &(WealthData[i]->ClassWealthData[j]);
		}
	}
	return NULL;
}

TArray<FClassWealthEntry*> UDDWealth::GetClassKindEntry(FName WealthKind)
{
	TArray<FClassWealthEntry*> WealthGroup;
	for (int i = 0; i < WealthData.Num(); ++i) {
		for (int j = 0; j < WealthData[i]->ClassWealthData.Num(); ++j) {
			if (WealthData[i]->ClassWealthData[j].WealthKind.IsEqual(WealthKind))
				WealthGroup.Push(&(WealthData[i]->ClassWealthData[j]));
		}
	}
	return WealthGroup;
}

void UDDWealth::DealClassSingleLoadStack()
{
	// 定义加载完成的序列
	TArray<ClassSingleLoadNode*> CompleteStack;
	for (int i = 0; i < ClassSingleLoadStack.Num(); ++i) {
		// 判断是否已经加载完成
		if (ClassSingleLoadStack[i]->WealthHandle->HasLoadCompleted()) {
			// 设置对应资源完成
			ClassSingleLoadStack[i]->WealthEntry->WealthClass = Cast<UClass>(ClassSingleLoadStack[i]->WealthEntry->WealthPtr.ToSoftObjectPath().ResolveObject());
			// 返回资源给对象
			BackClassWealth(ModuleIndex, ClassSingleLoadStack[i]->ObjectName, ClassSingleLoadStack[i]->FunName, ClassSingleLoadStack[i]->WealthEntry->WealthName, ClassSingleLoadStack[i]->WealthEntry->WealthClass);
			// 添加已经加载完成的节点到临时序列
			CompleteStack.Push(ClassSingleLoadStack[i]);
		}
	}
	// 销毁已经完成的节点
	for (int i = 0; i < CompleteStack.Num(); ++i) {
		// 移除出节点序列
		ClassSingleLoadStack.Remove(CompleteStack[i]);
		// 释放内存
		delete CompleteStack[i];
	}
}

补充 DDWealth – DDModule – DDOO – 对象的调用链。

DDModule.h

public:

	// 加载 Class 类型资源接口
	void LoadClassWealth(FName WealthName, FName ObjectName, FName FunName);

	void LoadClassWealthKind(FName WealthKind, FName ObjectName, FName FunName);

DDModule.cpp

void UDDModule::LoadClassWealth(FName WealthName, FName ObjectName, FName FunName)
{
	Wealth->LoadClassWealth(WealthName, ObjectName, FunName);
}

void UDDModule::LoadClassWealthKind(FName WealthKind, FName ObjectName, FName FunName)
{
	Wealth->LoadClassWealthKind(WealthKind, ObjectName, FunName);
}

DDOO.h

protected:

	// 加载 Class 类型资源接口
	void LoadClassWealth(FName WealthName, FName FunName);

	void LoadClassWealthKind(FName WealthKind, FName FunName);

DDOO.cpp

void IDDOO::LoadClassWealth(FName WealthName, FName FunName)
{
	IModule->LoadClassWealth(WealthName, GetObjectName(), FunName);
}

void IDDOO::LoadClassWealthKind(FName WealthKind, FName FunName)
{
	IModule->LoadClassWealthKind(WealthKind, GetObjectName(), FunName);
}

接下来准备测试一下加载单个 UClass 类型资源的逻辑。我们打算让一个普通的 Actor 被加载到场景中。而调用异步加载 UClass 资源的方法就让 WealthCallObject(它在 42 集开头通过配置在 PlayerData 里来生成)来调用。

WealthCallObject.h

public:

	virtual void DDLoading() override;

	// 回调函数
	UFUNCTION()
	void LoadActorClass(FName BackName, UClass* BackWealth);

public:

	// 生成位置
	UPROPERTY(EditAnywhere)
	FTransform ViewTrans;

WealthCallObject.cpp

void UWealthCallObject::DDLoading()
{
	Super::DDLoading();

	// 测试完后记得注释掉
	LoadClassWealth("ViewActor1", "LoadActorClass");
}

void UWealthCallObject::LoadActorClass(FName BackName, UClass* BackWealth)
{
	GetDDWorld()->SpawnActor<AActor>(BackWealth, ViewTrans);
}

编译后打开 WealthCallObject 蓝图,设置位置如下:

在这里插入图片描述
在 Blueprint 文件夹下创建一个基于 Actor 的蓝图,命名为 ViewActor1,给它添加一个网格体:

在这里插入图片描述

打开 PlayerData,配置如下:

在这里插入图片描述
运行游戏,可以看见目标地点生成了 ViewActor1。说明异步加载单个 UClass 类型资源的逻辑写好了。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Win10系统读不出U盘的四种解决方法

有用户特别喜欢用U盘来保存重要的内容&#xff0c;但有用户反映自己的Win10电脑读取不了U盘&#xff0c;这样用户就不能将Win10电脑上的内容传输到U盘了。下面小编带来四种简单有效的解决方法&#xff0c;解决后Win10电脑上的U盘就能被正常识别&#xff0c;从而恢复对U盘的使用…

【Linux笔记】进程等待与程序替换

一、进程的终止 1、进程退出码 在讲解进程的终止之前&#xff0c;先要普及一下进程的退出码概念。 我们父进程之所以要创建子进程&#xff0c;就是为了让子进程运行不一样的任务&#xff0c;那么对于子进程执行的这个任务执行完毕后的结果是否正确或者是否出差错&#xff0c…

QT上位机开发(利用tcp/ip访问plc)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 plc是工控领域很重要的一个器件。简单的plc一般就是对io进行控制&#xff0c;但是复杂的plc&#xff0c;还可以控制电机、变频器&#xff0c;在工业…

在CentOS上设置和管理静态HTTP网站的版本控制

在CentOS上设置和管理静态HTTP网站的版本控制是一项重要的任务&#xff0c;它可以帮助您跟踪和回滚对网站所做的更改&#xff0c;确保数据的一致性和完整性。以下是在CentOS上设置和管理静态HTTP网站的版本控制的步骤&#xff1a; 安装版本控制系统在CentOS上安装Git或其他版本…

打铁需要自身硬,我敢和欧系谬论硬刚源自实力与信心

我揭露欧系数学荒谬的目的是驱逐纯粹数学出中国&#xff0c;以恢复中华数学体系、最终让中华数学领导世界&#xff1b;我从来不隐瞒自己的“野心”&#xff0c;我对此有着绝对的信心。民族情怀是中国数学人的短板 纯粹数学是欧洲人的文化、是欧系数学的主体&#xff0c;它的历…

CMake+大漠插件的应用开发——处理dm.dll,免注册调用大漠插件

文章目录 CMake大漠插件的应用开发——处理dm.dll&#xff0c;免注册调用大漠插件说明环境项目结构配置编译环境编码-直接调用 dll编码-生成tlh文件&#xff0c;便于提示 CMake大漠插件的应用开发——处理dm.dll&#xff0c;免注册调用大漠插件 说明 网上有一种使用方式是&am…

SSM整合(实现简单查询功能)

在名为ssm的数据库内创建表 CREATE TABLE account (id int(11) NOT NULL AUTO_INCREMENT,name varchar(20) DEFAULT NULL,money double DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8; 创建工程 pom.xml <?xml version"1.0" encoding&quo…

野牛物联网-阿里云配置流程

1、 概述&#xff1a; 本文围绕阿里云物联网平台&#xff0c;实现设备上云、设备上报消息、云端订阅设备消息、云端下发指令到设备等服务&#xff0c;以野牛物联网YNK-MN316设备接入物联网平台为例&#xff0c;介绍设备如何接入物联网平台&#xff0c;向平台上报消息等。帮助您…

MySQL中约束是什么?

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;重拾MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出现错误&am…

MetaGPT前期准备与快速上手

大家好&#xff0c;MetaGPT 是基于大型语言模型&#xff08;LLMs&#xff09;的多智能体协作框架&#xff0c;GitHub star数量已经达到31.3k。 接下来我们聊一下快速上手 这里写目录标题 一、环境搭建1.python 环境2. MetaGpt 下载 二、MetaGPT配置1.调用 ChatGPT API 服务2.简…

写在学习webkit过程的前面

webkit起源于KHTML&#xff0c;是KDE开源项目的KHTML和KJS引擎的一部分。在它的诞生和发展过程中&#xff0c;由两家著名的公司参与开发过程中&#xff0c;造成两次裂变。诞生两个内核webkit和blink&#xff0c;并发展和产生了两个主流的浏览器&#xff0c;分别为safari和chrom…

全网快递查询工具:批量查询,提升工作效率的利器

在快递行业日新月异的今天&#xff0c;高效、准确的快递信息管理显得尤为重要。固乔快递查询助手正是一款专为快递网点设计的实用工具&#xff0c;它可以帮助您快速、批量查询全网快递单号&#xff0c;为您的网点运营带来诸多便利。 一、固乔快递查询助手的用途 批量查询&…

跨境商城系统如何开发代购商城、国际物流、一件代发等功能?

跨境商城系统的开发涉及到多个方面&#xff0c;其中代购商城、国际物流和一件代发等功能是其中的重要组成部分。本文将详细介绍如何开发这些功能&#xff0c;以帮助跨境商城系统更好地满足市场需求。 一、代购商城的开发 代购商城是跨境商城系统中的重要功能之一&#xff0c;它…

FilterQuery过滤查询

ES中的查询操作分为两种&#xff1a;查询和过滤。查询即是之前提到的query查询&#xff0c;它默认会计算每个返回文档的得分&#xff0c;然后根据得分排序。而过滤只会筛选出符合条件的文档&#xff0c;并不计算得分&#xff0c;并且可以缓冲记录。所以我们在大范围筛选数据时&…

阅读笔记lv.1

阅读笔记 sql中各种 count结论不同存储引擎计算方式区别count() 类型 责任链模式常见场景例子&#xff08;闯关游戏&#xff09; sql中各种 count 结论 innodb count(*) ≈ count(1) > count(主键id) > count(普通索引列) > count(未加索引列)myisam 有专门字段记录…

Python 全栈体系【四阶】(十二)

第四章 机器学习 十五、朴素贝叶斯 朴素贝叶斯是一组功能强大且易于训练的分类器&#xff0c;它使用贝叶斯定理来确定给定一组条件的结果的概率&#xff0c;“朴素”的含义是指所给定的条件都能独立存在和发生。朴素贝叶斯是多用途分类器&#xff0c;能在很多不同的情景下找到…

leetcode17 电话号码的字母组合

方法1 if-else方法 if-else方法的思路及其简单粗暴&#xff0c;如下图所示&#xff0c;以数字234为例&#xff0c;数字2所对应的字母是abc&#xff0c;数字3所对应的是def&#xff0c;数字4所对应的是ghi&#xff0c;最后所产生的结果就类似于我们中学所学过的树状图一样&…

跟着cherno手搓游戏引擎【4】窗口抽象、GLFW配置

引入GLFW&#xff1a; 在vendor里创建GLFW文件夹&#xff1a; 在github上下载&#xff0c;把包下载到GLFW包下。 GitHub - TheCherno/glfw: A multi-platform library for OpenGL, OpenGL ES, Vulkan, window and input修改SRC/premake5.lua的配置&#xff1a;12、13、15、36…

异地快速传输大文件的常用方法

在企业间的信息沟通与协作中&#xff0c;快速传输大文件是一项基本需求。然而&#xff0c;跨地域传输庞大文件时&#xff0c;往往面临着网络带宽、文件大小、传输速度以及数据安全等多方面的挑战。本文将介绍四种常用的异地快速传输大文件的方法&#xff0c;并分析它们的优缺点…

数据结构学习之顺序栈应用的案例(有效的括号)

实例要求&#xff1a; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效&#xff1b; 有效字符串需满足的条件&#xff1a; 1、左括号必须用相同类型的右括号闭合&#xff1b; 2、左括号必须…