当前位置: 首页 > news >正文

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

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

  • 46. 批量加载 UClass 功能
    • 测试批量加载多个同类 UClass 资源
  • 47. 创建单个资源对象
    • 测试加载并创建单个 UClass 资源对象
  • 48. 创建同类资源对象

46. 批量加载 UClass 功能

逻辑和批量加载同类 UObject 资源的逻辑差不多。区别在 DealClassKindLoadStack() 内,如果已经有资源率先加载完成了,那后续资源加载的途中我们想让已经加载好的资源执行额外的处理逻辑(比如让它每帧生成),我们就需要补充额外的判断条件,即判断其是否第一次生成完毕。

DDWealth.h

// 加载批量 Class
struct ClassKindLoadNode;UCLASS()
class DATADRIVEN_API UDDWealth : public UObject, public IDDMM
{GENERATED_BODY()protected:// 处理批量加载 Class 节点的方法void DealClassKindLoadStack();protected:TArray<ClassKindLoadNode*> ClassKindLoadStack;
};

DDWealth.cpp

struct ClassKindLoadNode
{// 加载句柄TSharedPtr<FStreamableHandle> WealthHandle;// 没有加载的资源TArray<FClassWealthEntry*> UnLoadWealthEntry;// 已经加载的资源的数组TArray<FClassWealthEntry*> LoadWealthEntry;// 请求对象名FName ObjectName;// 回调方法名FName FunName;// 构造函数ClassKindLoadNode(TSharedPtr<FStreamableHandle> InWealthHandle, TArray<FClassWealthEntry*> InUnLoadWealthEntry, TArray<FClassWealthEntry*>& InLoadWealthEntry, FName InObjectName, FName InFunName){WealthHandle = InWealthHandle;UnLoadWealthEntry = InUnLoadWealthEntry;LoadWealthEntry = InLoadWealthEntry;ObjectName = InObjectName;FunName = InFunName;}
};void UDDWealth::WealthTick(float DeltaSeconds)
{DealClassKindLoadStack();
}void UDDWealth::LoadClassWealthKind(FName WealthKind, FName ObjectName, FName FunName)
{TArray<FClassWealthEntry*> WealthEntryGroup = GetClassKindEntry(WealthKind);// 如果数量为 0if (WealthEntryGroup.Num() == 0) {DDH::Debug() << ObjectName << " Get Null WealthKind : " << WealthKind << DDH::Endl();return;}// 判断资源可用性for (int i = 0; i < WealthEntryGroup.Num(); ++i) {if (!WealthEntryGroup[i]->WealthPtr.ToSoftObjectPath().IsValid()) {DDH::Debug() << ObjectName << " Get Not Valid in Kind : " << WealthKind << " For Name : " << WealthEntryGroup[i]->WealthName << DDH::Endl();return;}}// 未加载资源序列TArray<FClassWealthEntry*> UnLoadWealthEntry;// 已加载资源序列TArray<FClassWealthEntry*> LoadWealthEntry;// 分类保存for (int i = 0; i < WealthEntryGroup.Num(); ++i) {if (WealthEntryGroup[i]->WealthClass)LoadWealthEntry.Push(WealthEntryGroup[i]);elseUnLoadWealthEntry.Push(WealthEntryGroup[i]);}// 判断所有资源是否都已经加载if (UnLoadWealthEntry.Num() == 0) {// 填充参数TArray<FName> NameGroup;TArray<UClass*> WealthGroup;for (int i = 0; i < LoadWealthEntry.Num(); ++i) {NameGroup.Push(LoadWealthEntry[i]->WealthName);WealthGroup.Push(LoadWealthEntry[i]->WealthClass);}// 返回资源给请求对象BackClassWealthKind(ModuleIndex, ObjectName, FunName, NameGroup, WealthGroup);}else {// 获取未加载资源路径数组TArray<FSoftObjectPath> WealthPaths;for (int i = 0; i < UnLoadWealthEntry.Num(); ++i)WealthPaths.Push(UnLoadWealthEntry[i]->WealthPtr.ToSoftObjectPath());// 进行异步加载获取句柄TSharedPtr<FStreamableHandle> WealthHandle = WealthLoader.RequestAsyncLoad(WealthPaths);// 添加新的加载节点ClassKindLoadStack.Push(new ClassKindLoadNode(WealthHandle, UnLoadWealthEntry, LoadWealthEntry, ObjectName, FunName));}
}void UDDWealth::DealClassKindLoadStack()
{// 定义已完成加载节点序列TArray<ClassKindLoadNode*> CompleteStack;for (int i = 0; i < ClassKindLoadStack.Num(); ++i) {// 判断第一次加载完成,WealthHandle 已经加载完成,UnLoadWealthEntry 数量大于 0if (ClassKindLoadStack[i]->WealthHandle->HasLoadCompleted() && ClassKindLoadStack[i]->UnLoadWealthEntry.Num() > 0) {// 如果已经加载完成,设置未加载序列的资源指针for (int j = 0; j < ClassKindLoadStack[i]->UnLoadWealthEntry.Num(); ++j)ClassKindLoadStack[i]->UnLoadWealthEntry[j]->WealthClass = Cast<UClass>(ClassKindLoadStack[i]->UnLoadWealthEntry[j]->WealthPtr.ToSoftObjectPath().ResolveObject());// 将未加载完成序列里的资源填充到已加载资源序列ClassKindLoadStack[i]->LoadWealthEntry.Append(ClassKindLoadStack[i]->UnLoadWealthEntry);// 清空 UnLoadWealthEntryClassKindLoadStack[i]->UnLoadWealthEntry.Empty();}// 如果未加载序列为 0,说明已经加载完成if (ClassKindLoadStack[i]->UnLoadWealthEntry.Num() == 0) {// 加载 UClass 或者直接生成资源的情况来处理// 设置反射参数TArray<FName> NameGroup;TArray<UClass*> WealthGroup;for (int j = 0; j < ClassKindLoadStack[i]->LoadWealthEntry.Num(); ++j) {NameGroup.Push(ClassKindLoadStack[i]->LoadWealthEntry[j]->WealthName);WealthGroup.Push(ClassKindLoadStack[i]->LoadWealthEntry[j]->WealthClass);}// 返回资源给请求对象BackClassWealthKind(ModuleIndex, ClassKindLoadStack[i]->ObjectName, ClassKindLoadStack[i]->FunName, NameGroup, WealthGroup);// 添加该节点到已完成序列CompleteStack.Push(ClassKindLoadStack[i]);}}// 清空已完成节点for (int i = 0; i < CompleteStack.Num(); ++i) {ClassKindLoadStack.Remove(CompleteStack[i]);delete CompleteStack[i];}
}

