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

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

  • 40. 多按键绑定
  • 41. 自动生成对象
  • 42. 资源模块数据结构
    • 测试自动生成对象
    • 按资源类型生成对象

40. 多按键绑定

上节课实现了按键绑定系统的 4 种基础绑定,这节课来实现多按键事件的绑定。

我们为多按键绑定额外编写一个类 InputBinder

DDMessage.h

// 多按键输入绑定类
#pragma region InputBinderDECLARE_DELEGATE(FDDInputEvent)		// 用于绑定多按键的目标方法UCLASS()
class DATADRIVEN_API UDDInputBinder : public UObject
{GENERATED_BODY()public:UDDInputBinder();void PressEvent();		// 按下事件(多按键的每个按键都要绑定它)void ReleaseEvent();	// 松开事件(同上)public:uint8 InputCount;	// 目前正在响应的按键数量uint8 TotalCount;	// 触发事件所需要响应的按键数量// 游戏暂停时是否执行按键事件uint8 bExecuteWhenPause;FDDInputEvent InputDele;	// 委托句柄public:// 模板初始化方法template<class UserClass>void InitBinder(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, uint8 InCount){TotalCount = InCount;InputDele.BindUObject(UserObj, InMethod);}
};#pragma endregionUCLASS()
class DATADRIVEN_API UDDMessage : public UObject, public IDDMM
{GENERATED_BODY()public:// 绑定多个按键template<class UserClass>UDDInputBinder& BindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, TArray<FKey>& KeyGroup, FName ObjectName);// 解绑对象的所有按键事件void UnBindInput(FName ObjectName);protected:// 绑定按键事件序列,键是按键事件名,值是按键事件的数组TMap<FName, TArray<UDDInputBinder*>> BinderGroup;
};template<class UserClass>
UDDInputBinder& UDDMessage::BindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, TArray<FKey>& KeyGroup, FName ObjectName)
{UDDInputBinder* InputBinder = NewObject<UDDInputBinder>();InputBinder->InitBinder(UserObj, InMethod, KeyGroup.Num());InputBinder->AddToRoot();	// 避免被 GC// 给所有目标按钮绑定按下和抬起的方法for (int i = 0; i < KeyGroup.Num(); ++i) {PlayerController->InputComponent->BindKey(KeyGroup[i], IE_Pressed, InputBinder, &UDDInputBinder::PressEvent).bExecuteWhenPaused = true;PlayerController->InputComponent->BindKey(KeyGroup[i], IE_Released, InputBinder, &UDDInputBinder::ReleaseEvent).bExecuteWhenPaused = true;}if (!BinderGroup.Contains(ObjectName)) {TArray<UDDInputBinder*> BinderList;BinderGroup.Add(ObjectName, BinderList);}BinderGroup.Find(ObjectName)->Push(InputBinder);return *InputBinder;
}

DDMessage.cpp

UDDInputBinder::UDDInputBinder()
{InputCount = 0;bExecuteWhenPause = false;
}void UDDInputBinder::PressEvent()
{InputCount++;// 如果 InputCount 与 TotalCount 相等,说明所有按键都按下了if (InputCount == TotalCount) {// 如果允许在暂停时执行if (bExecuteWhenPause)InputDele.ExecuteIfBound();else if (!bExecuteWhenPause && !UDDCommon::Get()->IsPauseGame())InputDele.ExecuteIfBound();}
}void UDDInputBinder::ReleaseEvent()
{InputCount--;
}void UDDMessage::UnBindInput(FName ObjectName)
{if (!BinderGroup.Contains(ObjectName))return;TArray<UDDInputBinder*> BinderList = *BinderGroup.Find(ObjectName);for (int i = 0; i < BinderList.Num(); ++i) {BinderList[i]->RemoveFromRoot();	// 移出 Root 以便被 GCBinderList[i]->ConditionalBeginDestroy();	// 申请销毁}BinderGroup.Remove(ObjectName);
}

依旧是部署好 DDMessage – DDModule – DDOO – 对象 这条调用链。

DDModule.h

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DATADRIVEN_API UDDModule : public USceneComponent
{GENERATED_BODY()public:// 绑定多个按键template<class UserClass>UDDInputBinder& BindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, TArray<FKey>& KeyGroup, FName ObjectName);// 解绑对象的所有按键事件void UnBindInput(FName ObjectName);
};template<class UserClass>
UDDInputBinder& UDDModule::BindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, TArray<FKey>& KeyGroup, FName ObjectName)
{return Message->BindInput(UserObj, InMethod, KeyGroup, ObjectName);
}

