当前位置: 首页 > 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】设…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Web 架构之 CDN 加速原理与落地实践

文章目录 一、思维导图二、正文内容&#xff08;一&#xff09;CDN 基础概念1. 定义2. 组成部分 &#xff08;二&#xff09;CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 &#xff08;三&#xff09;CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 &#xf…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

Razor编程中@Html的方法使用大全

文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障

关键领域软件测试的"安全密码"&#xff1a;Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力&#xff0c;从金融交易到交通管控&#xff0c;这些关乎国计民生的关键领域…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...