UE4运用C++和框架开发坦克大战教程笔记(八)(第23~25集)

UE4运用C++和框架开发坦克大战教程笔记(八)(第23~25集)

  • 23. 对象反射执行代码
  • 24. 对象反射方法调用
    • 测试所有的对象间反射调用的情况
  • 25. 对象反射系统宏定义
    • 模块(DDMM)通过反射调用对象方法

23. 对象反射执行代码

我们之前在 DDModel 里面添加的三个批量修改对象生命周期状态的方法(DestroyObject()EnableObject()DisableObject())就是留给模组类的反射方法调用的。

既然现在框架内对象可以访问到模组的方法,而 DDModel 保存了框架内已注册对象,那么顺理成章的,只要 DDModel 提供给模组一套获取对象的方法,那么框架内的对象之间现在也可以执行反射调用了,所以接下来我们来完善这一过程需要用到的代码逻辑。

更改 DDModel 里原本两个 Get() 方法的返回类型为 int32,并添加返回的内容。方便后续在反射批量执行方法时衡量是否有目标对象不存在而错过了执行,这会影响到返回的执行结果的枚举值。

DDModel.h

public:

	// 根据传入的对象名获取这些对象名对应对象外的其他对象
	int32 GetOtherObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup);

	// 根据名字数组获取相同类的其他对象,返回这个类的对象的数量
	int32 GetClassOtherObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup);

DDModel.cpp

int32 UDDModel::GetOtherObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup)
{

	// 返回全部对象的数量
	return ObjectGroup.Num();
}

int32 UDDModel::GetClassOtherObject(TArray<FName> TargetNameGroup, TArray<IDDOO*>& TargetObjectGroup)
{
	if (!ObjectGroup.Contains(TargetNameGroup[0]))
		return 0;	// 返回 0

	// ...省略
	
	// 返回对象类名对应的对象组的数量
	return (*ObjectClassGroup.Find(ObjectClassName)).Num();
}

把 DDModule 类里面,在上一节课写的两个临时方法的声明和定义都删掉。

随后添加三个批量修改对象生命周期状态的传递调用方法。并且添加六个执行方法,它们分别可以配合 DDModel 的六种获取目标对象的方法。

DDModule.h

public:

	// 反射事件批量销毁对象
	UFUNCTION()
	void DestroyObject(EAgreementType Agreement, TArray<FName> TargetNameGroup);

	// 反射事件批量激活对象
	UFUNCTION()
	void EnableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup);

	// 反射事件批量失活对象
	UFUNCTION()
	void DisableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup);

	// 删掉之前的测试用方法

protected:

	// 执行单个对象方法
	void ExecuteSelfObject(DDObjectAgreement Agreement, DDParam* Param);
	// 执行其余对象的方法
	void ExecuteOtherObject(DDObjectAgreement Agreement, DDParam* Param);
	// 支持相同类的区域对象方法
	void ExecuteClassOtherObject(DDObjectAgreement Agreement, DDParam* Param);
	// 执行类对象的方法
	void ExecuteSelfClass(DDObjectAgreement Agreement, DDParam* Param);
	// 执行其他类对象的方法
	void ExecuteOtherClass(DDObjectAgreement Agreement, DDParam* Param);
	// 执行所有对象的方法
	void ExecuteAll(DDObjectAgreement Agreement, DDParam* Param);

DDModule.cpp

void UDDModule::DestroyObject(EAgreementType Agreement, TArray<FName> TargetNameGroup)
{
	Model->DestroyObject(Agreement, TargetNameGroup);
}

void UDDModule::EnableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup)
{
	Model->EnableObject(Agreement, TargetNameGroup);
}

void UDDModule::DisableObject(EAgreementType Agreement, TArray<FName> TargetNameGroup)
{
	Model->DisableObject(Agreement, TargetNameGroup);
}