DDModule.cpp

void UDDModule::UnBindInput(FName ObjectName)
{Message->UnBindInput(ObjectName);
}

DDOO.h

class DATADRIVEN_API IDDOO
{GENERATED_BODY()protected:// 给之前的绑定方法的名字都添加 DD 前缀,避免与 UE4 原生的方法名重复template<class UserClass>FInputAxisBinding& DDBindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName AxisName);template<class UserClass>FInputTouchBinding& DDBindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const EInputEvent KeyEvent);template<class UserClass>FInputActionBinding& DDBindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent);template<class UserClass>FInputKeyBinding& DDBindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent);// 绑定 2 个按键(注意绑定多个按键时传入的形参不一样了)template<class UserClass>UDDInputBinder& DDBindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, FKey Key_I, FKey Key_II);// 绑定 3 个按键,下面的也差不多,就是多传了一两个按键形参template<class UserClass>UDDInputBinder& DDBindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, FKey Key_I, FKey Key_II, FKey Key_III);template<class UserClass>UDDInputBinder& DDBindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, FKey Key_I, FKey Key_II, FKey Key_III, FKey Key_IV);template<class UserClass>UDDInputBinder& DDBindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, FKey Key_I, FKey Key_II, FKey Key_III, FKey Key_IV, FKey Key_V);// 解绑该对象的所有多个按键void UnBindInput();
};// 模板方法的实现里,名字也同步添加前缀
template<class UserClass>
FInputAxisBinding& IDDOO::DDBindAxis(UserClass* UserObj, typename FInputAxisHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName AxisName)
{return IModule->BindAxis(UserObj, InMethod, AxisName);
}template<class UserClass>
FInputTouchBinding& IDDOO::DDBindTouch(UserClass* UserObj, typename FInputTouchHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const EInputEvent KeyEvent)
{return IModule->BindTouch(UserObj, InMethod, KeyEvent);
}template<class UserClass>
FInputActionBinding& IDDOO::DDBindAction(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FName ActionName, const EInputEvent KeyEvent)
{return IModule->BindAction(UserObj, InMethod, ActionName, KeyEvent);
}template<class UserClass>
FInputKeyBinding& IDDOO::DDBindInput(UserClass* UserObj, typename FInputActionHandlerSignature::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, const FKey Key, const EInputEvent KeyEvent)
{// 绑定单个按钮和多按钮时,调用的 BindInput() 不一样return IModule->BindInput(UserObj, InMethod, Key, KeyEvent);
}// 下面的方法都是绑定多按键的模板方法
template<class UserClass>
UDDInputBinder& IDDOO::DDBindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, FKey Key_I, FKey Key_II)
{TArray<FKey> KeyGroup;KeyGroup.Push(Key_I);KeyGroup.Push(Key_II);// 从传入的参数可以看出来return IModule->BindInput(UserObj, InMethod, KeyGroup, GetObjectName());
}template<class UserClass>
UDDInputBinder& IDDOO::DDBindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, FKey Key_I, FKey Key_II, FKey Key_III)
{TArray<FKey> KeyGroup;KeyGroup.Push(Key_I);KeyGroup.Push(Key_II);KeyGroup.Push(Key_III);return IModule->BindInput(UserObj, InMethod, KeyGroup, GetObjectName());
}template<class UserClass>
UDDInputBinder& IDDOO::DDBindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, FKey Key_I, FKey Key_II, FKey Key_III, FKey Key_IV)
{TArray<FKey> KeyGroup;KeyGroup.Push(Key_I);KeyGroup.Push(Key_II);KeyGroup.Push(Key_III);KeyGroup.Push(Key_IV);return IModule->BindInput(UserObj, InMethod, KeyGroup, GetObjectName());
}template<class UserClass>
UDDInputBinder& IDDOO::DDBindInput(UserClass* UserObj, typename FDDInputEvent::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, FKey Key_I, FKey Key_II, FKey Key_III, FKey Key_IV, FKey Key_V)
{TArray<FKey> KeyGroup;KeyGroup.Push(Key_I);KeyGroup.Push(Key_II);KeyGroup.Push(Key_III);KeyGroup.Push(Key_IV);KeyGroup.Push(Key_V);return IModule->BindInput(UserObj, InMethod, KeyGroup, GetObjectName());
}

