UE4运用C++和框架开发坦克大战教程笔记(六)(第16~19集)
16. 生命周期系统一
DDModel 保存对象,并且也管理这些对象的生命周期。
下图截取自梁迪老师的 DataDriven 思维导图和说明文档。
UE4 的原生对象的生命周期没有这么多的方法,老师是以自己以前研究 Unity 的经验来复刻出这一套生命周期。框架里的对象根据自身生命周期的不同时刻处于对应的状态枚举(如右侧所示)。
接下来我们编写一下框架内对象的生命周期所用到的方法。
来到 DDTypes,声明两个对象生命周期相关的枚举。
再顺便声明一个根据对象名字或类名来调整查找对象的方式的枚举(比如获取这个名字的对象;或者是获取这个类所有的对象)。
DDTypes.h
#pragma region LifeTimePart
// BaseObject 生命周期
UENUM()
enum class EBaseObjectLife : uint8
{
None = 0, // 初始化状态
Init,
Loading,
Register,
Enable,
Disable,
UnRegister,
UnLoading
};
// BaseObject 生命周期状态
UENUM()
enum class EBaseObjectState : uint8
{
Active = 0, // 激活
Stable, // 稳定
Destroy // 销毁
};
#pragma endregion
#pragma region ReflectPart
// 对象调用协议
UENUM()
enum class EAgreementType : uint8
{
SelfObject, // 给传入的对象通信
OtherObject, // 给传入的对象之外的对象通信
ClassOtherObject, // 给传入的对象的相同类的其他对象通信,调用这个方法要确保传过来的对象都是同一类的,如果不同类就多次通信
SelfClass, // 给这个类的对象通信
OtherClass, // 给这个类之外的类通信
All // 给所有的对象通信
};
#pragma endregion
既然是框架内对象的生命周期,那么我们把方法安排在框架内对象都要继承的 DDOO 接口。
DDOO.h
public:
// 激活生命周期,激活成功后返回 true,停止调用
bool ActiveLife();
// 销毁生命周期函数,销毁成功后返回 true,从数据模块注销
bool DestroyLife();
// 生命周期,由模组管理器调用
virtual void DDInit(); // 初始化
virtual void DDLoading(); // 加载绑定的资源
virtual void DDRegister(); // 注册数据或者事件
virtual void DDEnable(); // 激活对象
virtual void DDTick(float DeltaSeconds); // 帧函数
virtual void DDDisable(); // 失活对象
virtual void DDUnRegister(); // 注销数据或者事件
virtual void DDUnLoading(); // 销毁绑定资源
virtual void DDRelease(); // 释放自己
// 激活对象
virtual void OnEnable();
// 失活对象
virtual void OnDisable();
// 销毁自己
void DDDestroy();
public:
// 是否允许帧运行,如果要允许帧运行需要在构造函数或者 BeginPlay() 设置,在 UE4 里默认为 false
bool IsAllowTickEvent;
// 生命周期
EBaseObjectLife LifeState;
// 生命运行状态
EBaseObjectState RunState;
生命周期的一些方法的实现部分先空着,后面再补充。
DDOO.cpp
bool IDDOO::ActiveLife()
{
switch (LifeState)
{
case EBaseObjectLife::None:
DDInit();
LifeState = EBaseObjectLife::Init;
break;
case EBaseObjectLife::Init:
DDLoading();
LifeState = EBaseObjectLife::Loading;
break;
case EBaseObjectLife::Loading:
DDRegister();
LifeState = EBaseObjectLife::Register;
break;
case EBaseObjectLife::Register:
DDEnable();
LifeState = EBaseObjectLife::Enable;
// 设置运行状态为稳定
RunState = EBaseObjectState::Stable;
// 返回 true,停止运行激活状态函数
return true;
}
return false;
}
bool IDDOO::DestroyLife()
{
switch (LifeState)
{
case EBaseObjectLife::Enable:
DDDisable();
LifeState = EBaseObjectLife::Disable;
// 设置状态为销毁
RunState = EBaseObjectState::Destroy;
break;
case EBaseObjectLife::Disable:
DDUnRegister();
LifeState = EBaseObjectLife::UnRegister;
// 设置运行状态为销毁,避免从 Disable 状态下运行的对象没有修改 RunState 为销毁
RunState = EBaseObjectState::Destroy;
break;
case EBaseObjectLife::UnRegister:
DDUnLoading();
LifeState = EBaseObjectLife::UnLoading;
// 返回 true,停止运行销毁状态函数
return true;
}
return false;
}
void IDDOO::DDInit()
{}
void IDDOO::DDLoading()
{}
void IDDOO::DDRegister()
{}
void IDDOO::DDEnable()
{}
void IDDOO::DDTick(float DeltaSeconds)
{}
void IDDOO::DDDisable()
{}
void IDDOO::DDUnRegister()
{}
void IDDOO::DDUnLoading()
{}
void IDDOO::DDRelease()
{}
void IDDOO::OnEnable()
{
// 设置状态为激活状态
LifeState = EBaseObjectLife::Enable;
}
void IDDOO::OnDisable()
{
// 设置状态为失活状态
LifeState = EBaseObjectLife::Disable;
}
void IDDOO::DDDestroy()
{
}
来到负责管理对象生命周期的 DDModel,完善之前没写完的 RegisterObject()
。
声明三个用于保存对象的容器。
DDModel.h
protected:
// 框架对象数组,Key : ObjectName, Value : *
TMap<FName, IDDOO*> ObjectGroup;
// 框架对象类集合,Key : ClassName,Value : TArray<IDDOO*>
TMap<FName, TArray<IDDOO*>> ObjectClassGroup;
// 需要激活生命周期的对象集
TArray<IDDOO*> ObjectActiveGroup;
DDModel.cpp
void UDDModel::RegisterObject(IDDOO* ObjectInst)
{
// 如果不重复就添加到模组对象
if (!ObjectGroup.Contains(ObjectInst->GetObjectName())) {
// 添加到对象组
ObjectGroup.Add(ObjectInst->GetObjectName(), ObjectInst);
// 添加到对象类组
FName ObjectClassName = ObjectInst->GetClassName();
if (ObjectClassGroup.Contains(ObjectClassName)) {
ObjectClassGroup.Find(ObjectClassName)->Push(ObjectInst);
}
else {
TArray<IDDOO*> ObjectArray;
ObjectClassGroup.Add(ObjectClassName, ObjectArray);
ObjectClassGroup.Find(ObjectClassName)->Push(ObjectInst);
}
// 添加对象到激活生命周期组
ObjectActiveGroup.Push(ObjectInst);
}
else {
// 有重复注册进行 Debug
DDH::Debug() << "Object Repeated --> " << ObjectInst->GetObjectName() << DDH::Endl();
}
}
void UDDModel::ModelTick(float DeltaSeconds)
{
}
如果编译没问题,则可以继续下一节课了。
17. 生命周期系统二
继续补充 DDModel 内的逻辑。
声明一个数组,专门保存需要运行 Tick 函数的对象。
通过 ModelTick()
来让需要运行 Tick 的对象参与到 Tick 里。
DDModel.h
protected:
// 运行需要运行 Tick 函数的对象集合
TArray<IDDOO*> ObjectTickGroup;
DDModel.cpp
void UDDModel::ModelTick(float DeltaSeconds)
{
// 运行 Tick 组的 Tick 函数,放在此处确保在 Tick 组里的对象才会执行 DDTick()
for (int i = 0; i < ObjectTickGroup.Num(); ++i)
ObjectTickGroup[i]->DDTick(DeltaSeconds);
// 临时保存完成某个周期的对象
TArray<IDDOO*> TempObjectGroup;
// 循环运行激活生命周期函数
for (int i = 0; i < ObjectActiveGroup.Num(); ++i) {
// 如果激活成功
if (ObjectActiveGroup[i]->ActiveLife()) {
TempObjectGroup.Push(ObjectActiveGroup[i]);
}
}
// 将运行完激活生命周期的对象移出激活生命周期组,并且将运行 Tick 的对象添加到 Tick 组
for (int i = 0; i < TempObjectGroup.Num(); ++i) {
ObjectActiveGroup.Remove(TempObjectGroup[i]);
if (TempObjectGroup[i]->IsAllowTickEvent)
ObjectTickGroup.Push(TempObjectGroup[i]);
}
}
编译后,创建一个 C++ 的 DDActor 类,目标模块选择 RaceCarFrame (Runtime),路径直接为 Source/RaceCarFrame,取名为 LifeCallActor。
在 LifeCallActor 重写 DDOO 的激活周期相关的方法,添加一些 Debug 语句,简单测试下其生命周期。
LifeCallActor.h
public:
virtual void DDInit() override;
virtual void DDLoading() override;
virtual void DDRegister() override;
virtual void DDEnable() override;
virtual void DDTick(float DeltaSeconds) override;
LifeCallActor.cpp
void ALifeCallActor::DDInit()
{
Super::DDInit();
IsAllowTickEvent = true; // 如果改成 false 则关闭 DDTick()
DDH::Debug() << "DDInit" << DDH::Endl();
}
void ALifeCallActor::DDLoading()
{
DDH::Debug() << "DDLoading" << DDH::Endl();
}
void ALifeCallActor::DDRegister()
{
DDH::Debug() << "DDRegister" << DDH::Endl();
}
void ALifeCallActor::DDEnable()
{
DDH::Debug() << "DDEnable" << DDH::Endl();
}
void ALifeCallActor::DDTick(float DeltaSeconds)
{
DDH::Debug(0.f) << "DDTick" << DDH::Endl();
}
编译后,来到 Blueprint 文件夹,之前创建的 ShowActor_BP 可以删掉了。
在文件夹内创建 LifeCallActor 的蓝图,取名为 LifeCallActor_BP。打开蓝图,在其细节面板的 DataDriven 目录下,将 Module Name 的值设置为 Center。
将蓝图放进场景中,运行游戏,可以看见左上角的 Debug 语句。说明框架内的对象在激活生命周期中运行正常。
销毁生命周期
接下来我们测试下框架内对象的销毁生命周期。
来到 DDModel,声明一个指定对象名来销毁对象的方法;再声明三个容器用于保存进入销毁流程的对象。
DDModel.h
public:
// 通过对象名销毁对象,销毁对象本体调用
void DestroyObject(FName ObjectName);
protected:
// 销毁生命周期的对象集
TArray<IDDOO*> ObjectDestroyGroup;
// 释放周期函数对象集合,只执行一次就全部释放
TArray<IDDOO*> ObjectReleaseGroup;
// 预销毁对象组,这些对象还处于激活生命周期,但是在激活生命周期没运行完之前就已经设定为要进行销毁
TArray<IDDOO*> PreObjectDestroyGroup;
在 ModelTick()
里添加让对象进入销毁生命周期的逻辑。
由于老师不希望让一个仍处于激活中的、但已经预定要被销毁的对象直接在同一帧内从 “激活完毕” 直接进入到 “销毁 + 释放” 的流程,所以要把 “销毁 + 释放” 的逻辑放在 “把对象放进销毁对象组” 这个操作之前。
同理,释放的逻辑也放在销毁的逻辑之前,让方法处理的对象都是上一帧得出来的对象。
DDModel.cpp
void UDDModel::ModelTick(float DeltaSeconds)
{
// 执行释放对象函数,清空释放组
for (int i = 0; i < ObjectReleaseGroup.Num(); ++i)
ObjectReleaseGroup[i]->DDRelease();
ObjectReleaseGroup.Empty();
// 清空临时对象组
TempObjectGroup.Empty();
// 运行销毁对象组的销毁生命周期函数
for (int i = 0; i < ObjectDestroyGroup.Num(); ++i) {
// 如果销毁生命周期执行完毕
if (ObjectDestroyGroup[i]->DestroyLife()) {
// 添加对象到释放对象组
ObjectReleaseGroup.Push(ObjectDestroyGroup[i]);
// 添加对象到临时组
TempObjectGroup.Push(ObjectDestroyGroup[i]);
// 清除掉该销毁的对象
ObjectGroup.Remove(ObjectDestroyGroup[i]->GetObjectName());
ObjectClassGroup.Find(ObjectDestroyGroup[i]->GetClassName())->Remove(ObjectDestroyGroup[i]);
// 如果类对象数组为空
if (ObjectClassGroup.Find(ObjectDestroyGroup[i]->GetClassName())->Num() == 0)
ObjectClassGroup.Remove(ObjectDestroyGroup[i]->GetClassName());
}
}
// 把销毁的对象从销毁对象组移除
for (int i = 0; i < TempObjectGroup.Num(); ++i)
ObjectDestroyGroup.Remove(TempObjectGroup[i]);
// 清空临时对象组
TempObjectGroup.Empty();
// 处理预销毁对象组,如果对象进入稳定状态,就跳转到临时对象组
for (int i = 0; i < PreObjectDestroyGroup.Num(); ++i) {
if (PreObjectDestroyGroup[i]->RunState == EBaseObjectState::Stable) {
// 添加稳定运行状态的对象到临时对象组
TempObjectGroup.Push(PreObjectDestroyGroup[i]);
}
}
// 从预销毁对象组中清除稳定状态对象
for (int i = 0; i < TempObjectGroup.Num(); ++i) {
PreObjectDestroyGroup.Remove(TempObjectGroup[i]);
// 添加到销毁对象组
ObjectDestroyGroup.Push(TempObjectGroup[i]);
// 移除出帧函数组
ObjectTickGroup.Remove(TempObjectGroup[i]);
}
}
void UDDModel::DestroyObject(FName ObjectName)
{
// 获取需要销毁的对象
if (ObjectGroup.Contains(ObjectName)) {
IDDOO* TargetObject = *ObjectGroup.Find(ObjectName);
// 如果销毁对象组以及预销毁对象组都没有该对象
if (!ObjectDestroyGroup.Contains(TargetObject) && !PreObjectDestroyGroup.Contains(TargetObject)) {
// 如果是稳定状态就添加到 ObjectDestroyGroup,如果是激活状态就添加到 PreObjectDestroyGroup
switch (TargetObject->RunState)
{
case EBaseObjectState::Active:
PreObjectDestroyGroup.Push(TargetObject);
break;
case EBaseObjectState::Stable:
ObjectDestroyGroup.Push(TargetObject);
ObjectTickGroup.Remove(TargetObject);
break;
}
}
}
}
如果编译成功,则说明没有问题,下一节课会继续补充销毁过程的代码,并测试对象的销毁生命周期。
18. 数据模块系统一
接下来测试销毁方法。先来到 DDModule 声明一个销毁模组下指定名字的对象的方法。
DDModule.h
public:
// 模组所属对象销毁自身
void ChildDestroy(FName ObjectName);
DDModule.cpp
void UDDModule::ChildDestroy(FName ObjectName)
{
Model->DestroyObject(ObjectName);
}
某些类调用获取世界的方法会返回空,所以给 DDOO 声明一个获取世界的方法,用于调用引擎原生的销毁函数。
DDOO.h
public:
// 框架获取世界函数
UWorld* GetDDWorld() const;
让 DDOO 的 DDDestroy()
调用 DDModule 的销毁方法。
DDOO.cpp
UWorld* IDDOO::GetDDWorld() const
{
// 通过 Driver 获取世界,必然可以获取到
if (IDriver)
return IDriver->GetWorld();
return NULL;
}
void IDDOO::DDDestroy()
{
IModule->ChildDestroy(GetObjectName());
}
比如 DDUserWidget、DDObject 等可能就不能直接获取到世界。之前忘了给 DDObject 继承一下 DDOO 的接口。
DDObject.h
#include "DDOO.h" // 引入头文件
#include "DDObject.generated.h"
UCLASS()
class DATADRIVEN_API UDDObject : public UObject, public IDDOO // 继承接口
{
GENERATED_BODY()
};
不同对象之间释放资源的操作可能有差异。所以我们让继承 DDOO 接口的类来重写释放函数。
DDActor.h
public:
// 重写释放函数
virtual void DDRelease() override;
DDActor.cpp
void ADDActor::DDRelease()
{
IDDOO::DDRelease();
// 能调用这个方法那么一定是注册到了框架,获取的世界一定不为空
GetDDWorld()->DestroyActor(this);
}
来到 LifeCallActor,重写销毁周期相关的方法,添加一些 Debug 语句测试下销毁生命周期。
声明一个整型变量,我们希望让对象在第三帧的时候进入销毁流程。
LifeCallActor.h
public:
virtual void DDDisable() override; // 失活对象
virtual void DDUnRegister() override; // 注销数据或者事件
virtual void DDUnLoading() override; // 销毁绑定资源
virtual void DDRelease() override; // 释放自己
protected:
int32 TimeCounter;
给这些生命周期相关的方法都补充下对父类函数的调用。
LifeCallActor.cpp
void ALifeCallActor::DDInit()
{
TimeCounter = 0; // 初始化计时器
DDH::Debug() << "DDInit" << DDH::Endl();
}
void ALifeCallActor::DDLoading()
{
Super::DDLoading();
DDH::Debug() << "DDLoading" << DDH::Endl();
}
void ALifeCallActor::DDRegister()
{
Super::DDRegister();
DDH::Debug() << "DDRegister" << DDH::Endl();
}
void ALifeCallActor::DDEnable()
{
Super::DDEnable();
DDH::Debug() << "DDEnable" << DDH::Endl();
}
void ALifeCallActor::DDTick(float DeltaSeconds)
{
Super::DDTick(DeltaSeconds);
TimeCounter++;
if (TimeCounter < 3) {
DDH::Debug() << "DDTick --> " << TimeCounter << DDH::Endl();
}
else if (TimeCounter == 3) {
DDDestroy();
DDH::Debug() << "DDDestroy" << DDH::Endl();
}
}
void ALifeCallActor::DDDisable()
{
Super::DDDisable();
DDH::Debug() << "DDDisable" << DDH::Endl();
}
void ALifeCallActor::DDUnRegister()
{
Super::DDUnRegister();
DDH::Debug() << "DDUnRegister" << DDH::Endl();
}
void ALifeCallActor::DDUnLoading()
{
Super::DDUnLoading();
DDH::Debug() << "DDUnLoading" << DDH::Endl();
}
void ALifeCallActor::DDRelease()
{
Super::DDRelease();
DDH::Debug() << "DDRelease" << DDH::Endl();
}
打开 LifeCallActor_BP,给其添加一个网格体组件,然后任意赋予一个网格体。确保其 Tick 函数可以运行。
运行游戏,可以看到场景的 LifeCallActor_BP 实例被销毁了,并且左上角按生命周期的顺序输出了 Debug 语句。
前面说到不同对象的销毁方式可能有差异。比如 Actor 可以用 UE4 提供的 DestroyActor()
来销毁并释放(但并不是立刻销毁,而是在空闲时在销毁并释放)。老师之所以要设计 DDRelease()
和 ObjectReleaseGroup
是为了让将要释放的对象在完全不需要的时候才进行释放,否则立即销毁可能会导致一些指向对象的、还要使用的指针变成野指针。
通过对象名字批量获取目标对象
我们前面实现了让指定名字的对象销毁的方法,现在我们想一次性让多个对象改变生命周期状态。这时候就需要用到前面声明的 EAgreementType
枚举来选中相应对象了。
来到 DDModel,声明三个方法用于改变目标对象组的生命周期状态,不过我们先留到后面再写。然后再声明两个方法,它们会用两种不同的方式获取目标对象,对应了 EAgreementType
的前两个枚举值。
DDModel.h
public:
// 销毁对象
void DestroyObject(EAgreementType Agreement, TArray<FName> TargetNameGroup);
// 激活对象
void EnableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup);
// 失活对象
void DisableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup);
// 根据传入的对象名获取对象
void GetSelfObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup);
// 根据传入的对象名获取这些对象名对应对象外的其他对象
void GetOtherObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup);
DDModel.cpp
void UDDModel::DestroyObject(EAgreementType Agreement, TArray<FName> TargetNameGroup)
{
}
void UDDModel::EnableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup)
{
}
void UDDModel::DisableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup)
{
}
void UDDModel::GetSelfObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup)
{
for (int i = 0; i < TargetNameGroup.Num(); ++i)
if (ObjectGroup.Contains(TargetNameGroup[i]))
TargetObjectGroup.Push(*ObjectGroup.Find(TargetNameGroup[i]));
}
void UDDModel::GetOtherObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup)
{
for (TMap<FName, IDDOO*>::TIterator It(ObjectGroup); It; ++It) {
bool IsSame = false;
for (int i = 0; i < TargetNameGroup.Num(); ++i) {
// 检测名字是否相同,相同就跳出
if (TargetNameGroup[i].IsEqual(It->Key)) {
IsSame = true;
break;
}
}
if (!IsSame)
TargetObjectGroup.Push(It->Value);
}
}
剩余的部分留到下一节课继续完善。
19. 数据模块系统二
继续在 DDModel 里完善获取框架内对象的四个方法,毕竟对象获取协议的枚举 EAgreementType
是定义了六个枚举值的。
再声明一个方法,根据传入的 EAgreementType
的值来以相应的方法获取对象。
DDModel.h
public:
// 根据名字数组获取相同类的其他对象,返回这个类的对象的数量
// 名字数组里面对应的对象必须都是同一个类的
void GetClassOtherObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup);
// 根据名字获取类的对象
void GetSelfClass(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup);
// 根据名字获取类以外的对象
void GetOtherClass(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup);
// 获取所有对象
void GetAll(TArray<IDDOO*>& TargetObjectGroup);
// 根据协议获取对象集合
void GetAgreementObject(EAgreementType Agreement, TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup);
并且把我们上节课声明好的,批量改变对象生命周期状态的方法给补充上逻辑。
DDModel.cpp
void UDDModel::DestroyObject(EAgreementType Agreement, TArray<FName> TargetNameGroup)
{
// 定义获取到的对象数组
TArray<IDDOO*> TargetObjectGroup;
// 根据协议获取对象
GetAgreementObject(Agreement, TargetNameGroup, TargetObjectGroup);
// 批量销毁对象
for (int i = 0; i < TargetObjectGroup.Num(); ++i) {
// 如果销毁对象组以及预销毁对象组都没有该对象
if (!ObjectDestroyGroup.Contains(TargetObjectGroup[i]) && !PreObjectDestroyGroup.Contains(TargetObjectGroup[i])) {
// 如果是稳定状态就添加到 ObjectDestroyGroup,如果是激活状态就添加到 PreObjectDestroyGroup
switch(TargetObjectGroup[i]->RunState)
{
case EBaseObjectState::Active:
PreObjectDestroyGroup.Push(TargetObjectGroup[i]);
break;
case EBaseObjectState::Stable:
ObjectDestroyGroup.Push(TargetObjectGroup[i]);
ObjectTickGroup.Remove(TargetObjectGroup[i]);
break;
}
}
}
}
void UDDModel::EnableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup)
{
// 定义获取到的对象数组
TArray<IDDOO*> TargetObjectGroup;
// 根据协议获取对象
GetAgreementObject(Agreement, TargetNameGroup, TargetObjectGroup);
for (int i = 0; i < TargetObjectGroup.Num(); ++i) {
// 如果这个对象处于稳定与失活状态就运行它的激活状态函数
if (TargetObjectGroup[i]->RunState == EBaseObjectState::Stable && TargetObjectGroup[i]->LifeState == EBaseObjectLife::Disable) {
TargetObjectGroup[i]->OnEnable();
}
}
}
void UDDModel::DisableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup)
{
// 定义获取到的对象数组
TArray<IDDOO*> TargetObjectGroup;
// 根据协议获取对象
GetAgreementObject(Agreement, TargetNameGroup, TargetObjectGroup);
for (int i = 0; i < TargetObjectGroup.Num(); ++i) {
// 如果这个对象处于稳定与失活状态就运行它的激活状态函数
if (TargetObjectGroup[i]->RunState == EBaseObjectState::Stable &&
TargetObjectGroup[i]->LifeState == EBaseObjectLife::Enable) {
TargetObjectGroup[i]->OnDisable();
}
}
}
void UDDModel::GetClassOtherObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup)
{
if (!ObjectGroup.Contains(TargetNameGroup[0]))
return;
// 先获取对象类名
FName ObjectClassName = (*ObjectGroup.Find(TargetNameGroup[0]))->GetClassName();
// 迭代对象类实例数组
for (TArray<IDDOO*>::TIterator It(*ObjectClassGroup.Find(ObjectClassName)); It; ++It) {
bool IsSame = false;
for (int i = 0; i < TargetNameGroup.Num(); ++i) {
if ((*It)->GetObjectName().IsEqual(TargetNameGroup[i])) {
IsSame = true;
break;
}
}
if (!IsSame)
TargetObjectGroup.Push(*It);
}
}
void UDDModel::GetSelfClass(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup)
{
for(int i = 0; i < TargetNameGroup.Num(); ++i) {
// 如果不包含这个类,跳到下一个循环
if (!ObjectClassGroup.Contains(TargetNameGroup[i]))
continue;
TargetObjectGroup.Append(*ObjectClassGroup.Find(TargetNameGroup[i]));
}
}
void UDDModel::GetOtherClass(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup)
{
for (TMap<FName, TArray<IDDOO*>>::TIterator It(ObjectClassGroup); It; ++It) {
if (!TargetNameGroup.Contains(It->Key))
TargetObjectGroup.Append(It->Value);
}
}
void UDDModel::GetAll(TArray<IDDOO*>& TargetObjectGroup)
{
ObjectGroup.GenerateValueArray(TargetObjectGroup);
}
void UDDModel::GetAgreementObject(EAgreementType Agreement, TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup)
{
switch(Agreement)
{
case EAgreementType::SelfObject:
GetSelfObject(TargetNameGroup, TargetObjectGroup);
break;
case EAgreementType::OtherObject:
GetOtherObject(TargetNameGroup, TargetObjectGroup);
break;
case EAgreementType::ClassOtherObject:
GetClassOtherObject(TargetNameGroup, TargetObjectGroup);
break;
case EAgreementType::SelfClass:
GetSelfClass(TargetNameGroup, TargetObjectGroup);
break;
case EAgreementType::OtherClass:
GetOtherClass(TargetNameGroup, TargetObjectGroup);
break;
case EAgreementType::All:
GetAll(TargetObjectGroup);
break;
}
}
DDActor 类有 BeginPlay()
,在里面调用注册到框架的方法后,只要把实例放进场景里就可以自动注册到框架。但是像 UObject、UserWidget 这些类就没有 BeginPlay()
,所以它们需要手动进行注册,后面会讲到。
来到接口类 DDOO,添加一个 “根据传入的模组 ID 来注册模组” 的方法。
DDOO.h
public:
void RegisterToModule(FName ModName, FName ObjName = FName(), FName ClsName = FName());
// 同上,只转入模组 Index
void RegisterToModule(int32 ModIndex, FName ObjName = FName(), FName ClsName = FName());
DDOO.cpp
void IDDOO::RegisterToModule(int32 ModIndex, FName ObjName, FName ClsName)
{
if (IDriver && IModule) return;
// 去掉对 ModName 的判断
if (!ObjName.IsNone()) IObjectName = ObjName;
if (ClsName.IsNone()) IClassName = ClsName;
IBody = Cast<UObject>(this);
IDriver = UDDCommon::Get()->GetDriver();
if (IDriver) {
// 直接提供模组序号
ModuleIndex = ModIndex;
if (ModuleIndex < 0) {
// 更改下 Debug 语句
DDH::Debug() << GetObjectName() << " Get ModuleIndex " << ModuleIndex << " Failed!" << DDH::Endl();
return;
}
if (!IDriver->RegisterToModule(this)) {
// 更改下 Debug 语句
DDH::Debug() << GetObjectName() << " Register To ModuleIndex " << ModuleIndex << " Failed!" << DDH::Endl();
}
}
else {
DDH::Debug() << GetObjectName() << " Get DDDriver Failed!" << DDH::Endl();
}
}
给 DDObject 和 DDUserWidget 也重写一下 DDRelease()
,因为它们的销毁方法不同。并且由于是手动注册到框架所以不需要 3 个名字变量了。
DDObject.h
public:
// 重写释放函数
virtual void DDRelease() override;
DDObject.cpp
void UDDObject::DDRelease()
{
IDDOO::DDRelease();
// 从 Root 移除
RemoveFromRoot();
// 准备回收资源
ConditionalBeginDestroy();
}
DDUserWidget.h
#include "DDOO.h" // 引入头文件
#include "DDUserWidget.generated.h"
UCLASS()
class DATADRIVEN_API UDDUserWidget : public UUserWidget, public IDDOO // 继承接口
{
GENERATED_BODY()
public:
// 重写释放函数
virtual void DDRelease() override;
};
DDUserWidget.h
void UDDUserWidget::DDRelease()
{
IDDOO::DDRelease();
// 再检查一次从父类移除
RemoveFromParent();
// 从 Root 移除
RemoveFromRoot();
// 准备回收资源
ConditionalBeginDestroy();
}
如果编译可以通过,则说明没问题了。