void UDDModule::ExecuteSelfObject(DDObjectAgreement Agreement, DDParam* Param)
{
	// 定义存储目标对象的组
	TArray<IDDOO*> TargetObjectGroup;
	// 从数据模组获取对象组
	Model->GetSelfObject(Agreement.ObjectGroup, TargetObjectGroup);
	// 循环调用反射事件
	for (int i = 0; i < TargetObjectGroup.Num(); ++i) {
		// 获取反射方法
		UFunction* ExeFunc = TargetObjectGroup[i]->GetObjectBody()->FindFunction(Agreement.FunctionName);
		if (ExeFunc) {
			// 设置调用成功
			Param->CallResult = ECallResult::Succeed;
			// 执行方法
			TargetObjectGroup[i]->GetObjectBody()->ProcessEvent(ExeFunc, Param->ParamPtr);
		}
		else {
			// 设置找不到方法
			Param->CallResult = ECallResult::NoFunction;
		}
	}
	// 如果获取的对象有缺失,设置结果为对象缺失,这个结果的优先级最高
	if (TargetObjectGroup.Num() != Agreement.ObjectGroup.Num())
		Param->CallResult = ECallResult::LackObject;
}

void UDDModule::ExecuteOtherObject(DDObjectAgreement Agreement, DDParam* Param)
{
	// 定义存储目标对象的组
	TArray<IDDOO*> TargetObjectGroup;
	// 从数据组件获取对象组(注意 TotalObjectNum 接收的是已注册到 DDModel 的所有对象的数量)
	int32 TotalObjectNum = Model->GetOtherObject(Agreement.ObjectGroup, TargetObjectGroup);
	// 循环调用反射事件
	for (int i = 0; i < TargetObjectGroup.Num(); ++i) {
		// 获取反射方法
		UFunction* ExeFunc = TargetObjectGroup[i]->GetObjectBody()->FindFunction(Agreement.FunctionName);
		if (ExeFunc) {
			// 设置调用成功
			Param->CallResult = ECallResult::Succeed;
			// 执行方法
			TargetObjectGroup[i]->GetObjectBody()->ProcessEvent(ExeFunc, Param->ParamPtr);
		}
		else {
			// 设置找不到方法
			Param->CallResult = ECallResult::NoFunction;
		}
	}
	// 判断对象有没有缺失
	if (Agreement.ObjectGroup.Num() + TargetObjectGroup.Num() != TotalObjectNum)
		Param->CallResult = ECallResult::LackObject;
}

void UDDModule::ExecuteClassOtherObject(DDObjectAgreement Agreement, DDParam* Param)
{
	// 定义存储目标对象的组
	TArray<IDDOO*> TargetObjectGroup;
	// 从数据组件获取对象组
	int32 TotalClassNum = Model->GetClassOtherObject(Agreement.ObjectGroup, TargetObjectGroup);
	// 循环调用反射事件
	for (int i = 0; i < TargetObjectGroup.Num(); ++i) {
		// 获取反射方法
		UFunction* ExeFunc = TargetObjectGroup[i]->GetObjectBody()->FindFunction(Agreement.FunctionName);
		if (ExeFunc) {
			// 设置调用成功
			Param->CallResult = ECallResult::Succeed;
			// 执行方法
			TargetObjectGroup[i]->GetObjectBody()->ProcessEvent(ExeFunc, Param->ParamPtr);
		}
		else {
			// 设置找不到方法
			Param->CallResult = ECallResult::NoFunction;
		}
	}
	// 判断对象缺失
	if (Agreement.ObjectGroup.Num() + TargetObjectGroup.Num() != TotalClassNum)
		Param->CallResult = ECallResult::LackObject;
}