为了避免出现忘记注销事件指针导致访问错误位置的情况,我们在 DDRelease() 里注销前面写的三个系统的所有事件。

DDOO.cpp

void IDDOO::DDRelease()
{// 注销所有协程,延时以及按键事件(也可以分别在其他地方调用)StopAllCoroutine();StopAllInvoke();UnBindInput();
}void IDDOO::UnBindInput()
{IModule->UnBindInput(GetObjectName());
}

最后我们来验证一下多按键事件绑定是否正常运作。

CoroActor.h

protected:void MultiKeyEvent();

CoroActor.cpp

void ACoroActor::DDEnable()
{// 加前缀,但不运行这个单按键绑定//DDBindInput(this, &ACoroActor::BKeyEvent, EKeys::B, IE_Pressed);DDBindInput(this, &ACoroActor::MultiKeyEvent, EKeys::J, EKeys::K, EKeys::L);
}void ACoroActor::MultiKeyEvent()
{DDH::Debug() << "MultiKeyEvent" << DDH::Endl();
}

编译运行,同时按 J、K、L 三个键,左上角才会输出 “MultiKeyEvent”。说明多按键绑定功能也编写好了。

41. 自动生成对象

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

资源加载系统概述

关于资源加载方式,读者可以回顾课程的第 8 集和第 9 集,里面讲到了资源的同异步加载。

开发资源加载系统,我们首先要定义一个专用于保存资源数据的数据类,以及与之配套的结构体。目前我们先考虑生成以下 3 种类型的对象:Object、Actor 和 Widget。

DDTypes.h

#include "Engine/DataAsset.h"	// 引入头文件
#include "DDTypes.generated.h"#pragma region WealthUSTRUCT()	// 用这个宏说明结构体可与蓝图交互
struct FWealthItem	// 资源基类结构体
{GENERATED_BODY()public:// 对象名UPROPERTY(EditAnywhere)FName ObjectName;// 类名UPROPERTY(EditAnywhere)FName ClassName;
};USTRUCT()
struct FWealthObject : public FWealthItem	// Object 类型资源的结构体
{GENERATED_BODY()public:UPROPERTY(EditAnywhere)TSubclassOf<UObject> WealthClass;
};USTRUCT()
struct FWealthActor : public FWealthItem	// Actor 类型资源的结构体
{GENERATED_BODY()public:UPROPERTY(EditAnywhere)TSubclassOf<AActor> WealthClass;UPROPERTY(EditAnywhere)FTransform Transform;
};USTRUCT()
struct FWealthWidget : public FWealthItem	// Widget 类型资源的结构体
{GENERATED_BODY()public:UPROPERTY(EditAnywhere)TSubclassOf<UUserWidget> WealthClass;
};UCLASS()
class DATADRIVEN_API UWealthData : public UDataAsset	// 继承自原生的数据类
{GENERATED_BODY()public:// 模组名字,这个 DataAsset 下的资源生成的对象默认注册到 ModuleName 对应的模组// 如果为空(None)则说明该 Asset 使用于多个模组下,自动生成的对象注册到该 Asset 放置的模组下UPROPERTY(EditAnywhere)FName ModuleName;// 自动生成的 ObjectUPROPERTY(EditAnywhere)TArray<FWealthObject> AutoObjectData;// 自动生成的 ActorUPROPERTY(EditAnywhere)TArray<FWealthActor> AutoActorData;// 自动生成的 WidgetUPROPERTY(EditAnywhere)TArray<FWealthWidget> AutoWidgetData;
};#pragma endregion

要在 C++ 内获取到蓝图里的内容,则需要让 DDModule 拥有一个暴露给蓝图的资源组,这样就可以在编辑器内对资源组配置目标对象。

DDModule.h

public:// 暴露给蓝图的资源组UPROPERTY(EditAnywhere, Category = "DataDriven")TArray<UWealthData*> WealthData;TArray<UDDModule*> ChildrenModule;// 不再将 ModuleIndex 暴露给蓝图int32 ModuleIndex;

自动生成资源的逻辑放在 DDWealth 里,所以需要将资源组从 DDModule 赋值给 DDWealth。

DDWealth.h