测试批量加载多个同类 UClass 资源

接下来是验证环节,我们打算让多个 Actor 生成在场景中,并且为了让它们不会挤到一块,所以每次生成的时候将位置偏移一点。

WealthCallObject.h

public:// 回调函数UFUNCTION()void LoadKindClass(TArray<FName> BackNames, TArray<UClass*> BackWealths);public:// 生成的偏移量UPROPERTY(EditAnywhere)float OffsetValue;

WealthCallObject.cpp


void UWealthCallObject::DDLoading()
{// 测试完毕后记得注释掉LoadClassWealthKind("ViewActor", "LoadKindClass");
}void UWealthCallObject::LoadKindClass(TArray<FName> BackNames, TArray<UClass*> BackWealths)
{for (int i = 0; i < BackWealths.Num(); ++i) {DDH::Debug() << BackNames[i] << DDH::Endl();GetDDWorld()->SpawnActor<AActor>(BackWealths[i], ViewTrans.GetLocation() + FVector(OffsetValue * i, 0.f, 0.f), FQuat::Identity.Rotator());}
}

编译后,在 Blueprint 文件夹下创建一个 ViewActor 文件夹,将 ViewActor1 放进去,并复制两个,分别取名为 ViewActor2ViewActor3。给它俩换一下网格体模型。

更改网格体模型
给 PlayerData 里再配置两个 Class 资源数据:

在这里插入图片描述
打开 WealthCallObject 的蓝图,设置 Offset Value 为 150。

运行游戏,可见左上角输出了 3 个 Class 资源的名字,场景内也出现了它们的实例。

在这里插入图片描述
在第一次运行后,在 Deal{xxx}LoadStack() 方法里会给加载节点里的 UObject* / UClass* 赋值,它们就不为空了,也就不会进行异步加载,为了让它每次都像第一次运行那样(为了方便测试异步加载无误),我们要修改一下代码,让它在编辑器运行时每次都清空 WealthObject 和 WealthClass。这样就以后打包完游戏就不会自动清空。

DDWealth.cpp