void UDDModule::ExecuteSelfClass(DDObjectAgreement Agreement, DDParam* Param)
{
	// 定义存储目标对象的组
	TArray<IDDOO*> TargetObjectGroup;
	// 从数据组件获取对象组
	Model->GetSelfClass(Agreement.ObjectGroup, TargetObjectGroup);
	// 循环调用反射事件
	for (int i = 0; i < TargetObjectGroup.Num(); ++i) {
		// 获取反射方法
		UFunction* ExeFunc = TargetObjectGroup[i]->GetObjectBody()->FindFunction(Agreement.FunctionName);
		if (ExeFunc) {
			// 设置调用成功
			Param->CallResult = ECallResult::Succeed;
			// 执行方法
			TargetObjectGroup[i]->GetObjectBody()->ProcessEvent(ExeFunc, Param->ParamPtr);
		}
		else {
			// 设置找不到方法
			Param->CallResult = ECallResult::NoFunction;
		}
	}
	// 判断对象缺失
	if (TargetObjectGroup.Num() == 0)
		Param->CallResult = ECallResult::LackObject;
}

void UDDModule::ExecuteOtherClass(DDObjectAgreement Agreement, DDParam* Param)
{
	// 定义存储目标对象的组
	TArray<IDDOO*> TargetObjectGroup;
	// 从数据组件获取对象组
	Model->GetOtherClass(Agreement.ObjectGroup, TargetObjectGroup);
	// 循环调用反射事件
	for (int i = 0; i < TargetObjectGroup.Num(); ++i) {
		// 获取反射方法
		UFunction* ExeFunc = TargetObjectGroup[i]->GetObjectBody()->FindFunction(Agreement.FunctionName);
		if (ExeFunc) {
			// 设置调用成功
			Param->CallResult = ECallResult::Succeed;
			// 执行方法
			TargetObjectGroup[i]->GetObjectBody()->ProcessEvent(ExeFunc, Param->ParamPtr);
		}
		else {
			// 设置找不到方法
			Param->CallResult = ECallResult::NoFunction;
		}
	}
	// 判断对象缺失
	if (TargetObjectGroup.Num() == 0)
		Param->CallResult = ECallResult::LackObject;
}

void UDDModule::ExecuteAll(DDObjectAgreement Agreement, DDParam* Param)
{
	// 定义存储目标对象的组
	TArray<IDDOO*> TargetObjectGroup;
	// 从数据组件获取对象组
	Model->GetAll(TargetObjectGroup);
	// 循环调用反射事件
	for (int i = 0; i < TargetObjectGroup.Num(); ++i) {
		// 获取反射方法
		UFunction* ExeFunc = TargetObjectGroup[i]->GetObjectBody()->FindFunction(Agreement.FunctionName);
		if (ExeFunc) {
			// 设置调用成功
			Param->CallResult = ECallResult::Succeed;
			// 执行方法
			TargetObjectGroup[i]->GetObjectBody()->ProcessEvent(ExeFunc, Param->ParamPtr);
		}
		else {
			// 设置找不到方法
			Param->CallResult = ECallResult::NoFunction;
		}
	}
}

如果要测试上面写的代码的话需要两个类,所以我们先做好一些准备工作,实际测试留到下一节课再继续讲解:

来到 LifeCallActor.cpp,把目前所有的 Debug 语句都注释掉,对 HappyFunc() 的调用也注释掉。

创建 C++ 的 DDActor 类,目标模块为 RaceCarFrame (Runtime),取名为 ReflectActor,直接默认路径创建。

24. 对象反射方法调用

这节课的目的是要测试一下之前写的对象反射调用系统。

在 Blueprint 文件夹创建 ReflectActor 的蓝图,命名为 ReflectActor_BP

给 LifeCallActor_BP 和 ReflectActor_BP 的细节面板设置成下图:

UE4运用C++和框架开发坦克大战教程笔记(八)(第23~25集)_第1张图片
由于我们打算让 ReflectActor 调用 LifeCallActor 下的方法,所以我们在 LifeCallActor 这里声明并定义一个方法供反射调用。

LifeCallActor.h

public:

	UFUNCTION()
	void AcceptCall(FString InfoStr);

LifeCallActor.cpp