public:// 指定资源组void AssignData(TArray<UWealthData*>& InWealthData);protected:// 资源组TArray<UWealthData*> WealthData;// Widget 类型对象专属,保存 Widget 指针,放止被 GCUPROPERTY()TArray<UUserWidget*> GCWidgetGroup;

由于生成 Actor 对象需要获取世界,所以我们在 DDMM 里添加一个获取世界的方法。

DDMM.h

protected:// 获取世界UWorld* GetDDWorld() const;

DDMM.cpp

UWorld* IDDMM::GetDDWorld() const
{if (IDriver)return IDriver->GetWorld();return NULL;
}

在 DDWealth.cpp 里补全资源生成逻辑,即遍历所配置的资源组并生成里面的 3 种类型的所有对象。

DDWealth.cpp

// 引入头文件
#include "DDObject/DDOO.h"
#include "Blueprint/UserWidget.h"void UDDWealth::WealthBeginPlay()
{// 遍历自动生成对象for (int i = 0; i < WealthData.Num(); ++i) {// 生成 Object 对象for (int j = 0; j < WealthData[i]->AutoObjectData.Num(); ++j) {// 根据获取到的 UClass 生成指定的对象UObject* NewObj = NewObject<UObject>(this, WealthData[i]->AutoObjectData[j].WealthClass);NewObj->AddToRoot();IDDOO* InstPtr = Cast<IDDOO>(NewObj);// 注册到框架if (InstPtr) {InstPtr->RegisterToModule(WealthData[i]->ModuleName.IsNone() ? IModule->GetFName() : WealthData[i]->ModuleName, WealthData[i]->AutoObjectData[j].ObjectName,WealthData[i]->AutoObjectData[j].ClassName);}}// 生成 Actor 对象for (int j = 0; j < WealthData[i]->AutoActorData.Num(); ++j) {AActor* NewAct = GetDDWorld()->SpawnActor<AActor>(WealthData[i]->AutoActorData[j].WealthClass, WealthData[i]->AutoActorData[j].Transform);IDDOO* InstPtr = Cast<IDDOO>(NewAct);if (InstPtr) {InstPtr->RegisterToModule(WealthData[i]->ModuleName.IsNone() ? IModule->GetFName() : WealthData[i]->ModuleName, WealthData[i]->AutoActorData[j].ObjectName,WealthData[i]->AutoActorData[j].ClassName);}}// 生成 Widget 对象for (int j = 0; j < WealthData[i]->AutoWidgetData.Num(); ++j) {UUserWidget* NewWidget = CreateWidget<UUserWidget>(GetDDWorld(), WealthData[i]->AutoWidgetData[j].WealthClass);// 避免回收(AddToRoot() 不适用于 Widget,即便加了也会被回收)GCWidgetGroup.Push(NewWidget);IDDOO* InstPtr = Cast<IDDOO>(NewWidget);if (InstPtr) {InstPtr->RegisterToModule(WealthData[i]->ModuleName.IsNone() ? IModule->GetFName() : WealthData[i]->ModuleName, WealthData[i]->AutoWidgetData[j].ObjectName,WealthData[i]->AutoWidgetData[j].ClassName);}}}
}void UDDWealth::AssignData(TArray<UWealthData*>& InWealthData)
{WealthData = InWealthData;
}

在 DDModule 的 BeginPlay() 里给 DDWealth 指定资源组。

DDModule.cpp

void UDDModule::ModuleBeginPlay()
{// 给 Wealth 指定资源Wealth->AssignData(WealthData);// ... 省略
}

编译如果没有问题的话,我们将验证部分留到下一节课。

42. 资源模块数据结构

测试自动生成对象

接下来准备一下将要生成在场景中的资源。我们打算生成资源的种类有:Object、Actor、Pawn(实际上也是 Actor)和 Widget。由于 Object 是不允许直接生成在场景中的,我们需要创建它的蓝图然后将其生成在场景中。

创建一个以 DDObject 为基类的 C++ 类,目标模组选项目,命名为 WealthCallObject,直接在默认路径创建。显示自动热重载失败,不用管,直接点 No。
创建一个以 DDActor 为基类的 C++ 类,命名为 TestWealthActor,负责调用被生成对象带有的,用于输出 Debug 语句的蓝图方法。

WealthCallObject.h

UCLASS(Blueprintable, BlueprintType)	// 添加两个说明符,以便能跟蓝图交互
class RACECARFRAME_API UWealthCallObject : public UDDObject
{GENERATED_BODY()
};