void UDDWealth::WealthBeginPlay()
{for (int i = 0; i < WealthData.Num(); ++i) {// ... 省略#if WITH_EDITOR	// 循环设置 WealthObject 和 WealthClass 为空,目的在于每次从编辑器启动游戏时,资源 Asset 的状态都重置for (int j = 0; j < WealthData[i]->ObjectWealthData.Num(); ++j)WealthData[i]->ObjectWealthData[j].WealthObject = NULL;for (int j = 0; j < WealthData[i]->ClassWealthData.Num(); ++j)WealthData[i]->ClassWealthData[j].WealthClass = NULL;
#endif}
}

47. 创建单个资源对象

我们先前写的逻辑都是加载资源然后将其返回给请求者,接下来我们打算实现:加载 UClass 资源并创建对象后,将对象返回给请求者。

下图截取自梁迪老师的 DataDriven 文档:

在这里插入图片描述
创建多个对象的方法里,每帧都会创建一个对象,创建足够数量的对象后才会将所有的对象返回给请求对象。

这节课我们先实现创建单个资源对象的功能。

DDWealth.h

public:// 创建一个对象实例void BuildSingleClassWealth(EWealthType WealthType, FName WealthName, FName ObjectName, FName FunName, FTransform SpawnTransform);protected:// 生成三种资源对象的反射回调函数DDOBJFUNC_TWO(BackObject, FName, BackName, UObject*, BackObject);DDOBJFUNC_TWO(BackActor, FName, BackName, AActor*, BackActor);DDOBJFUNC_TWO(BackWidget, FName, BackName, UUserWidget*, BackWidget);

DDWealth.cpp

// 给 UClass 加载节点补充另外的内容,以便支持对象创建
struct ClassSingleLoadNode
{TSharedPtr<FStreamableHandle> WealthHandle;FClassWealthEntry* WealthEntry;FName ObjectName;FName FunName;// 生成位置FTransform SpawnTransform;// 是否只加载 UClass 资源bool IsLoadClass;ClassSingleLoadNode(TSharedPtr<FStreamableHandle> InWealthHandle, FClassWealthEntry* InWealthEntry, FName InObjectName, FName InFunName){WealthHandle = InWealthHandle;WealthEntry = InWealthEntry;ObjectName = InObjectName;FunName = InFunName;IsLoadClass = true;		// 添加}// 另一个构造函数ClassSingleLoadNode(TSharedPtr<FStreamableHandle> InWealthHandle, FClassWealthEntry* InWealthEntry, FName InObjectName, FName InFunName, FTransform InSpawnTransform){WealthHandle = InWealthHandle;WealthEntry = InWealthEntry;ObjectName = InObjectName;FunName = InFunName;SpawnTransform = InSpawnTransform;IsLoadClass = false;}
};// 给处理方法增加判断,是仅仅加载资源还是创建对象
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());// 判断是否生成对象if (ClassSingleLoadStack[i]->IsLoadClass) {// 返回资源给对象BackClassWealth(ModuleIndex, ClassSingleLoadStack[i]->ObjectName, ClassSingleLoadStack[i]->FunName, ClassSingleLoadStack[i]->WealthEntry->WealthName, ClassSingleLoadStack[i]->WealthEntry->WealthClass);}else {// 生成相应类型的对象并且传递对象到请求者if (ClassSingleLoadStack[i]->WealthEntry->WealthType == EWealthType::Object) {UObject* InstObject = NewObject<UObject>(this, ClassSingleLoadStack[i]->WealthEntry->WealthClass);InstObject->AddToRoot();BackObject(ModuleIndex, ClassSingleLoadStack[i]->ObjectName, ClassSingleLoadStack[i]->FunName, ClassSingleLoadStack[i]->WealthEntry->WealthName, InstObject);}else if (ClassSingleLoadStack[i]->WealthEntry->WealthType == EWealthType::Actor) {AActor* InstActor = GetDDWorld()->SpawnActor<AActor>(ClassSingleLoadStack[i]->WealthEntry->WealthClass, ClassSingleLoadStack[i]->SpawnTransform);BackActor(ModuleIndex, ClassSingleLoadStack[i]->ObjectName, ClassSingleLoadStack[i]->FunName, ClassSingleLoadStack[i]->WealthEntry->WealthName, InstActor);}else if (ClassSingleLoadStack[i]->WealthEntry->WealthType == EWealthType::Widget) {UUserWidget* InstWidget = CreateWidget<UUserWidget>(GetDDWorld(), ClassSingleLoadStack[i]->WealthEntry->WealthClass);// 避免回收GCWidgetGroup.Push(InstWidget);BackWidget(ModuleIndex, ClassSingleLoadStack[i]->ObjectName, ClassSingleLoadStack[i]->FunName, ClassSingleLoadStack[i]->WealthEntry->WealthName, InstWidget);}}CompleteStack.Push(ClassSingleLoadStack[i]);}}for (int i = 0; i < CompleteStack.Num(); ++i) {ClassSingleLoadStack.Remove(CompleteStack[i]);delete CompleteStack[i];}
}void UDDWealth::BuildSingleClassWealth(EWealthType WealthType, FName WealthName, FName ObjectName, FName FunName, FTransform SpawnTransform)
{// 获取对应的资源结构体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;}// 如果资源已经加载if (WealthEntry->WealthClass) {// 根据类型来执行不同生成逻辑并且传递对象到请求者if (WealthType == EWealthType::Object) {UObject* InstObject = NewObject<UObject>(this, WealthEntry->WealthClass);InstObject->AddToRoot();// 传递对象到请求者BackObject(ModuleIndex, ObjectName, FunName, WealthName, InstObject);}else if (WealthType == EWealthType::Actor) {AActor* InstActor = GetDDWorld()->SpawnActor<AActor>(WealthEntry->WealthClass, SpawnTransform);BackActor(ModuleIndex, ObjectName, FunName, WealthName, InstActor);}else if (WealthType == EWealthType::Widget) {UUserWidget* InstWidget = CreateWidget<UUserWidget>(GetDDWorld(), WealthEntry->WealthClass);// 避免回收GCWidgetGroup.Push(InstWidget);BackWidget(ModuleIndex, ObjectName, FunName, WealthName, InstWidget);}}else {// 异步加载,获取加载句柄TSharedPtr<FStreamableHandle> WealthHandle = WealthLoader.RequestAsyncLoad(WealthEntry->WealthPtr.ToSoftObjectPath());// 创建新加载节点ClassSingleLoadStack.Push(new ClassSingleLoadNode(WealthHandle, WealthEntry, ObjectName, FunName, SpawnTransform));}
}

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