void ALifeCallActor::AcceptCall(FString InfoStr)
{
	DDH::Debug() << GetObjectName() << " --> " << InfoStr << DDH::Endl();
}

来到 ReflectActor,重写 DDEnable() 以便在它里面调用反射执行方法。

我们先前在 DDDefine 里添加的宏是针对框架内对象调用模组方法的(使用通信协议的结构体为 DDModuleAgreement),但对象与对象之间用的通信协议结构体是 DDObjectAgreement,所以我们暂时先声明一套供测试使用的结构体和带返回值的方法。

ReflectActor.h

public:

	virtual void DDEnable() override;

protected:

	struct AcceptCallParam : DDParam
	{
		struct
		{
			FString InfoStr;
		} Parameter;
		FString InfoStr() { return Parameter.InfoStr; }
		AcceptCallParam() { ParamPtr = &Parameter; }
	};
	AcceptCallParam* AcceptCallRT(int32 ModuleIndex1, EAgreementType AgreementType, TArray<FName> ObjectGroup, FName FunctionName, FString InfoStr)
	{
		DDObjectAgreement Agreement;
		Agreement.ModuleIndex = ModuleIndex1;
		Agreement.AgreementType = AgreementType;
		Agreement.ObjectGroup = ObjectGroup;
		Agreement.FunctionName = FunctionName;
		AcceptCallParam* Param = new AcceptCallParam();
		Param->Parameter.InfoStr = InfoStr;
		ExecuteFunction(Agreement, Param);
		return Param;
	}

DDEnable() 内添加反射调用,并且输出反射调用的执行结果。

ReflectActor.cpp

void AReflectActor::DDEnable()
{
	Super::DDEnable();

	AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, EAgreementType::SelfObject, TArray<FName>{"LifeCallActor"}, "AcceptCall", "Happy Everyday");
	DDH::Debug() << DDH::GetEnumValueAsString<ECallResult>("ECallResult", Param->CallResult) << DDH::Endl();
	delete Param;
}

对象通过反射调用对象方法的调用过程如下:如果目标对象属于当前自己的所属模组,就直接让所属模组传递调用;否则就让 Driver 通过中央模组再到目标模组,调用目标对象的方法。

根据调用协议的不同,模组类需要传递执行不同的执行方法。

DDModule.h

public:

	// 调用对象方法
	void ExecuteFunction(DDObjectAgreement Agreement, DDParam* Param);

DDModule.cpp

void UDDModule::ExecuteFunction(DDObjectAgreement Agreement, DDParam* Param)
{
	// 区分类型执行放射方法
	switch (Agreement.AgreementType)
	{
		case EAgreementType::SelfObject:
			ExecuteSelfObject(Agreement, Param);
			break;
		case EAgreementType::OtherObject:
			ExecuteOtherObject(Agreement, Param);
			break;
		case EAgreementType::ClassOtherObject:
			ExecuteClassOtherObject(Agreement, Param);
			break;
		case EAgreementType::SelfClass:
			ExecuteSelfClass(Agreement, Param);
			break;
		case EAgreementType::OtherClass:
			ExecuteOtherClass(Agreement, Param);
			break;
		case EAgreementType::All:
			ExecuteAll(Agreement, Param);
			break;
	}
}

中央模组也添加一个目标为对象的反射传递执行方法,供 Driver 调用;先前的同名方法是针对模组使用的。

DDCenterModule.h

public:

	// 执行反射方法
	void AllotExecuteFunction(DDObjectAgreement Agreement, DDParam* Param);

DDCenterModule.cpp

void UDDCenterModule::AllotExecuteFunction(DDObjectAgreement Agreement, DDParam* Param)
{
	if (Agreement.ModuleIndex < ModuleGroup.Num() && ModuleGroup[Agreement.ModuleIndex])
		ModuleGroup[Agreement.ModuleIndex]->ExecuteFunction(Agreement, Param);
	else
		Param->CallResult = ECallResult::NoModule;
}

