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,一经查实,立即删除!

相关文章

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

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

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

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

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

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

分类模型评估方法

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

番茄台式电脑装机日记

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

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;比如边缘、形状、颜色、纹理等。这些信息可以帮助卷积神经网…

【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;这对我们的信心来说会有…

如何恢复 iPhone 上永久删除的照片?

2007年&#xff0c;苹果公司推出了一款惊天动地的智能手机&#xff0c;也就是后来的iPhone。你会惊讶地发现&#xff0c;迄今为止&#xff0c;苹果公司已经售出了 7 亿部 iPhone 设备。根据最新一项调查数据&#xff0c;智能手机利润的 95% 都进了苹果公司的腰包。 如此受欢迎…

【Vue2+3入门到实战】(16)VUEVue路由的重定向、404、编程式导航、path路径跳转传参 详细代码示例

目录 一、Vue路由-重定向1.问题2.解决方案3.语法4.代码演示 二、Vue路由-4041.作用2.位置3.语法4.代码示例 三、Vue路由-模式设置1.问题2.语法 四、编程式导航-两种路由跳转方式1.问题2.方案3.语法4.path路径跳转语法5.代码演示 path跳转方式6.name命名路由跳转7.代码演示通过n…

2023十大编程语言及未来展望

2023十大编程语言及未来展望 1. 2023年十大编程语言排行榜2. 十大编程语言未来展望PythonCCJavaC#JavaScriptPHPVisual BasicSQLAssembly language 1. 2023年十大编程语言排行榜 TIOBE排行榜是根据互联网上有经验的程序员、课程和第三方厂商的数量&#xff0c;并使用搜索引擎&a…

阿里云PolarDB数据库优惠价格表11元一天起

阿里云数据库PolarDB租用价格表&#xff0c;云数据库PolarDB MySQL版2核4GB&#xff08;通用&#xff09;、2个节点、60 GB存储空间55元5天&#xff0c;云数据库 PolarDB 分布式版标准版2核16G&#xff08;通用&#xff09;57.6元3天&#xff0c;阿里云百科aliyunbaike.com分享…

ansible管理windows测试

一、环境介绍 Ansible管理主机&#xff1a; 系统: redhat7.6 Linux管理服务器需安装pywinrm插件 Windows客户端主机&#xff1a; 系统: Server2012R2 Windows机器需要安装或升级powershell4.0以上版本&#xff0c;Server2008R2默认的版本是2.0&#xff0c;因此必须升…

使用flutter开发windows桌面软件读取ACR22U设备的nfc卡片id,5分钟搞定demo

最近有个需求&#xff0c;要使用acr122u读卡器插入电脑usb口&#xff0c;然后读取nfc卡片的id&#xff0c;并和用户账号绑定&#xff0c;调研了很多方式&#xff0c;之前使用rust实现过一次&#xff0c;还有go实现过一次&#xff0c;然后使用electron的时候遇到安装pcsc-lite失…

MacBook查看本机IP

嘚吧嘚 其实这也不是什么困难的问题&#xff0c;但是今年刚刚入坑Mac&#xff0c;外加用的频率不是很高&#xff0c;每次使用的时候都查&#xff0c;用完就忘&#xff0c;下次用的时候再查&#x1f92e;。真的把自己恶心坏了&#x1f648;。 所以写篇文章记录一下&#x1f92…