DDModule.h

public:	// 创建一个对象实例void BuildSingleClassWealth(EWealthType WealthType, FName WealthName, FName ObjectName, FName FunName, FTransform SpawnTransform);

DDModule.cpp

void UDDModule::BuildSingleClassWealth(EWealthType WealthType, FName WealthName, FName ObjectName, FName FunName, FTransform SpawnTransform)
{Wealth->BuildSingleClassWealth(WealthType, WealthName, ObjectName,  FunName, SpawnTransform);
}

DDOO 有些不一样,它需要额外判断是否需要传入生成位置,这是专门为 Actor 准备的。

DDOO.h

protected:// 创建一个对象实例// 给 Object 和 Widget 用的void BuildSingleClassWealth(EWealthType WealthType, FName WealthName, FName FunName);// 给 Actor 用的void BuildSingleClassWealth(EWealthType WealthType, FName WealthName, FName FunName, FTransform SpawnTransform);

DDOO.cpp

void IDDOO::BuildSingleClassWealth(EWealthType WealthType, FName WealthName, FName FunName)
{IModule->BuildSingleClassWealth(WealthType, WealthName, GetObjectName(), FunName, FTransform::Identity);
}void IDDOO::BuildSingleClassWealth(EWealthType WealthType, FName WealthName, FName FunName, FTransform SpawnTransform)
{IModule->BuildSingleClassWealth(WealthType, WealthName, GetObjectName(), FunName, SpawnTransform);
}

测试加载并创建单个 UClass 资源对象

我们打算只测试创建一个 Actor 资源对象,并且让它一直旋转。

WealthCallObject.h

public:virtual void DDTick(float DeltaSeconds) override;// 回调方法UFUNCTION()void BuildActor(FName BackName, AActor* BackActor);public:// 保存返回的 ActorAActor* SingleActor;

WealthCallObject.cpp

void UWealthCallObject::DDLoading()
{Super::DDLoading();IsAllowTickEvent = true;	// 实际上开启帧函数的bool值这个最好放在 DDInit()// ... 省略// 测试完毕后记得注释掉BuildSingleClassWealth(EWealthType::Actor, "ViewActor1", "BuildActor", ViewTrans);	
}void UWealthCallObject::DDTick(float DeltaSeconds)
{Super::DDTick(DeltaSeconds);if (SingleActor) {SingleActor->AddActorWorldRotation(FRotator(1.f, 0.f, 0.f));}
}void UWealthCallObject::BuildActor(FName BackName, AActor* BackActor)
{DDH::Debug() << BackName << DDH::Endl();SingleActor = BackActor;
}

编译后运行游戏,可以看见左上角输出了 ViewActor1,场景中生成了 ViewActor1,并且它一直在旋转。说明加载并创建 UClass 对象的逻辑写好了。

在这里插入图片描述
顺便打开 PlayerData 将 AutoActorData 下的两个对象去掉,免得在场景里占位置。

48. 创建同类资源对象

接下来实现创建多个同种类名(WealthKind)的资源对象实例后返回给申请者。

DDWealth.h 里的反射回调函数的声明宏,调整一下原本第一个参数的命名,加一个 Single,避免与最后一个参数重名。并且 .cpp 里对应的调用语句也要跟着修改。

DDWealth.h

public:// 创建同资源种类名的对象实例,同种类名下的每个资源链接创建一个对象实例void BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName ObjectName, FName FunName, TArray<FTransform> SpawnTransforms);protected:// 给之前的生成单个对象的反射回调函数,函数名后添加 “Single”DDOBJFUNC_TWO(BackObjectSingle, FName, BackName, UObject*, BackObject);// 添加同种类 Object 的反射回调函数DDOBJFUNC_TWO(BackObjectKind, TArray<FName>, BackNames, TArray<UObject*>, BackObjects);DDOBJFUNC_TWO(BackActorSingle, FName, BackName, AActor*, BackActor);// 添加同种类 Actor 的反射回调函数DDOBJFUNC_TWO(BackActorKind, TArray<FName>, BackNames, TArray<AActor*>, BackActors);DDOBJFUNC_TWO(BackWidgetSingle, FName, BackName, UUserWidget*, BackWidget);// 添加同种类 Widget 的反射回调函数DDOBJFUNC_TWO(BackWidgetKind, TArray<FName>, BackNames, TArray<UUserWidget*>, BackWidgets);

DDWealth.cpp