Driver 也添加一个传递调用方法。

DDDriver.h

public:

	// 执行反射方法
	void ExecuteFunction(DDObjectAgreement Agreement, DDParam* Param);

DDDriver.cpp

void ADDDriver::ExecuteFunction(DDObjectAgreement Agreement, DDParam* Param)
{
	Center->AllotExecuteFunction(Agreement, Param);
}

最后给 DDOO 添加一个目标为对象的反射执行方法。

DDOO.h

protected:

	// 执行反射方法
	void ExecuteFunction(DDObjectAgreement Agreement, DDParam* Param);

DDOO.cpp

void IDDOO::ExecuteFunction(DDObjectAgreement Agreement, DDParam* Param)
{
	if (Agreement.ModuleIndex == ModuleIndex)
		IModule->ExecuteFunction(Agreement, Param);
	else
		IDriver->ExecuteFunction(Agreement, Param);
}

编译后,将 ReflectActor_BP 放入场景。运行游戏,可以看见左上角输出 Debug 语句,说明 ReflectActor_BP 通过反射调用 LifeCallActor_BP 成功了。

互相调用

测试所有的对象间反射调用的情况

添加一个跟原来方法一样的 AcceptCallRT()。区别在于,将原来形参中的 FName 数组改成单个 FName 变量。
再添加一个也是传单个 FName 变量的 AcceptCallRT(),不过不传入对象调用协议,而是直接写在方法里。

ReflectActor.h

protected:

	// 只传单个名字
	AcceptCallParam* AcceptCallRT(int32 ModuleIndex1, EAgreementType AgreementType, FName ObjName, FName FunctionName, FString InfoStr)
	{
		// ... 省略
		Agreement.ObjectGroup.Push(ObjName);
		// ... 省略
	}

	// 只传单个名字并且只对这个名字所指向的单个对象执行反射调用
	AcceptCallParam* AcceptCallRT(int32 ModuleIndex1, FName ObjName, FName FunctionName, FString InfoStr)
	{
		// ... 省略
		Agreement.AgreementType = EAgreementType::SelfObject;
		Agreement.ObjectGroup.Push(ObjName);
		// ... 省略
	}

接下来分别测试这两个新的执行反射调用方法。

ReflectActor.cpp

void AReflectActor::DDEnable()
{
	
	// 将原来的反射调用语句改成这句
	// 测试 1
	AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, EAgreementType::SelfObject, "LifeCallActor", "AcceptCall", "Happy Everyday");
	// 测试 2,注意两次测试分别进行,试完上面的再试下面被注释的这一句
	//AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, "LifeCallActor", "AcceptCall", "Happy Everyday");
	
}

两段代码分别编译后运行游戏,左上角两次的输出结果跟上次都是一样的。

接下来我们尝试下:指定模组下,通过类名指定反射调用的对象。

ReflectActor.cpp

void AReflectActor::DDEnable()
{
	
	// 测试 3
	AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, EAgreementType::SelfClass, "LifeCallClass", "AcceptCall", "Happy Everyday");
	
}

把场景里的 LifeCallActor_BP 删除,打开它的蓝图,设置细节面板内容如下:

UE4运用C++和框架开发坦克大战教程笔记(八)(第23~25集)_第2张图片
然后往场景里拖入 3 个 LifeCallActor_BP 对象。运行游戏,左上角会显示 LackObject。这是因为 DDOO 的 GetClassName() 出了问题。(此处老师会教如何 Debug 时添加监视来观察数据)

DDOO.cpp

void IDDOO::RegisterToModule(FName ModName, FName ObjName, FName ClsName)
{
	
	// 此处更改
	if (!ClsName.IsNone()) IClassName = ClsName;
	
}

void IDDOO::RegisterToModule(int32 ModIndex, FName ObjName, FName ClsName)
{

	// 此处更改
	if (!ClsName.IsNone()) IClassName = ClsName;
	
}