TestWealthActor.h

UCLASS()
class RACECARFRAME_API ATestWealthActor : public ADDActor
{GENERATED_BODY()public:virtual void DDEnable() override;protected:DDOBJFUNC(CallWealth);
};

TestWealthActor.cpp

void ATestWealthActor::DDEnable()
{Super::DDEnable();
}

编译后,在 Blueprint 文件夹内创建蓝图,它们都是待会要生成的对象:
以 DDActor 为基类创建一个蓝图,命名为 WealthCallActor
以 DDPawn 为基类创建一个蓝图,命名为 WealthCallPawn
以 DDUserWidget 为基类创建一个蓝图,命名为 WealthCallWidget
以 WealthCallObject 为基类创建一个蓝图,命名为 WealthCallObject

随后给它们添加一些方便观察是否正常生成的模型或者输出 Debug 语句的方法:(图片可能会有些糊,两个模型也可以随便选,重要的是 4 个输出的方法)

在这里插入图片描述

在 Blueprint 文件夹下创建两个 DataAsset,都以 WealthData 为基类,分别命名为 PlayerDataHUDData

然后对两个 DataAsset 设置一下内容,再配置到 GameDriver_BP 的两个模组上:

在这里插入图片描述
在 TestWealthActor.cpp 里的 DDEnable() 里补充对 4 种资源输出方法的调用。

TestWealthActor.cpp

void ATestWealthActor::DDEnable()
{Super::DDEnable();// 调用 4 个生成对象的输出方法(测试完了需要注释掉)CallWealth((int32)ERCGameModule::Player, "WealthCallObject", "CallObject");CallWealth((int32)ERCGameModule::Player, "WealthCallActor", "CallActor");CallWealth((int32)ERCGameModule::Player, "WealthCallPawn", "CallPawn");CallWealth((int32)ERCGameModule::HUD, "WealthCallWidget", "CallWidget");
}

编译后,在 Blueprint 文件夹下创建一个以 TestWealthActor 为基类的蓝图,命名为 TestWealthActor_BP。给其细节面板修改如下:

在这里插入图片描述

将它放到场景中,运行游戏,可见左上角输出了 4 条 Debug 语句,并且场景中也生成了目标 Actor 和 Pawn,界面上也正确生成了两个按钮。

在这里插入图片描述
我们来梳理下脉络:GameDriver_BP 下的 Center 模组内有 Player 模组和 HUD 模组,这两个模组内分别配置了 PlayerData 和 HUDData,里面存储了要生成的对象。两个模组将各自的 DataAsset 传给各自的 DDWealth 来执行生成逻辑。

资源对象生成后,Object、Actor、Pawn 这三个对象属于 Player 模组,Widget 属于 HUD 模组,而已经出现在场景内的 TestWealthActor_BP (属于 Center 模组)通过框架的反射事件系统调用这 4 个对象各自的蓝图 Debug 方法。由此已经可以看出梁迪老师的框架功能是比较齐全的。

按资源类型生成对象

前面截取的图片里提到,按资源类型分类也有两种情况:

在这里插入图片描述
所以我们接下来准备写通过 UObject 和 UClass 这两种方式生成资源的逻辑。

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

在这里插入图片描述
我们先从数据结构开始写起:

DDTypes.h

#pragma region Wealth// Object 资源结构体
USTRUCT()
struct FObjectWealthEntry
{GENERATED_BODY()public:// 资源名UPROPERTY(EditAnywhere)FName WealthName;// 资源种类名UPROPERTY(EditAnywhere)FName WealthKind;// 资源链接UPROPERTY(EditAnywhere)FStringAssetReference WealthPath;// 加载出来的对象,如果有重复生成的情况就直接引用它,而不是生成多个UPROPERTY()UObject* WealthObject;
};// UClass 类型枚举
UENUM()
enum class EWealthType : uint8 {Object,Actor,Widget
};// Class 资源结构体
USTRUCT()
struct FClassWealthEntry
{GENERATED_BODY()public:// 资源类别UPROPERTY(EditAnywhere)EWealthType WealthType;// 资源名UPROPERTY(EditAnywhere)FName WealthName;// 资源种类名UPROPERTY(EditAnywhere)FName WealthKind;// 资源链接UPROPERTY(EditAnywhere)TSoftClassPtr<UObject> WealthPtr;// 加载出来的对象UPROPERTY()UClass* WealthClass;
};// 纯获取链接结构体,不进行同异步加载
USTRUCT()
struct FWealthURL
{GENERATED_BODY()public:// 资源名UPROPERTY(EditAnywhere)FName WealthName;// 资源种类名UPROPERTY(EditAnywhere)FName WealthKind;// 资源链接UPROPERTY(EditAnywhere)FStringAssetReference WealthPath;// 资源链接UPROPERTY(EditAnywhere)TSoftClassPtr<UObject> WealthPtr;
};// 声明 3 个上面的结构体的数组
UCLASS()
class DATADRIVEN_API UWealthData : public UDataAsset
{GENERATED_BODY()public:// Object 资源链接集合UPROPERTY(EditAnywhere)TArray<FObjectWealthEntry> ObjectWealthData;// Class 资源链接集合UPROPERTY(EditAnywhere)TArray<FClassWealthEntry> ClassWealthData;// 资源链接集合UPROPERTY(EditAnywhere)TArray<FWealthURL> WealthURL;
};#pragma endregion