struct ClassKindLoadNode
{TSharedPtr<FStreamableHandle> WealthHandle;TArray<FClassWealthEntry*> UnLoadWealthEntry;TArray<FClassWealthEntry*> LoadWealthEntry;FName ObjectName;FName FunName;// 多个生成位置TArray<FTransform> SpawnTransforms;// 是否只加载 Classbool IsLoadClass;// 保存生成的对象与名字TArray<FName> NameGroup;TArray<UObject*> ObjectGroup;TArray<AActor*> ActorGroup;TArray<UUserWidget*> WidgetGroup;ClassKindLoadNode(TSharedPtr<FStreamableHandle> InWealthHandle, TArray<FClassWealthEntry*> InUnLoadWealthEntry, TArray<FClassWealthEntry*>& InLoadWealthEntry, FName InObjectName, FName InFunName){WealthHandle = InWealthHandle;UnLoadWealthEntry = InUnLoadWealthEntry;LoadWealthEntry = InLoadWealthEntry;ObjectName = InObjectName;FunName = InFunName;IsLoadClass = true;		// 补充}// 创建 UClass 对象所使用的构造函数ClassKindLoadNode(TSharedPtr<FStreamableHandle> InWealthHandle, TArray<FClassWealthEntry*> InUnLoadWealthEntry, TArray<FClassWealthEntry*>& InLoadWealthEntry, FName InObjectName, FName InFunName, TArray<FTransform> InSpawnTransforms){WealthHandle = InWealthHandle;UnLoadWealthEntry = InUnLoadWealthEntry;LoadWealthEntry = InLoadWealthEntry;ObjectName = InObjectName;FunName = InFunName;SpawnTransforms = InSpawnTransforms;IsLoadClass = false;	}
};void UDDWealth::DealClassSingleLoadStack()
{// ... 省略else {if (ClassSingleLoadStack[i]->WealthEntry->WealthType == EWealthType::Object) {UObject* InstObject = NewObject<UObject>(this, ClassSingleLoadStack[i]->WealthEntry->WealthClass);InstObject->AddToRoot();// 更改BackObjectSingle(ModuleIndex, ClassSingleLoadStack[i]->ObjectName, ClassSingleLoadStack[i]->FunName, ClassSingleLoadStack[i]->WealthEntry->WealthName, InstObject);}else if (ClassSingleLoadStack[i]->WealthEntry->WealthType == EWealthType::Actor) {AActor* InstActor = GetDDWorld()->SpawnActor<AActor>(ClassSingleLoadStack[i]->WealthEntry->WealthClass, ClassSingleLoadStack[i]->SpawnTransform);// 更改BackActorSingle(ModuleIndex, ClassSingleLoadStack[i]->ObjectName, ClassSingleLoadStack[i]->FunName, ClassSingleLoadStack[i]->WealthEntry->WealthName, InstActor);}else if (ClassSingleLoadStack[i]->WealthEntry->WealthType == EWealthType::Widget) {UUserWidget* InstWidget = CreateWidget<UUserWidget>(GetDDWorld(), ClassSingleLoadStack[i]->WealthEntry->WealthClass);GCWidgetGroup.Push(InstWidget);// 更改BackWidgetSingle(ModuleIndex, ClassSingleLoadStack[i]->ObjectName, ClassSingleLoadStack[i]->FunName, ClassSingleLoadStack[i]->WealthEntry->WealthName, InstWidget);}}CompleteStack.Push(ClassSingleLoadStack[i]);}}// ... 省略
}void UDDWealth::DealClassKindLoadStack()
{TArray<ClassKindLoadNode*> CompleteStack;for (int i = 0; i < ClassKindLoadStack.Num(); ++i) {// 补充判断条件,句柄可用才继续执行加载if (ClassKindLoadStack[i]->WealthHandle.IsValid() && ClassKindLoadStack[i]->WealthHandle->HasLoadCompleted() && ClassKindLoadStack[i]->UnLoadWealthEntry.Num() > 0) {// ... 省略}if (ClassKindLoadStack[i]->UnLoadWealthEntry.Num() == 0) {// 将原来的代码多套一层 if 判断,确定是否要生成对象// 加载 UClass 或者直接生成资源的情况来处理if(ClassKindLoadStack[i]->IsLoadClass) {TArray<FName> NameGroup;TArray<UClass*> WealthGroup;for (int j = 0; j < ClassKindLoadStack[i]->LoadWealthEntry.Num(); ++j) {NameGroup.Push(ClassKindLoadStack[i]->LoadWealthEntry[j]->WealthName);WealthGroup.Push(ClassKindLoadStack[i]->LoadWealthEntry[j]->WealthClass);}BackClassWealthKind(ModuleIndex, ClassKindLoadStack[i]->ObjectName, ClassKindLoadStack[i]->FunName, NameGroup, WealthGroup);CompleteStack.Push(ClassKindLoadStack[i]);}	else {	// 如果要生成对象// 从已加载的资源数组中取出第一个FClassWealthEntry* WealthEntry = ClassKindLoadStack[i]->LoadWealthEntry[0];// 移除出序列ClassKindLoadStack[i]->LoadWealthEntry.RemoveAt(0);// 根据资源类型生成对象if (WealthEntry->WealthType == EWealthType::Object) {UObject* InstObject = NewObject<UObject>(this, WealthEntry->WealthClass);InstObject->AddToRoot();// 添加找参数数组ClassKindLoadStack[i]->NameGroup.Push(WealthEntry->WealthName);ClassKindLoadStack[i]->ObjectGroup.Push(InstObject);// 判断是否生成了全部的对象if (ClassKindLoadStack[i]->LoadWealthEntry.Num() == 0) {// 给请求者传递生成的对象BackObjectKind(ModuleIndex, ClassKindLoadStack[i]->ObjectName, ClassKindLoadStack[i]->FunName, ClassKindLoadStack[i]->NameGroup, ClassKindLoadStack[i]->ObjectGroup);// 添加到完成序列CompleteStack.Push(ClassKindLoadStack[i]);}}else if (WealthEntry->WealthType == EWealthType::Actor) {// 获取生成位置FTransform SpawnTransform = ClassKindLoadStack[i]->SpawnTransforms.Num() == 1 ? ClassKindLoadStack[i]->SpawnTransforms[0] : ClassKindLoadStack[i]->SpawnTransforms[ClassKindLoadStack[i]->ActorGroup.Num()];// 生成对象AActor* InstActor = GetDDWorld()->SpawnActor<AActor>(WealthEntry->WealthClass, SpawnTransform);// 添加找参数数组ClassKindLoadStack[i]->NameGroup.Push(WealthEntry->WealthName);ClassKindLoadStack[i]->ActorGroup.Push(InstActor);// 判断是否生成了全部的对象if (ClassKindLoadStack[i]->LoadWealthEntry.Num() == 0) {// 给请求者传递生成的对象BackActorKind(ModuleIndex, ClassKindLoadStack[i]->ObjectName, ClassKindLoadStack[i]->FunName, ClassKindLoadStack[i]->NameGroup, ClassKindLoadStack[i]->ActorGroup);// 添加到完成序列CompleteStack.Push(ClassKindLoadStack[i]);}}else if (WealthEntry->WealthType == EWealthType::Widget) {UUserWidget* InstWidget = CreateWidget<UUserWidget>(GetDDWorld(), WealthEntry->WealthClass);// 避免回收GCWidgetGroup.Push(InstWidget);// 添加找参数数组ClassKindLoadStack[i]->NameGroup.Push(WealthEntry->WealthName);ClassKindLoadStack[i]->WidgetGroup.Push(InstWidget);// 判断是否生成了全部的对象if (ClassKindLoadStack[i]->LoadWealthEntry.Num() == 0) {// 给请求者传递生成的对象BackWidgetKind(ModuleIndex, ClassKindLoadStack[i]->ObjectName, ClassKindLoadStack[i]->FunName, ClassKindLoadStack[i]->NameGroup, ClassKindLoadStack[i]->WidgetGroup);// 添加到完成序列CompleteStack.Push(ClassKindLoadStack[i]);}}}	}}// ... 省略
}void UDDWealth::BuildSingleClassWealth(EWealthType WealthType, FName WealthName, FName ObjectName, FName FunName, FTransform SpawnTransform)
{// ... 省略if (WealthEntry->WealthClass) {if (WealthType == EWealthType::Object) {UObject* InstObject = NewObject<UObject>(this, WealthEntry->WealthClass);InstObject->AddToRoot();// 更改BackObjectSingle(ModuleIndex, ObjectName, FunName, WealthName, InstObject);}else if (WealthType == EWealthType::Actor) {AActor* InstActor = GetDDWorld()->SpawnActor<AActor>(WealthEntry->WealthClass, SpawnTransform);// 更改BackActorSingle(ModuleIndex, ObjectName, FunName, WealthName, InstActor);}else if (WealthType == EWealthType::Widget) {UUserWidget* InstWidget = CreateWidget<UUserWidget>(GetDDWorld(), WealthEntry->WealthClass);GCWidgetGroup.Push(InstWidget);// 更改BackWidgetSingle(ModuleIndex, ObjectName, FunName, WealthName, InstWidget);}}// ... 省略
}void UDDWealth::BuildKindClassWealth(EWealthType WealthType, FName WealthKind, FName ObjectName, FName FunName, TArray<FTransform> SpawnTransforms)
{TArray<FClassWealthEntry*> WealthEntryGroup = GetClassKindEntry(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]->WealthPtr.ToSoftObjectPath().IsValid()) {DDH::Debug() << ObjectName << " Get Not Valid In Kind : " << WealthKind << " For Name : " << WealthEntryGroup[i]->WealthName << DDH::Endl();return;}// 如果资源类型不匹配if (WealthEntryGroup[i]->WealthType != WealthType) {DDH::Debug() << ObjectName << " Get Error Type In Kind : " << WealthKind << " For Name : " << WealthEntryGroup[i]->WealthName << DDH::Endl();return;}}// 对于 Actor,有多少个对象就有多少个 Transform;对于 Object 和 Widget 则只有一个 Transform// 判断 Transform 数组是否为 1 或者是否为 WealthEntryGroup 的数量if (WealthType == EWealthType::Actor && SpawnTransforms.Num() != 1 && SpawnTransforms.Num() != WealthEntryGroup.Num()) {DDH::Debug() << ObjectName << " Send Error Spawn Count : " << WealthKind << DDH::Endl();return;}// 未加载的资源链接TArray<FClassWealthEntry*> UnLoadWealthEntry;// 已加载的资源链接TArray<FClassWealthEntry*> LoadWealthEntry;// 资源分类for (int i = 0; i < WealthEntryGroup.Num(); ++i) {if (WealthEntryGroup[i]->WealthClass)LoadWealthEntry.Push(WealthEntryGroup[i]);elseUnLoadWealthEntry.Push(WealthEntryGroup[i]);}// 声明一个加载句柄TSharedPtr<FStreamableHandle> WealthHandle;// 如果有未加载的资源if (UnLoadWealthEntry.Num() > 0) {// 获取资源路径TArray<FSoftObjectPath> WealthPaths;for (int i = 0; i < UnLoadWealthEntry.Num(); ++i)WealthPaths.Push(UnLoadWealthEntry[i]->WealthPtr.ToSoftObjectPath());// 获取加载句柄WealthHandle = WealthLoader.RequestAsyncLoad(WealthPaths);}// 创建帧处理的节点ClassKindLoadStack.Push(new ClassKindLoadNode(WealthHandle, UnLoadWealthEntry, LoadWealthEntry, ObjectName, FunName, SpawnTransforms));
}