FName IDDOO::GetClassName()
{
	if (!IClassName.IsNone()) return IClassName;
	// 此处更改
	IClassName = IBody->GetClass()->GetFName();
	return IClassName;
}

编译后运行游戏,左上角输出 Debug 语句如下(Object 名字会有差异 ),说明通过指定类名执行反射调用成功。

UE4运用C++和框架开发坦克大战教程笔记(八)(第23~25集)_第3张图片
接下来尝试一下:指定模组下,执行指定对象名以外的同类对象的方法。

ReflectActor.cpp

void AReflectActor::DDEnable()
{
	
	// 测试 4
	AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, EAgreementType::ClassOtherObject, "LifeCallActor", "AcceptCall", "Happy Everyday");
	
}

编译后将关卡内任意一个 LifeCallActor_BP 对象的 ObjectName 变量的值改成 LifeCallActor。

运行游戏,此时左上角输出的是另外两个 LifeCallActor_BP 对象的 Debug 语句。(笔者这里对应的是 BP2 和 BP3)

接下来我们测试下:指定模组下,对指定名字以外的对象的反射调用。

给 ReflectActor 添加一个跟 LifeCallActor 的一样的,供反射调用的方法。

ReflectActor.h

public:

	UFUNCTION()
	void AcceptCall(FString InfoStr);

ReflectActor.cpp

void AReflectActor::DDEnable()
{
	
	// 测试 5
	AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, EAgreementType::OtherObject, "LifeCallActor", "AcceptCall", "Happy Everyday");
	
}

void AReflectActor::AcceptCall(FString InfoStr)
{
	DDH::Debug() << GetObjectName() << " --> " << InfoStr << DDH::Endl();
}

编译后,打开 ReflectActor_BP 的蓝图,将它的 ModuleName 改成 Center,此时与 LifeCallActor_BP 的 ModuleName 相同。

运行游戏,左上角 Debug 信息如下,说明调用成功。

UE4运用C++和框架开发坦克大战教程笔记(八)(第23~25集)_第4张图片
接下来我们尝试一下:指定模组下,通过反射调用指定类名以外的对象的方法。

ReflectActor.cpp

void AReflectActor::DDEnable()
{
	
	// 测试 6
	AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, EAgreementType::OtherClass, "LifeCallClass", "AcceptCall", "Happy Everyday");
	
}

编译后运行游戏,左上角的 Debug 信息只有 ReflectActor 的(因为此时其类名为引擎自动生成的类名)。

最后我们来尝试一下:指定模组下,调用所有对象的反射方法。

ReflectActor.cpp

void AReflectActor::DDEnable()
{
	
	// 测试 7
	AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, EAgreementType::All, "LifeCallClass", "AcceptCall", "Happy Everyday");

	// 测试完毕后改回这个,前面的测试 1 ~ 7 都可以删掉了
	//AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, EAgreementType::SelfObject, "LifeCallActor", "AcceptCall", "Happy Everyday");
}

编译后运行游戏,左上角显示了 4 个对象的 Debug 信息,说明调用成功。

25. 对象反射系统宏定义

接下来补充一下不带返回值的反射调用方法,这里就不测试了。

ReflectActor.h

protected:

	// 跟前面的三个方法区别在于无返回值、方法名去掉 RT 以及最后要 delete
	void AcceptCall(int32 ModuleIndex1, EAgreementType AgreementType, TArray<FName> ObjectGroup, FName FunctionName, FString InfoStr)
	{
		// ... 省略
		delete Param;
	}
	void AcceptCall(int32 ModuleIndex1, EAgreementType AgreementType, FName ObjName, FName FunctionName, FString InfoStr)
	{
		// ... 省略
		delete Param;
	}
	void AcceptCall(int32 ModuleIndex1, FName ObjName, FName FunctionName, FString InfoStr)
	{
		// ... 省略
		delete Param;
	}

