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

news2024/9/22 19:20:35

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.coloradmin.cn/o/1360914.html

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

相关文章

跟着小德学C++之日志记录

嗨&#xff0c;大家好&#xff0c;我是出生在达纳苏斯的一名德鲁伊&#xff0c;我是要立志成为海贼王&#xff0c;啊不&#xff0c;是立志成为科学家的德鲁伊。最近&#xff0c;我发现我们所处的世界是一个虚拟的世界&#xff0c;并由此开始&#xff0c;我展开了对我们这个世界…

VS2017 搭建opencv工程

VS2017 搭建opencv工程 opencv在处理图像方面具有很强的能力&#xff0c;在使用opencv之前先需要造好轮子。 1、opencv 官网 &#xff0c;下载对应的资源文件包。 根据自身选择。下载包之后&#xff0c;解压。分为build和sources source目录下分别存放&#xff1a; modules: …

Java面试项目推荐,异构数据源数据流转服务DatalinkX

前言 作为一个年迈的夹娃练习生&#xff0c;每次到了春招秋招面试实习生时都能看到一批简历&#xff0c;十个简历里得有七八个是写商城或者外卖项目。 不由得想到了我大四那会&#xff0c;由于没有啥项目经验&#xff0c;又想借一个质量高点的项目通过简历初筛&#xff0c;就…

Pytest的测试报告——Allure

一、html-report测试报告 html-report测试报告。是pytest下基本的测试报告。要使用pytest-html测试报告&#xff0c;就要确保python版本在3.6及以上即可。本身pytest所提供的测试结果汇总&#xff0c;是基于控制台的文本输出形式。 pytest-html是基于HTML格式实现的测试报告的…

三、C语言分支与循环知识点补充——随机数生成

本章分支结构的学习内容如下&#xff1a; 三、C语言中的分支与循环—if语句 (1) 三、C语言中的分支与循环—关系操作符 (2) 三、C语言中的分支与循环—条件操作符 与逻辑操作符(3) 三、C语言中的分支与循环—switch语句&#xff08;4&#xff09;分支结构 完 本章循环结构的…

Python 中的==操作符 和 is关键字

Python是一种功能强大的通用编程语言&#xff0c;提供了各种比较值和对象的方法。其中包括操作符和is关键字&#xff0c;它们的用途不同&#xff0c;但由于它们有时可以达到相同的目的&#xff0c;所以经常会被混淆。在本文中&#xff0c;我们将深入研究和is之间的区别&#xf…

解决:ModuleNotFoundError: No module named ‘bs4’

解决&#xff1a;ModuleNotFoundError: No module named ‘bs4’ 文章目录 解决&#xff1a;ModuleNotFoundError: No module named bs4背景报错问题报错翻译报错位置代码报错原因解决方法方法一&#xff0c;直接安装方法二&#xff0c;手动下载安装方法三&#xff0c;编译安装…

odoo17 | 视图字段验证约束

前言 前一章介绍了向模型添加一些业务逻辑的能力。现在我们可以将按钮链接到业务代码&#xff0c;但是我们如何防止用户输入错误的数据呢?例如&#xff0c;在我们的房地产模块中&#xff0c;没有什么可以阻止用户设置负预期价格。 Odoo提供了两种方法来设置自动验证的不变量…

Spring学习 Spring IOC

创建工程&#xff1a; 2.1.程序的耦合 耦合&#xff1a;耦合指的就是对象之间的依赖关系。对象之间的耦合越高&#xff0c;维护成本越高。 案例&#xff1a;没有引入IOC容器时系统的Web层、业务层、持久层存在耦合 /*** 持久层实现类*/ public class UserDaoImpl implements U…

如何实现无人机识别功能

无人机识别算法可以基于不同的传感器和技术&#xff0c;结合多种方法进行实现。以下是一些常见的无人机识别算法和技术&#xff1a; 视觉识别&#xff1a; 图像处理&#xff1a; 使用计算机视觉技术对无人机图像进行处理&#xff0c;包括特征提取、目标检测和跟踪等。深度学习&…