剩余部分(补全调用链与测试功能)留到下一节课。

相关文章:

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

UE4运用C和框架开发坦克大战教程笔记&#xff08;十五&#xff09;&#xff08;第46~48集&#xff09; 46. 批量加载 UClass 功能测试批量加载多个同类 UClass 资源 47. 创建单个资源对象测试加载并创建单个 UClass 资源对象 48. 创建同类资源对象 46. 批量加载 UClass 功能 逻…...

《Linux系列》Linux虚拟机,LVM逻辑卷扩容,xfs文件系统扩容

Linux虚拟机&#xff0c;LVM逻辑卷扩容&#xff0c;xfs文件系统扩容 1 虚拟机配置介绍 在创建虚拟机的时候只给了20G磁盘空间大小&#xff0c;但是现在需求变更&#xff0c;想要增加到40G磁盘空间大小&#xff0c;所以需要通过两步扩容磁盘空间。 系统版本是Centos7 根目录…...

springboot(ssm动漫手办商城 动漫周边商系统Java系统

springboot(ssm动漫手办商城 动漫周边商系统Java系统 开发语言&#xff1a;Java 框架&#xff1a;springboot&#xff08;可改ssm&#xff09; vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.7&#xf…...

卸载 MariaDB:

如果你想将 MariaDB 5.5.68 替换为 MySQL 8&#xff0c;请按照以下步骤操作。在执行这些步骤之前&#xff0c;请确保你已经备份了所有重要的数据库和数据&#xff0c;以防发生意外情况。 1. 卸载 MariaDB&#xff1a; 使用适合你系统的包管理器卸载 MariaDB。在 CentOS/RHEL …...

javaweb总览

javaweb需要学习哪些技术 前端web开发&#xff1a; 技术描述HTML用于构建网站的基础结构的css用于美化页面的&#xff0c;作用和化妆或者整容作用一样JavaScript实现网页和用户的交互Vue主要用于将数据填充到html页面上的Element主要提供了一些非常美观的组件Nginx一款web服务…...

树,二叉树及其相关知识

1.树概念及结构 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#…...

NumPy必知必会50例 | 5. 高级数组操作:成为 NumPy 数组的忍者

文章目录 5. 高级数组操作&#xff1a;成为 NumPy 数组的忍者数组重塑&#xff1a;变形大师例子&#xff1a;从一维到二维 数组合并&#xff1a;忍者团队联合例子&#xff1a;水平和垂直合并 数组分割&#xff1a;忍者的快速撤退例子&#xff1a;水平和垂直分割 5. 高级数组操作…...

《WebKit 技术内幕》学习之五(3): HTML解释器和DOM 模型

3 DOM的事件机制 基于 WebKit 的浏览器事件处理过程&#xff1a;首先检测事件发生处的元素有无监听者&#xff0c;如果网页的相关节点注册了事件的监听者则浏览器会将事件派发给 WebKit 内核来处理。另外浏览器可能也需要处理这样的事件&#xff08;浏览器对于有些事件必须响应…...

extends 和 implements

以下是 extends 和 implements 在Java代码中的区别和示例&#xff1a; 示例1&#xff1a;使用 extends 实现类继承 // 定义一个父类 Animal public class Animal {public void eat() {System.out.println("动物在吃东西");}public void sleep() {System.out.printl…...

响应拦截器的 return Promise.reject(res.data.message)

今天在看老师讲解代码的时候,解决了我心中的一些疑惑。 在做excel文件导出的时候,没有告诉浏览器文件的格式是Blod产生了报错。 看下图: 可以看到下面的内容:如果业务成功 返回 res.data 如果业务失败,给出错误信息的提示&#xff0c;将这个错误抛出去。 因此我们在发送一个…...

Windows下 VS2022 编译OpenSSL 库

SSL是Secure Sockets Layer(安全套接层协议)的缩写,可以在Internet上提供秘密性传输。Netscape公司在推出第一个Web浏览器的同时,提出了SSL协议标准。其目标是保证两个应用间通信的保密性和可靠性,可在服务器端和用户端同时实现支持。已经成为Internet上保密通讯的工业标准…...

【GitHub项目推荐--一个简单的绘图应用程序(Rust + GTK4)】【转载】

一个用 Rust 和 GTK4 编写的简单的绘图应用程序来创建手写笔记。 Rnote 旨在成为一个简单但实用的笔记应用程序&#xff0c;用于手绘或注释图片或文档。它最终能够导入/导出各种媒体文件格式。而且输出的作品是基于矢量的&#xff0c;这使其在编辑和更改内容时非常灵活。 地址…...

【算法小记】——机器学习中的概率论和线性代数,附线性回归matlab例程

内容包含笔者个人理解&#xff0c;如果错误欢迎评论私信告诉我 线性回归matlab部分参考了up主DR_CAN博士的课程 机器学习与概率论 在回归拟合数据时&#xff0c;根据拟合对象&#xff0c;可以把分类问题视为一种简答的逻辑回归。在逻辑回归中算法不去拟合一段数据而是判断输入…...

MySQL数据库的锁机制

目录 一、引言 二、锁的类型及作用 2.1 行级锁 2.2 间隙锁与临键锁 2.3 共享锁与排他锁 2.4 意向锁 2.5 表级锁 2.6 元数据锁 三、锁的管理与优化 3.1 合理设置事务隔离级别 3.2 避免长事务 3.3 索引优化 3.4 明确锁定范围 3.5 避免不必要的全表扫描 四、实战分…...

解决 conda新建虚拟环境只有一个conda-meta文件&conda新建虚拟环境不干净

像以前一样通过conda 新建虚拟环境时发现环境一团糟&#xff0c;首先新建虚拟环境 conda create -n newenv这时候activate newenv&#xff0c;通过pip list&#xff0c;会发现有很多很多的包&#xff0c;都是我在其他环境用到的。但诡异的是&#xff0c;来到anaconda下env的目…...

React16源码: React中的completeWork对HostText处理含更新的源码实现

HostText 1 &#xff09;概述 在 completeWork 中 对 HostText的处理在第一次挂载和后续更新的不同条件下进行操作 第一次挂载主要是创建实例后续更新其实也是重新创建实例 2 &#xff09;源码 定位到 packages/react-reconciler/src/ReactFiberCompleteWork.js#L663 到 c…...

网络协议与攻击模拟_07UDP协议

一、简单概念 1、UDP协议简介 UDP&#xff08;用户数据报&#xff09;协议&#xff0c;是传输层的协议。不需要建立连接&#xff0c;直接发送数据&#xff0c;不会重新排序&#xff0c;不需要确认。 2、UDP报文字段 源端口目的端口UDP长度UDP校验和 3、常见的UDP端口号 5…...

生命在于折腾——WeChat机器人的研究和探索

一、前言 2022年&#xff0c;我玩过原神&#xff0c;当时看到了云崽的QQ机器人&#xff0c;很是感兴趣&#xff0c;支持各种插件&#xff0c;查询游戏内角色相关信息&#xff0c;当时我也自己写了几个插件&#xff0c;也看到很多大佬编写的好玩的插件&#xff0c;后来因为QQ不…...

融资项目——EasyExcel将Excel文件保存至数据库

上一篇博客已经基本介绍了EasyExcel的配置与基本使用方法。现在准备使用EasyExcel将Excel文件保存至数据库。 1.由于我们想每读取Excel中的N条记录后将这些记录全部写入数据库中。所以首先我们在Mybatis文件内先要写一个批量保存Excel文件中的记录的sql语句。 <insert id&q…...

【Oracle】设置FGA(Fine-Grained Audit)细粒度审计

文章目录 【Oracle】设置FGA(Fine-Grained Audit)细粒度审计参考 【声明】文章仅供学习交流&#xff0c;观点代表个人&#xff0c;与任何公司无关。 编辑|SQL和数据库技术(ID:SQLplusDB) 收集Oracle数据库内存相关的信息 【Oracle】ORA-32017和ORA-00384错误处理 【Oracle】设…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...