补充方法以后,就可以直接使用反射调用方法而不必考虑保存返回值以及销毁返回值的指针了。

并且目前 输出反射执行结果 的过程太繁琐了,所以我们在 DDCommon 将其封装成一个方法。

DDCommon.h

namespace DDH
{

	template<typename TEnum>
	FORCEINLINE FString GetEnumValueAsString(const FString& Name, TEnum Value)
	{
	
		// 这里也改一下
		return EnumPtr->GetNameStringByIndex((int32)Value);
	}


	template<typename TEnum>
	FORCEINLINE FName GetEnumValueAsName(const FString& Name, TEnum Value)
	{
	
		// 这里也改一下
		return FName(*EnumPtr->GetNameStringByIndex((int32)Value));
	}

	
	// 获取反射调用结果
	FORCEINLINE FString GetCallResult(ECallResult Value)
	{
		const UEnum* EnumPtr = FindObject<UEnum>((UObject*)ANY_PACKAGE, *FString("ECallResult"), true);
		// GetEnumName() 在 4.26 已经准备废弃
		//return EnumPtr->GetEnumName((int32)Value);
		return EnumPtr->GetNameStringByIndex((int32)Value);
	}
}

随后来到 ReflectActor.cpp 来调用封装好的输出结果方法。

ReflectActor.cpp

void AReflectActor::DDEnable()
{


	// 输出结果的逻辑更改成如下
	DDH::Debug() << DDH::GetCallResult(Param->CallResult) << DDH::Endl();
	delete Param;
}

接下来我们将 ReflectActor.h 里的反射执行调用方法转化成宏定义。为了节约篇幅我们只写出两个方法的宏定义,建议读者是直接复制老师的 Github 项目代码 的 DDDefine.h 里面 550 ~ 1822 行部分

DDDefine.h


// 建议读者直接复制粘贴老师的 github 项目代码
#define DDOBJFUNC_ONE(FuncName, ParamType1, ParamName1); \
	struct FuncName##Param : DDParam \
	{ \
		struct \
		{ \
			ParamType1 ParamName1; \
		} Parameter; \
		ParamType1 ParamName1() { return Parameter.ParamName1; } \
		FuncName##Param() { ParamPtr = &Parameter; } \
	}; \
	FuncName##Param* AcceptCallRT(int32 ModuleIndex1, EAgreementType AgreementType, TArray<FName> ObjectGroup, FName FunctionName, ParamType1 ParamName1) \
	{ \
		DDObjectAgreement Agreement; \
		Agreement.ModuleIndex = ModuleIndex1; \
		Agreement.AgreementType = AgreementType; \
		Agreement.ObjectGroup = ObjectGroup; \
		Agreement.FunctionName = FunctionName; \
		AcceptCallParam* Param = new AcceptCallParam(); \
		Param->Parameter.ParamName1 = ParamName1; \
		ExecuteFunction(Agreement, Param); \
		return Param; \
	} \
	FuncName##Param* AcceptCallRT(int32 ModuleIndex1, EAgreementType AgreementType, FName ObjName, FName FunctionName, ParamType1 ParamName1) \
	{ \
		DDObjectAgreement Agreement; \
		Agreement.ModuleIndex = ModuleIndex1; \
		Agreement.AgreementType = AgreementType; \
		Agreement.ObjectGroup.Push(ObjName); \
		Agreement.FunctionName = FunctionName; \
		AcceptCallParam* Param = new AcceptCallParam(); \
		Param->Parameter.ParamName1 = ParamName1; \
		ExecuteFunction(Agreement, Param); \
		return Param; \
	} 

回到 ReflectActor.h,我们将之前声明的结构体和反射调用方法都注释掉,换成我们的宏定义。

ReflectActor.h

protected:

	DDOBJFUNC_ONE(AcceptCall, FString, InfoStr);

	// 删除或注释掉 protected 下所有内容

编译后运行游戏,可见左上角输出成功。