剩下的逻辑留到后续课程。

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

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

相关文章

自动化部署与容器化:构建现代化DevOps工作流

流畅DevOps之路&#xff1a;探秘自动化部署和容器化的前沿技术 前言 随着软件开发的复杂性不断增加&#xff0c;传统的手动部署和管理方式已无法满足迅速变化的需求。本文将探讨现代DevOps实践中的关键技术&#xff0c;从容器化到自动化部署&#xff0c;以及涉及到的关键工具…

python+django高校教材共享管理系统PyCharm 项目

本中原工学院教材共享平台采用的数据库是mysql&#xff0c;使用nodejs技术开发。在设计过程中&#xff0c;充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。系统所要实现的功能分析&#xff0c;对于现在网络方便的管理&…

Java:IO流——字节流和字符流

目录 IO流的基本概念 IO流体系结构 FileOutputStream字节输出流 构造方法 成员方法 细节 关流 FileInputStream字节输入流 构造方法及成员方法 read不带参数代码示例 read带参数代码示例​编辑 将字节数组或字符数组转成字符串 FileReader 字符输入流 构造方法和…

云计算的服务模式和发展趋势

一、云计算的三种服务模式&#xff1a; 1、Iaas&#xff08;infrastructure as a service&#xff09;: 基础设施即服务&#xff08;基础的云主机、磁盘、系统等&#xff0c;云厂商都给配置好&#xff0c;需要自己搭建云平台&#xff0c;并在云平台上部署业务所需要的软件&…

在macOS中搭建.NET MAUI开发环境

文章目录 准备安装扩展安装 .NET安装工作负载安装 Xcode 命令行工具调试安卓应用安装 JDK安装 Android SDK 安装 Android 模拟器安装模拟器安装镜像创建虚拟机 同意许可条款创建 MAUI 项目调试 MAUI 应用切换调试目标 参考资料 准备 一台 macOS Monterey 以上的电脑安装 XCode…

前端如何性能优化

前端性能优化是提升网页加载速度和用户体验的关键方面 压缩和合并文件&#xff1a;压缩 CSS 和 JavaScript 文件&#xff0c;减少文件大小。将多个文件合并为一个&#xff0c;减少网络请求次数。 使用缓存&#xff1a;设置合适的缓存策略&#xff0c;使得静态资源可以被浏览器…

分类模型评估方法

1.数据集划分 1.1 为什么要划分数据集? 思考&#xff1a;我们有以下场景&#xff1a; 将所有的数据都作为训练数据&#xff0c;训练出一个模型直接上线预测 每当得到一个新的数据&#xff0c;则计算新数据到训练数据的距离&#xff0c;预测得到新数据的类别 存在问题&…

番茄台式电脑装机日记

番茄台式电脑装机日记 start 时至今日&#xff0c;终于圆了年少时的梦&#xff1a;拥有自己的一台台式电脑。就好像第一次学会系鞋带&#xff0c;第一次剥榴莲&#xff0c;亲手组装自己的第一台台式电脑&#xff0c;这个体验完全是此生少有且独特的完美体验。作者&#xff1a…

Innodb执行insert造成死锁的问题

背景 起因是线上报了一个错误信息&#xff0c;Deadlock found when trying to get lock; try restarting transaction&#xff0c;这是Mysql检测到死锁后&#xff0c;自动回滚了事务引发的异常。spring框架里打印了异常堆栈&#xff0c;所以很快就定位到哪一行代码出了问题。 …

