从老师准备的 DataDriven 插件的导图中可知,Driver 类是驱动整个框架的主类,其方法会驱动下一层模组类的对应方法,整个框架的运行顺序呈层级结构。
下面图片截取自梁迪老师的 DataDriven 思维导图。
那先来到 DDDriver,完善其初始化框架的逻辑。
由于 Driver 需要用到其下层的功能模块,有部分变量和方法都在文章的下面才正式实现,建议读者可以了解 Driver 的大体逻辑后,继续看下层功能模块的逻辑,理清下层的逻辑后再回来看 Driver 的实现,方便理清脉络。
在框架里面,某些模块的特定方法里会被放置一些 Debug 输出,便于后面测试,测试完毕后需要删除。
DDDriver.h
#include "DDCenterModule.h" // 引入头文件
#include "DDCommon/DDCommon.h" // 引入头文件
#include "DDDriver.generated.h"
UCLASS()
class DATADRIVEN_API ADDDriver : public AActor
{
GENERATED_BODY()
public:
ADDDriver();
// 移到上面来
virtual void Tick(float DeltaTime) override;
// 初始化框架
virtual void PostInitializeComponents() override;
public:
UPROPERTY(EditAnywhere, NoClear, BlueprintReadOnly, Category = "DataDriven")
USceneComponent* RootScene;
// 中央模组,NoClear 代表无法设置为空
UPROPERTY(EditAnywhere, NoClear, BlueprintReadOnly, Category = "DataDriven")
UDDCenterModule* Center;
// 模组对应的枚举名字
UPROPERTY(EditDefaultsOnly, Category = "DataDriven")
FName ModuleType;
protected:
// 是否已经运行 BeginPlay 函数
bool IsBeginPlay;
};
DDDriver.cpp
ADDDriver::ADDDriver()
{
RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
RootComponent = RootScene;
Center = CreateDefaultSubobject<UDDCenterModule>(TEXT("Center"));
Center->SetupAttachment(RootComponent);
}
void ADDDriver::PostInitializeComponents()
{
Super::PostInitializeComponents();
// 注册 Driver 到 UDDCommon 单例
UDDCommon::Get()->InitDriver(this);
// 在游戏运行之前必须进行一次模组 ID 的设定,在这里面会注册子模组到数组
Center->IterChangeModuleType(Center, ModuleType);
// 创建所用模组的模块
Center->IterCreateManager(Center);
}
void ADDDriver::BeginPlay()
{
Super::BeginPlay();
// 迭代调用 Init 函数
Center->IterModuleInit(Center);
}
void ADDDriver::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (!IsBeginPlay) {
// 迭代调用 BeginPlay 函数
Center->IterModuleBeginPlay(Center);
// 只执行第一帧
IsBeginPlay = true;
}
else {
// 迭代调用 Tick 函数
Center->IterModuleTick(Center, DeltaTime);
}
}
来到通用类,实现单例模式的功能。
为了让它能在框架的各个地方都可以使用,所以需要注册 Driver 给它。再添加一个获取 Driver 的方法。
DDCommon.h
// 提前声明
class ADDDriver;
namespace DDH
{
// ... 省略
};
UCLASS()
class DATADRIVEN_API UDDCommon : public UObject
{
GENERATED_BODY()
public:
static UDDCommon* Get();
void InitDriver(ADDDriver* InDriver);
ADDDriver* GetDriver();
private:
static UDDCommon* DDInst;
ADDDriver* Driver;
};
DDCommon.cpp
UDDCommon* UDDCommon::DDInst = NULL;
UDDCommon* UDDCommon::Get()
{
if (!DDInst) {
DDInst = NewObject<UDDCommon>();
DDInst->AddToRoot();
}
return DDInst;
}
void UDDCommon::InitDriver(ADDDriver* InDriver)
{
Driver = InDriver;
}
ADDDriver* UDDCommon::GetDriver()
{
return Driver;
}
DDMM 作为一个接口,其作用是保存 Model、Message 和 Wealth 这三个模块所属的模组(Module),以及保存 Driver 的指针。
DDMM.h
#include "DDCommon/DDCommon.h" // 引入头文件
#include "DDMM.generated.h"
class UDDModule; // 提前声明
UINTERFACE(MinimalAPI)
class UDDMM : public UInterface
{
GENERATED_BODY()
};
class DATADRIVEN_API IDDMM
{
GENERATED_BODY()
public:
// 指定模组
void AssignModule(UDDModule* Mod);
protected:
// 保存对应的模组
UDDModule* IModule;
// 保存驱动器
ADDDriver* IDriver;
};
DDMM.cpp
void IDDMM::AssignModule(UDDModule* Mod)
{
IModule = Mod;
IDriver = UDDCommon::Get()->GetDriver();
}
写好了接口,那么接下来给要继承接口的类添加上各自的生命周期相关的方法。
Model 的作用是保存数据。
DDModel.h
#include "DDMM.h" // 引入头文件
#include "DDModel.generated.h"
UCLASS()
class DATADRIVEN_API UDDModel : public UObject, public IDDMM // 继承接口
{
GENERATED_BODY()
public:
virtual void ModelInit() {}
virtual void ModelBeginPlay() {}
virtual void ModelTick(float DeltaSeconds);
};
DDModel.cpp
void UDDModel::ModelTick(float DeltaSeconds)
{
}
Wealth 的作用是保存资源。
DDWealth.h
#include "DDMM.h" // 引入头文件
#include "DDWealth.generated.h"
UCLASS()
class DATADRIVEN_API UDDWealth : public UObject, public IDDMM // 继承接口
{
GENERATED_BODY()
public:
virtual void WealthInit() {}
virtual void WealthBeginPlay();
// 资源的 Tick 函数
virtual void WealthTick(float DeltaSeconds);
};
DDWealth.cpp
void UDDWealth::WealthBeginPlay()
{
}
void UDDWealth::WealthTick(float DeltaSeconds)
{
}
Message 负责处理消息。
DDMessage.h
#include "DDMM.h" // 引入头文件
#include "DDMessage.generated.h"
UCLASS()
class DATADRIVEN_API UDDMessage : public UObject, public IDDMM // 继承接口
{
GENERATED_BODY()
public:
virtual void MessageInit();
virtual void MessageBeginPlay();
virtual void MessageTick(float DeltaSeconds);
};
DDMessage.cpp
void UDDMessage::MessageInit()
{
DDH::Debug() << "MessageInit" << DDH::Endl();
}
void UDDMessage::MessageBeginPlay()
{
DDH::Debug() << "MessageBeginPlay" << DDH::Endl();
}
void UDDMessage::MessageTick(float DeltaSeconds)
{
DDH::Debug(0.f) << "MessageTick" << DDH::Endl();
}
处理了最顶层的 Driver,则继续来完善它下一级的 Module。
给 DDModule 去掉 TickComponent()
和 BeginPlay()
相关的方法和内容,因为用不上。我们会添加自己的类似方法。
Module 除了自身的生命周期相关方法,还负责实例化自己所拥有的模块(Model、Message、Wealth);并且它还需要保存自己的子 Module。
DDModule.h
#include "DDMessage.h" // 引入头文件
#include "DDModule.generated.h"
// 提前声明
class UDDModel;
class UDDWealth;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DATADRIVEN_API UDDModule : public USceneComponent
{
GENERATED_BODY()
public:
// 实例化模组底下的模块
void CreateManager();
// Module 的 Init 函数
virtual void ModuleInit();
// Module 的 BeginPlay 函数
virtual void ModuleBeginPlay();
// Module 的 Tick 函数
virtual void ModuleTick(float DeltaSeconds);
// 去掉 TickComponent() 和 BeginPlay()
public:
// 存放子模组的数组
TArray<UDDModule*> ChildrenModule;
protected:
// 数据模块指针
UDDModel* Model;
// 消息模块指针
UDDMessage* Message;
// 资源模块指针
UDDWealth* Wealth;
};
DDModule.cpp
// 引入头文件
#include "DDCore/DDModel.h"
#include "DDCore/DDWealth.h"
UDDModule::UDDModule()
{
PrimaryComponentTick.bCanEverTick = false; // 改成 false,避免运行原生 Tick()
}
void UDDModule::CreateManager()
{
// 实例化组件,这里用 NewObject 实例化组件时不能在括号内填 this,否则编辑器运行游戏退出时会崩溃
Model = NewObject<UDDModel>();
Message = NewObject<UDDMessage>();
Wealth = NewObject<UDDWealth>();
// 避免被 GC 销毁
Model->AddToRoot();
Message->AddToRoot();
Wealth->AddToRoot();
// 指定模组
Model->AssignModule(this);
Message->AssignModule(this);
Wealth->AssignModule(this);
}
void UDDModule::ModuleInit()
{
// 调用 Init 函数
Model->ModelInit();
Message->MessageInit();
Wealth->WealthInit();
DDH::Debug() << GetFName() << " --> ModuleInit" << DDH::Endl();
}
void UDDModule::ModuleBeginPlay()
{
// 调用 BeginPlay
Model->ModelBeginPlay();
Message->MessageBeginPlay();
Wealth->WealthBeginPlay();
DDH::Debug() << GetFName() << " --> ModuleBeginPlay" << DDH::Endl();
}
void UDDModule::ModuleTick(float DeltaSeconds)
{
// 调用 Tick
Model->ModelTick(DeltaSeconds);
Wealth->WealthTick(DeltaSeconds);
Message->MessageTick(DeltaSeconds);
DDH::Debug(0.f) << GetFName() << " --> ModuleTick" << DDH::Endl();
}
// 去掉 TickComponent() 和 BeginPlay()
中央模组负责存放和管理所有的子模组,它需要提供方法给 Driver 来启动子模组的生命周期。
DDCenterModule.h
UCLASS()
class DATADRIVEN_API UDDCenterModule : public UDDModule
{
GENERATED_BODY()
public:
// 迭代调用本模组以及子模组的 EditChangeModuleType 方法(后面课程会继续补充)
void IterChangeModuleType(UDDModule* Module, FName ModType);
// 递归创建模块
void IterCreateManager(UDDModule* Module);
// 递归初始化
void IterModuleInit(UDDModule* Module);
// 递归 BeginPlay
void IterModuleBeginPlay(UDDModule* Module);
// 递归 Tick
void IterModuleTick(UDDModule* Module, float DeltaSeconds);
};
DDCenterModule.cpp
void UDDCenterModule::IterChangeModuleType(UDDModule* Module, FName ModType)
{
for (int i = 0; i < Module->GetAttachChildren().Num(); ++i) {
UDDModule* ChildModule = Cast<UDDModule>(Module->GetAttachChildren()[i]);
if (ChildModule) {
// 添加这个对象到这个 Module 的 ChildModule
Module->ChildrenModule.Push(ChildModule);
IterChangeModuleType(ChildModule, ModType);
}
}
}
void UDDCenterModule::IterCreateManager(UDDModule* Module)
{
Module->CreateManager();
for (int i = 0; i < Module->ChildrenModule.Num(); ++i) {
IterCreateManager(Module->ChildrenModule[i]);
}
}
void UDDCenterModule::IterModuleInit(UDDModule* Module)
{
Module->ModuleInit();
for (int i = 0; i < Module->ChildrenModule.Num(); ++i) {
IterModuleInit(Module->ChildrenModule[i]);
}
}
void UDDCenterModule::IterModuleBeginPlay(UDDModule* Module)
{
Module->ModuleBeginPlay();
for (int i = 0; i < Module->ChildrenModule.Num(); ++i) {
IterModuleBeginPlay(Module->ChildrenModule[i]);
}
}
void UDDCenterModule::IterModuleTick(UDDModule* Module, float DeltaSeconds)
{
Module->ModuleTick(DeltaSeconds);
for (int i = 0; i < Module->ChildrenModule.Num(); ++i) {
IterModuleTick(Module->ChildrenModule[i], DeltaSeconds);
}
}
这时候再回头看一下 Driver 类,有利于梳理脉络。
下面的验证功能部分在下一节课才会讲到,为了连贯性我把它放到这里。
在 Content 下创建 Blueprint 文件夹,创建一个基于 DDDrive 的蓝图,取名叫 GameDriver_BP。
打开蓝图,在 Center 模组下添加两个 DDModule 并改名如下:
将蓝图放入场景中。运行游戏,可看见左上角按顺序输出 Debug 语句:
可见,Driver 启动生命周期,中央模组先进行模组的初始化,然后再初始化其下的子模组,并按照生命周期的顺序执行后续方法。
测试完毕后要将 DDModule.cpp 和 DDMessage.cpp 的 Debug 语句删掉。
这节课要探讨的是如何在 Driver 里准确地获得模组(即 DDModule 的实例)。
下图截取自梁迪老师的 DataDriven 思维导图和说明文档。
新建两个 C++ 的 Object 类,选择 DataDriven 模块,位于 Public/DDGame 路径下,分别取名叫 DGCommon 和 DGTypes。
来到 DGTypes,定义两个枚举,一个用于游戏场景内,一个用于菜单界面。
DGTypes.h
#pragma region GamePart
/**************************************************************************/
/* 开发的游戏内容放这里 Begin 开发的游戏内容放这里 */
/**************************************************************************/
// 模组约定,如果要使用 DataDriven 本身的 UI 框架,请一定要把 HUD 模组放在第二位,UIFrame 框架管理器始终在 HUD 模组下
UENUM()
enum class ERCGameModule : uint8
{
Center = 0, // 中央模组
HUD, // UI 模组,约定放在第二位,UI 框架会直接使用序号 1 去获取 HUD 模组,如果不使用 UI 框架,不需要强制在第二位
Player, // 玩家模组
};
UENUM()
enum class ERCMenuModule : uint8
{
Center = 0,
HUD,
};
/**************************************************************************/
/* 开发的游戏内容放这里 End 开发的游戏内容放这里 */
/**************************************************************************/
#pragma endregion
给 DGCommon 引入枚举的头文件。
DGCommon.h
#include "DGTypes.h" // 引入头文件
#include "DGCommon.generated.h"
然后又给 DDCommon 引入头文件,这样三者就连起来了。
然后添加几个方法,用于枚举名与枚举值、序号和文本类型的互相转换。
FName 在创建实例对象的时候,会自动注册到一个专门的哈希表,所以它的查找以及对比的效率比 FString 要高。
DDCommon.h
#include "DDGame/DGCommon.h" // 引入头文件
#include "DDCommon.generated.h"
namespace DDH
{
// 将传入的 Enum 值对应的 FString 输出
template<typename TEnum>
FORCEINLINE FString GetEnumValueAsString(const FString& Name, TEnum Value)
{
const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *Name, true);
if (!EnumPtr) return FString("InValid");
return EnumPtr->GetEnumName((int32)Value);
}
// 将传入的 Enum 值对应的 FName 输出
template<typename TEnum>
FORCEINLINE FName GetEnumValueAsName(const FString& Name, TEnum Value)
{
const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *Name, true);
if (!EnumPtr) return FName("InValid");
return FName(*EnumPtr->GetEnumName((int32)Value));
}
// 将传入的 FName 对应的 Enum 输出
template<typename TEnum>
FORCEINLINE TEnum GetEnumValueFromName(const FString& Name, FName Value)
{
const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *Name, true);
if (!EnumPtr) return TEnum(0);
return (TEnum)EnumPtr->GetIndexByName(Value);
}
// 将传入的 FName 对应的 Enum 的序号输出
FORCEINLINE int32 GetEnumIndexFromName(const FString& Name, FName Value)
{
const UEnum* EnumPtr = FindObject<UEnum>((UObject*)ANY_PACKAGE, *Name, true);
if (!EnumPtr) {
return -1;
}
return EnumPtr->GetIndexByName(Value);
}
}
再回到模组类,解决上节课未编写完整的 ModuleType 相关方法。
DDModule.h
public:
// 根据模组名字对应模组序号
void ChangeModuleType(FName ModuleType);
public:
// 对应枚举内的模组序号
UPROPERTY(VisibleAnywhere, Category = "DataDriven")
int32 ModuleIndex;
DDModule.cpp
void UDDModule::ChangeModuleType(FName ModuleType)
{
ModuleIndex = DDH::GetEnumIndexFromName(ModuleType.ToString(), GetFName());
if (ModuleIndex < 0) {
DDH::Debug() << "Generate Module Index Error --> " << GetName() << DDH::Endl();
}
}
来到中央模组类,给 IterChangeModuleType()
添加对模组名转换序号方法的调用。
声明一个保存模组的数组,方便取用。再配套两个方法来填充这个数组。
DDCenterModule.h
public:
// 提取所有模组到模组数组,传入枚举名字
void TotalGatherModule(FName ModType);
// 迭代提取所有模组到数组
void IterGatherModule(UDDModule* Module, TArray<UDDModule*>& GatherGroup);
protected:
// 保存模组的数组,顺序与枚举相同
TArray<UDDModule*> ModuleGroup;
DDCenterModule.cpp
void UDDCenterModule::IterChangeModuleType(UDDModule* Module, FName ModType)
{
Module->ChangeModuleType(ModType);
}
void UDDCenterModule::TotalGatherModule(FName ModType)
{
if (ModType.IsNone()) return;
// 先获取所有的模组到 GatherGroup
TArray<UDDModule*> GatherGroup;
IterGatherModule(this, GatherGroup);
// 获取枚举的元素数量
int32 ModuleNum = FindObject<UEnum>((UObject*)ANY_PACKAGE, *(ModType.ToString()), true)->GetMaxEnumValue();
// 填充空对象到 ModuleGroup
for (int i = 0; i < ModuleNum; ++i) {
ModuleGroup.Push(NULL);
}
// 按模组 ID 填充模组到 ModuleGroup
for (int i = 0; i < GatherGroup.Num(); ++i) {
ModuleGroup[GatherGroup[i]->ModuleIndex] = GatherGroup[i];
}
}
void UDDCenterModule::IterGatherModule(UDDModule* Module, TArray<UDDModule*>& GatherGroup)
{
GatherGroup.Push(Module);
for (int i = 0; i < Module->ChildrenModule.Num(); ++i) {
IterGatherModule(Module->ChildrenModule[i], GatherGroup);
}
}
给 Driver 添加一个只要 ModuleType 变了就迭代更新模组序号的方法。以及在初始化框架的时候让中央模组填充模组数组。
DDDriver.h
public:
#if WITH_EDITOR
// 属性修改方法
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
DDDriver.cpp
void ADDDriver::PostInitializeComponents()
{
// 指定完模组 ID 后收集模组到总数组
Center->TotalGatherModule(ModuleType);
Center->IterCreateManager(Center);
}
#if WITH_EDITOR
void ADDDriver::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(ADDDriver, ModuleType)) {
Center->IterChangeModuleType(Center, ModuleType);
}
}
#endif
编译后,运行游戏,可以看到右上角 Debug 语句报告生成模组序列错误。
打开 GameDriver_BP,在细节面板将其 Data Driven 目录下的 ModuleType 的值设置为 ERCGameModule。再次运行游戏,可以看到左上角的 Debug 报错没有了。(不要设置成 ERCMenuModule,否则会因为已有的模组多于枚举内模组而报错)
下图截取自梁迪老师的 DataDriven 说明文档。
我们打算让对象通过指定一个 FName 类型的模组名来将其注册到指定的模组底下,并且注册到 DDModel 这个数据模块来方便保存数据。
DDOO 作为一个接口,它的作用是让继承它的类能与框架交互。
DDOO.h
// 引入头文件
#include "DDCore/DDDriver.h"
#include "DDCore/DDModule.h"
// 引入完毕
#include "DDOO.generated.h"
class DATADRIVEN_API IDDOO
{
GENERATED_BODY()
public:
// 注册到框架
void RegisterToModule(FName ModName, FName ObjName = FName(), FName ClsName = FName());
// 获取对象名
FName GetObjectName();
// 获取类名
FName GetClassName();
// 获取模组 Index
int32 GetModuleIndex() const;
// 获取 Object 主体
UObject* GetObjectBody() const;
// 从外部指定模组
void AssignModule(UDDModule* Mod);
protected:
// 保存自身的 UObject
UObject* IBody;
// 保存对应的模组
UDDModule* IModule;
// 保存驱动器
ADDDriver* IDriver;
// 对象名字
FName IObjectName;
// 对象类名字
FName IClassName;
// 对应模组的序号
int32 ModuleIndex;
};
DDOO.cpp
void IDDOO::RegisterToModule(FName ModName, FName ObjName, FName ClsName)
{
// 判断是否已经注册到框架了
if (IDriver && IModule) return;
// 如果模组名为空,直接返回
if (ModName.IsNone()) return;
// 指定对象名和类名
if (!ObjName.IsNone()) IObjectName = ObjName;
if (ClsName.IsNone()) IClassName = ClsName;
// 获取 UObject 主体
IBody = Cast<UObject>(this);
// 获取驱动器
IDriver = UDDCommon::Get()->GetDriver();
// 注册到模组
if (IDriver) {
// 如果获得的 ID 为负直接返回
ModuleIndex = DDH::GetEnumIndexFromName(IDriver->ModuleType.ToString(), ModName);
if (ModuleIndex < 0) {
DDH::Debug() << GetObjectName() << " Get " << ModName << " ModuleIndex Failed!" << DDH::Endl();
return;
}
// 如果注册不成功说明还没有实例化对应的 Module
if (!IDriver->RegisterToModule(this)) {
DDH::Debug() << GetObjectName() << " Register To " << ModName << " Failed!" << DDH::Endl();
}
}
else {
// DDriver 不存在
DDH::Debug() << GetObjectName() << " Get DDDriver Failed!" << DDH::Endl();
}
}
FName IDDOO::GetObjectName()
{
if (!IObjectName.IsNone()) return IObjectName;
IObjectName = IBody->GetFName();
return IObjectName;
}
FName IDDOO::GetClassName()
{
if (!IClassName.IsNone()) return IClassName;
IClassName = IBody->StaticClass()->GetFName();
return IClassName;
}
int32 IDDOO::GetModuleIndex() const
{
return ModuleIndex;
}
UObject* IDDOO::GetObjectBody() const
{
return IBody;
}
void IDDOO::AssignModule(UDDModule* Mod)
{
IModule = Mod;
}
Driver 通过 DDOO 传递数据给中央模组判定对象指定的模组名所对应的序号是否有效,有效则将该对象注册给对应的模组和 DDModel。
DDDriver.h
class IDDOO; // 提前声明
UCLASS()
class DATADRIVEN_API ADDDriver : public AActor
{
GENERATED_BODY()
public:
// 提供给资源们进行注册
bool RegisterToModule(IDDOO* ObjectInst);
};
DDDriver.cpp
// 引入头文件
#include "DDObject/DDOO.h"
bool ADDDriver::RegisterToModule(IDDOO* ObjectInst)
{
// 调用中央模组进行注册
return Center->RegisterToModule(ObjectInst);
}
DDModel 这里先定义空白的注册方法,后续再继续补充。
DDModel.h
class IDDOO;
UCLASS()
class DATADRIVEN_API UDDModel : public UObject, public IDDMM
{
GENERATED_BODY()
public:
// 注册对象到数据模块
void RegisterObject(IDDOO* ObjectInst);
};
DDModel.cpp
// 引入头文件
#include "DDObject/DDOO.h"
void UDDModel::RegisterObject(IDDOO* ObjectInst)
{
}
提供一个方法,让传过来的对象注册到 DDModel 里,同时也使模组自己作为对象的模组。
DDModule.h
class IDDOO; // 提前声明
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DATADRIVEN_API UDDModule : public USceneComponent
{
GENERATED_BODY()
public:
// 注册对象到数据模块
void RegisterObject(IDDOO* ObjectInst);
};
DDModule.cpp
// 引入头文件
#include "DDObject/DDOO.h"
void UDDModule::RegisterObject(IDDOO* ObjectInst)
{
// 注册对象到数据组件
Model->RegisterObject(ObjectInst);
// 把自己注册到对象的模组
ObjectInst->AssignModule(this);
}
终于来到中央模组,它要做的就是运用上面的类提供的方法使对象能够注册到模组和 DDModel。
DDCenterModule.h
class IDDOO; // 声明
UCLASS()
class DATADRIVEN_API UDDCenterModule : public UDDModule
{
GENERATED_BODY()
public:
// 注册对象到模组
bool RegisterToModule(IDDOO* ObjectInst);
};
DDCenterModule.cpp
// 引入头文件
#include "DDObject/DDOO.h"
bool UDDCenterModule::RegisterToModule(IDDOO* ObjectInst)
{
// 判断模组 ID 是否有效并且注册
if (ObjectInst->GetModuleIndex() < ModuleGroup.Num() && ModuleGroup[ObjectInst->GetModuleIndex()]) {
ModuleGroup[ObjectInst->GetModuleIndex()]->RegisterObject(ObjectInst);
return true;
}
return false;
}
下面的测试内容是下一节课才会进行演示的,为了连贯性,笔者就安排在这里了。
DDActor 用于作为框架内 Actor 的基类,声明三个 FName 变量,指定其目标模组、对象名和类名。
去掉原生的 Tick()
,因为框架本身有自己的 Tick。
DDActor.h
#include "DDOO.h" // 引入头文件
#include "DDActor.generated.h"
UCLASS()
class DATADRIVEN_API ADDActor : public AActor, public IDDOO // 继承接口
{
GENERATED_BODY()
public:
// 模组名字,如果为空,说明要手动指定,不为空就是自动指定
UPROPERTY(EditAnywhere, Category = "DataDriven")
FName ModuleName;
// 对象名字,如果为空,说明要手动指定,不为空就是自动指定
// 这个名字是独一无二的
UPROPERTY(EditAnywhere, Category = "DataDriven")
FName ObjectName;
// 类名字,如果为空,说明要手动指定,不为空就是自动指定
UPROPERTY(EditAnywhere, Category = "DataDriven")
FName ClassName;
// 去掉 Tick()
};
DDActor.cpp
ADDActor::ADDActor()
{
PrimaryActorTick.bCanEverTick = false; // 关闭原生 Tick
}
void ADDActor::BeginPlay()
{
Super::BeginPlay();
// 自动注册到框架
RegisterToModule(ModuleName, ObjectName, ClassName);
}
// 去掉 Tick()
编译后,在 Blueprint 文件夹下创建 DDActor 的蓝图,取名叫 ShowActor_BP,然后将其放进场景里。
打开蓝图,在其细节面板的 DataDriven 目录下随便修改 ModuleName 的值,如 Nothing。运行游戏,可以看见左上角显示 Debug 语句显示获取模组序列失败。
若把 ModuleName 的值改成 Player 这个有效的枚举值,运行游戏,左上角没有 Debug 提示,则说明对象注册模组和成功了。