转换为宏定义后的输出结果

值得注意的是,如果 .cpp 里调用的是带返回值的方法,记得一定要手动释放 Param 所占的内存。

模块(DDMM)通过反射调用对象方法

前面我们说过,继承 DDMM 接口的有 DDWealth、DDModel 和 DDMessage,接下来我们让它们也可以通过反射来调用对象的方法。

在 DDMM 也添加两个跟 DDOO 一样的,传递执行反射调用的方法;并且保存一个模组序号的变量。

DDMM.h

class DATADRIVEN_API IDDMM
{


protected:

	// 执行反射方法
	void ExecuteFunction(DDModuleAgreement Agreement, DDParam* Param);

	// 执行反射方法
	void ExecuteFunction(DDObjectAgreement Agreement, DDParam* Param);

protected:

	// 对应模组的序号
	int32 ModuleIndex;
};

DDMM.cpp

// 引入头文件
#include "DDCore/DDModule.h"
#include "DDCore/DDDriver.h"

void IDDMM::AssignModule(UDDModule* Mod)
{
	IModule = Mod;
	ModuleIndex = IModule->ModuleIndex;		// 初始化模组序号
	IDriver = UDDCommon::Get()->GetDriver();
}

void IDDMM::ExecuteFunction(DDModuleAgreement Agreement, DDParam* Param)
{
	if (Agreement.ModuleIndex == ModuleIndex)
		IModule->ExecuteFunction(Agreement, Param);
	else
		IDriver->ExecuteFunction(Agreement, Param);
}

void IDDMM::ExecuteFunction(DDObjectAgreement Agreement, DDParam* Param)
{
	if (Agreement.ModuleIndex == ModuleIndex)
		IModule->ExecuteFunction(Agreement, Param);
	else
		IDriver->ExecuteFunction(Agreement, Param);
}

来到 ReflectActor,声明一个方法供模块反射调用。

ReflectActor.h

public:

	UFUNCTION()
	void WealthCall(int32 Counter, FString InfoStr, bool InFlag);

ReflectActor.cpp

void AReflectActor::DDEnable()
{
	Super::DDEnable();

	/*
	AcceptCallParam* Param = AcceptCallRT((int32)ERCGameModule::Center, EAgreementType::SelfObject, "LifeCallActor", "AcceptCall", "Happy Everyday");
	DDH::Debug() << DDH::GetCallResult(Param->CallResult) << DDH::Endl();
	delete Param;
	*/
}


void AReflectActor::WealthCall(int32 Counter, FString InfoStr, bool InFlag)
{
	DDH::Debug() << Counter << " --> " << InfoStr << " --> " << InFlag << DDH::Endl();
}

我们打算在 DDWealth 这个模块里调用,给其添加一个宏。

并且为了确保这个模块已经注册到框架了,我们添加一个计时器变量,打算在第 5 帧执行反射调用。

DDWealth.h

// 以下都是临时代码
protected:

	int32 TimeCounter;

protected:

	DDOBJFUNC_THREE(TestReflect, int32, Counter, FString, InfoStr, bool, InFlag);

DDWealth.cpp

void UDDWealth::WealthTick(float DeltaSeconds)
{
	TimeCounter++;
	if (TimeCounter == 5) {
		TestReflect((int32)ERCGameModule::Center, "ReflectActor", "WealthCall", 32, "NoNoNo", true);
	}
}

按照框架的结构来看,每个模组都会有上面说到的三个继承了 DDMM 的模块,而目前我们在 GameDriver_BP 里有 Center、HUD、Player 这 3 个模组,所以这个反射调用会执行 3 次。

编译后运行游戏,可以看见左上角输出如下,说明模块通过反射调用对象的方法成功了。

模块反射调用对象方法
测试完毕后将 DDWealth 的临时代码相关部分都删除掉。

你可能感兴趣的:(UE4/5,的学习笔记,ue4,c++,笔记)