optimizer.load_state_dict()报错parameter group不匹配的问题的原因

在加载预训练权重时可能会遇到类似下面的错误&#xff1a; optimizer.load_state_dict(checkpoint[optimizer_state]) File "/opt/conda/lib/python3.8/site-packages/torch/optim/optimizer.py", line 145, in load_state_dict raise ValueError("loade…

Visual Studio Markdown Editor 插件导出 HTML

Visual Studio Markdown Editor 可通过右键弹出菜单选择“另存为”&#xff0c;轻松导出HTML文件或是单一的mhtml文件。然而&#xff0c;这个插件在导出HTML文件时有一个小问题&#xff0c;就是md文件中的一些内部链接无法在导出的HTML文件中正常工作。 其中的原因是&#xff…

Docker单点部署Seata(2.0.0) + Nacos(v2.3.0) + Mysql(5.7)

文章目录 一、部署Nacos二、部署Mysql三、Seata准备工作1. 记住nacos、mysql、宿主机的ip2. 建立数据库3. Nacos远程配置文件 四、部署Seata五、初步检验Seata部署情况六、微服务使用Seata1.引入依赖2. application.yml配置 七、遇到的坑1. Nacos显示Seata服务的ip为容器内网ip…

使用SpringBoot AOP记录操作日志和异常日志

使用SpringBoot AOP记录操作日志和异常日志 平时我们在做项目时经常需要对一些重要功能操作记录日志&#xff0c;方便以后跟踪是谁在操作此功能&#xff1b;我们在操作某些功 能时也有可能会发生异常&#xff0c;但是每次发生异常要定位原因我们都要到服务器去查询日志才能找…

第3课 获取并播放音频流

本课对应源文件下载链接&#xff1a; https://download.csdn.net/download/XiBuQiuChong/88680079 FFmpeg作为一套庞大的音视频处理开源工具&#xff0c;其源码有太多值得研究的地方。但对于大多数初学者而言&#xff0c;如何快速利用相关的API写出自己想要的东西才是迫切需要…

【机器学习】卷积神经网络(一)

一、网络结构 典型CNN结构 卷积神经网络是一种能够从图像、声音或其他类型的数据中学习特征的人工智能模型。你可以把它想象成一个有很多层的过滤器&#xff0c;每一层都能够提取出数据中的一些有用的信息&#xff0c;比如边缘、形状、颜色、纹理等。这些信息可以帮助卷积神经网…

ES高级用法:DeleteByQueryRequest

背景 在Elasticsearch中&#xff0c;delete_by_query API 允许你基于查询条件删除文档。在Java中&#xff0c;你可以使用Elasticsearch的Rest High Level Client或者Transport Client来执行这个操作。 示例代码 下面是使用Rest High Level Client进行delete_by_query操作的一…

【Matlab】ELM极限学习机时序预测算法

资源下载&#xff1a; https://download.csdn.net/download/vvoennvv/88681649 一&#xff0c;概述 ELM&#xff08;Extreme Learning Machine&#xff09;是一种单层前馈神经网络结构&#xff0c;与传统神经网络不同的是&#xff0c;ELM的隐层神经元权重以及偏置都是随机产生的…

【Android12】Android Framework系列---tombstone墓碑生成机制

tombstone墓碑生成机制 Android中程序在运行时会遇到各种各样的问题&#xff0c;相应的就会产生各种异常信号&#xff0c;比如常见的异常信号 Singal 11&#xff1a;Segmentation fault表示无效的地址进行了操作&#xff0c;比如内存越界、空指针调用等。 Android中在进程(主要…

Apache-ActiveMQ 反序列化漏洞(CVE-2015-5254)复现

CVE-2016-3088 一、环境搭建 Java:jdk8 影响版本 Apache ActiveMQ < 5.13.0 二、用docker搭建漏洞环境 访问一下web界面 然后进入admin目录登录 账号:admin 密码:admin 三、工具准备 cd /opt wget https://github.com/matthiaskaiser/jmet/releases/download/0.1.0/jmet-0…

QT上位机开发(第一个应用)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 不管是软件&#xff0c;还是硬件&#xff0c;如果我们能够顺利启动第一个应用&#xff0c;点亮第一个电路的话&#xff0c;这对我们的信心来说会有…