PHP语言B/S架构医院(安全)不良事件上报系统源码

医院安全&#xff08;不良&#xff09;事件上报系统采用无责的、自愿的填报不良事件方式&#xff0c;有效地减轻医护人员的思想压力&#xff0c;实现以事件为主要对象&#xff0c;可以自动、及时、实际地反应医院的安全、不良、近失事件的情况&#xff0c;更好地掌握不良事件的…

【python实战】python一行代码,实现文件共享服务器

一行代码实现文件共享 在一个局域网内&#xff0c;需要共享一个文件夹里内容。 我们可以在任意一台有python环境的电脑上&#xff0c;迅速架起一个http协议的服务&#xff0c;然后将文件夹里的文件内容共享出来。是的仅仅需要一行代码 就是这么简单 把电脑的相关项目文件通…

2023 hnust 湖南科技大学 大四上 商务智能 课程 期末考试 复习资料

前言 《听了课就能及格》由于老师发的复习PPT内容过多&#xff08;近两万字&#xff09;&#xff0c;因此在此大幅删减由于老师透露太少&#xff0c;删减全凭主观意志&#xff0c;请谨慎参考&#xff01;&#xff01;&#xff01;猜测逻辑 过于细碎概念的不考&#xff08;不算…

三、Kubernetes(K8s)入门(一)

视频教程连接k8s 入门到微服务项目实战.xmind链接&#xff1a;https://pan.baidu.com/s/1q04euH7baE8eXNyG3kPPbA 提取码&#xff1a;jej4比较好的笔记 kubectl命令的语法如下&#xff1a; kubectl [command] [type] [name] [flags]comand&#xff1a;指定要对资源执行的操作…

[足式机器人]Part2 Dr. CAN学习笔记-动态系统建模与分析 Ch02-6频率响应与滤波器

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-动态系统建模与分析 Ch02-6频率响应与滤波器 1st order system 一阶系统 低通滤波器——Loss Pass Filter

使用.Net nanoFramework获取ESP32板载按键的点击事件

本文以 ESP32-S3-Zero 板载的按键为例&#xff0c;介绍了GPIO的使用方法&#xff0c;以及如何获取按键的点击事件。板载按钮作为自带的天然用户按钮&#xff0c;除了其本身的功能外&#xff0c;也可以作为某些应用场景下的简单的交互方式。 1. 引言 对于一般的产品来说&#x…

在 wsl 中运用 kubeconfig 实现自由管理 kubernetes 集群

本文来自我的博客地址 文章目录 k8s 集群配置理解 kubeconfig思路整理:在 wsl 上安装 kubectl配置自动补全 拷贝 kubeconfig登到 k8s 集群的 master 节点, 把 kubeconfig 拷贝到 wsl测试 kubectl k8s 集群配置 IPHost配置11.0.1.150master1 (keepalivedhaproxy)2C 4G 30G11.0.…

前端--基础 常用标签 - 超链接标签 ( 内部链接,空链接,下载链接,网页元素连接)

链接分类 &#xff1a; 外部链接 内部链接 空链接 下载链接 网页元素链接 内部链接 &#xff1a; 即 网站内部页面之间的相互链接&#xff0c;直接点击 链接内部页面名称即可 所谓内部链接&#xff0c;就是在同一个网站里面&#xff0c;有许多链接&#xff0c;当你在 a…

【STM32】RTC实时时钟

1 unix时间戳 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数&#xff0c;不考虑闰秒 时间戳存储在一个秒计数器中&#xff0c;秒计数器为32位/64位的整型变量 世界上所有时区的秒计数器相同&#xff0c;不同时区…

MongoDB入门介绍与实战

目录 1. 什么是MongoDB&#xff1f; 2. 安装MongoDB 1. 离线安装 2. 使用docker-compose安装 3. mongo命令行实战 1. 连接到 MongoDB 数据库 2. 创建集合&#xff08;表&#xff09; 3. 插入数据 4. 查询数据 5. 更新数据 6. 删除数据 4. Springboot集成MongoDB实战…