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();
}

如果编译可以通过,则说明没问题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值