【UE4全反射松耦合框架笔记】第二章 框架结构搭建

4 篇文章 1 订阅

第二章 框架结构搭建

一、框架介绍

框架目录:
完成的框架插件目录

二、仿QT打印功能

涉及的文件及其说明:

  • DDTypes
    我们会在DDTypes中定义打印对象及成员操作。
  • DDDefine
    DDDefine会包含框架的主要的头文件,这里将包含DDTypes头文件。然后在DDCommon中包含DDDefine的头文件,也就间接使用了DDTypes的头文件。
  • DDCommon
    在DDCommon中接种DDTypes中声明的对象定义打印方法,这样就能更方便的使用打印对象。

代码示例:
DDTypes:


//#pragma region XXX是VS中的宏。使用这个宏后,代码编辑框左边会有+ -的符号,可以把代码块收展起来。
#pragma region LogDebug

class DATADRIVER_API DDRecord
{
private:
	//使用单例模式
	static TSharedPtr<DDRecord> RecordInst = nullptr;
	//调试信息
	FString RecordInfo;
	//显示时间
	float ShowTime;
	//显示颜色
	FColor ShowColor;
public:
	//打印模式:0-Debug,1-Log,2-Warning,3-Error
	uint8 PatternID;

public:
	inline DDRecord() {}

	~DDRecord() {}
	//这里定义的Get是为后面DDH的命名空间使用,而不是在使用打印时以Get为开始
	static TSharedPtr<DDRecord> Get()
	{
		if (!RecordInst.IsValid) {
			RecordInst = MakeShareable(new DDRecord());
		}
		return RecordInst;
	}

	//设置颜色及显示时间参数
	inline void InitParam(float InTime, FColor InColor)
	{
		ShowTime = InTime;
		ShowColor = InColor;
	}
	//执行打印方法
	inline void Output()
	{
		switch (PatternID)
		{
		case 0:
			if (GEngine)
				GEngine->AddOnScreenDebugMessage(-1, ShowTime, ShowColor, RecordInfo);
			break;
		case 1:
			UE_LOG(LogTemp, Log, TEXT("%s"), *RecordInfo);
			break;
		case 2:
			UE_LOG(LogTemp, Warning, TEXT("%s"), *RecordInfo);
			break;
		case 3:
			UE_LOG(LogTemp, Error, TEXT("%s"), *RecordInfo);
			break;
		}
		//清空字符串
		RecordInfo.Empty();
	}

	//重写操作符
	inline DDRecord& operator<<(FString Info){ RecordInfo.Append(Info);	return *this; }
	inline DDRecord& operator<<(FName Info) { RecordInfo.Append(Info.ToString());	return *this; }
	inline DDRecord& operator<<(FText Info) { RecordInfo.Append(Info.ToString());	return *this; }
	inline DDRecord& operator<<(const char* Info) { RecordInfo+=Info;	return *this; }
	inline DDRecord& operator<<(const char Info) { RecordInfo.AppendChar(Info);	return *this; }
	inline DDRecord& operator<<(int32 Info) { RecordInfo.Append(FString::FromInt(Info));	return *this; }
	inline DDRecord& operator<<(float Info) { RecordInfo.Append(FString::SanitizeFloat(Info));	return *this; }
	inline DDRecord& operator<<(double Info) { RecordInfo.Append(FString::SanitizeFloat(Info));	return *this; }
	inline DDRecord& operator<<(bool Info) { RecordInfo.Append(Info?"true":"false");	return *this; }
	inline DDRecord& operator<<(FVector2D Info) { RecordInfo.Append(Info.ToString());	return *this; }
	inline DDRecord& operator<<(FVector Info) { RecordInfo.Append(Info.ToString());	return *this; }
	inline DDRecord& operator<<(FRotator Info) { RecordInfo.Append(Info.ToString());	return *this; }
	inline DDRecord& operator<<(FQuat Info) { RecordInfo.Append(Info.ToString());	return *this; }
	inline DDRecord& operator<<(FTransform Info) { RecordInfo.Append(Info.ToString());	return *this; }
	inline DDRecord& operator<<(FMatrix Info) { RecordInfo.Append(Info.ToString());	return *this; }
	inline DDRecord& operator<<(FColor Info) { RecordInfo.Append(Info.ToString());	return *this; }
	inline DDRecord& operator<<(FLinearColor Info) { RecordInfo.Append(Info.ToString());	return *this; }
	//当最后一个输入是DDRecord对象时,输出结果
	inline void operator<<(DDRecord& Record) { Record.Output(); }
};
#pragma endregion

DDCommon:
使用打印命令时,我们使用的是DDH命名空间提供的方法作为开头和结束。

namespace DDH
{
	//Debug是打印的开始,可以设置打印的参数
	FORCEINLINE DDRecord& Debug(float InTime = 100.f, FColor InColor = FColor::Yellow)
	{
		DDRecord::Get()->PatternID = 0;
		DDRecord::Get()->InitParam(InTime, InColor);
		return *DDRecord::Get();
	}

	FORCEINLINE DDRecord& Debug(FColor InColor)
	{
		DDRecord::Get()->PatternID = 0;
		DDRecord::Get()->InitParam(100.f, InColor);
		return *DDRecord::Get();
	}
	//输出日志
	FORCEINLINE DDRecord& Log()
	{
		DDRecord::Get()->PatternID = 1;
		return *DDRecord::Get();
	}
	//输出警告
	FORCEINLINE DDRecord& Warning()
	{
		DDRecord::Get()->PatternID = 2;
		return *DDRecord::Get();
	}
	//输出错误
	FORCEINLINE DDRecord& Error()
	{
		DDRecord::Get()->PatternID = 3;
		return *DDRecord::Get();
	}
	
	//结束输入,输出打印结果
	FORCEINLINE DDRecord& Endl()
	{
		return *DDRecord::Get();
	}
}

打印函数调用:

DDP::Debug() << "Hello, World!" << 2020 << "." <<DDP::Endl();
DDP::Debug(FColor::Red) << "Hello, World!" << 2020 << "." <<DDP::Endl();

三、模组生命周期

下图是框架的各个模块及组件的初始化及运行流程:
各组件初始化及调用流程
首先,Driver初始化并构造CenterModule。再次期间,各个子组件(Module)也将被初始化。在PostInitializeComponents函数执行时,Driver除了将自身注册到Common外,还会迭代执行CenterModule子组件的Manager,而Manager负责创建Module的Model、Message、Wealth三个部分。另外各个子模块的Init、BeginPlay及Tick也将依次执行,这里通过各层级的三个函数依次调用实现。

1.Driver执行BeginPlay
2.迭代执行各Module的Init
3.先执行CenterModule的Init函数
4.然后通过遍历子组件及迭代执行各子Module的Init函数

Common
我们把Common写成单例模式,并且将Driver对象的引用保存到Common。这样,我们就可以随时方便的获取到Driver了。当然Driver也会包含子组件的引用等,所以通过这样逻辑包含关系就可以通过Common和Driver方便地获取到框架的大部分核心组件。

UCLASS()
class DATADRIVER_API UDDCommon : public UObject
{
	GENERATED_BODY()
public:
	static UDDCommon* Get()
	{
		if(!CommonInst)
		{
			CommonInst = NewObject<UDDCommon>();
			CommonInst->AddToRoot();		//添加到根,防止被GC
		}
		return CommonInst;
	}
	void InitDriver(ADDDriver* Driver){DriverInst = Driver;}
	ADDDriver* GetDriver(){return DriverInst;}
private:
	static UDDCommon* CommonInst = nullptr;
	static ADDDriver* DriverInst;
};

Driver
主要是初始化各模块组件,并将子组件的方法函数放入到Driver的PostInitializeComponents、BeginPlay、Tick等函数,借这些函数将子模块或组件驱动起来。Driver通过CenterModule的迭代函数,不断执行CenterModule下的子Module组件成员函数来带动执行。

UCLASS()
class DATADRIVER_API ADDDriver : public AActor
{
	GENERATED_BODY()
public:	
	ADDDriver();		//构造器将创建根组件和CenterModule,CenterModule附着在根组件上
	virtual void PostInitializeComponents() override
	{
		Super::PostInitializeComponents();
		UDDCommon::Get()->InitDriver(this);		//注册到Driver
		CenterModule->IterChangeModuleType(CenterModule, ModuleType);
		CenterModule->IterCreateManager(CenterModule);
	}
	virtual void Tick(float DeltaTime) override;	//像其它函数一样,这里迭代BeginPlay和Tick
protected:
	virtual void BeginPlay() override
	{
		Super::BeginPlay();
		CenterModule->IterModuleInit(CenterModule);
	}
public:	
	//NoClear-防止该对象引用在编辑器中被设置为None.隐藏编辑器的清除(以及浏览)按钮。
	UPROPERTY(EditAnywhere, NoClear, BlueprintReadOnly, Category = "DataDriver")
	USceneComponent* RootModule;
	UPROPERTY(EditAnywhere, NoClear, BlueprintReadOnly, Category = "DataDriver")
	UDDCenterModule* CenterModule;
	UPROPERTY(EditDefaultsOnly, Category = "DataDriver")
	FName ModuleType;
protected:
	bool IsBeginPlay;		//将IterModuleBeginPlay放到Tick的第一帧执行,此变量保存是否执行。
};

IDDMM
IDDM是Model、Message、Wealth的抽象父类,保存了Model、Message、Wealth的父对象引用(Module和Driver),注意这里的父对象指的是父组件而非父类。Module通过调用AssignModule来将其自身引用保存到子组件对象上。

class DATADRIVER_API IDDMM
{
	GENERATED_BODY()
public:
	void AssignModule(UDDModule* Module)
	{
		ParentModule = Module;
		Driver = UDDCommon::Get()->GetDriver();
	}
protected:
	ADDDriver* Driver;
	UDDModule* ParentModule;
};

Module
Module是Driver的子组件,每个Module负责一类事务处理,例如HUD或Player等。其包含三个子组件Model、Message、Wealth,说是三个子组件并不完全对,因为这三个类集成UObject而非USceneComponent。所以,为了更方便地获取与调用这三个部分,所以将保存他们的引用。
Module也将重新声明新的Init、BeginPlay及Tick函数,这三个函数驱动Model、Message、Wealth的三个函数执行。
Module通过CreateManager函数实例化三个组件对象,并将其注册到GC的根,并注册自身引用到他们的父对象(ParentModule)上。

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DATADRIVER_API UDDModule : public USceneComponent
{
	GENERATED_BODY()
public:	
	UDDModule();
	void CreateManager()
	{	//创建Model、Message、Wealth子对象
		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);
	}
	virtual void ModuleInit()
	{	//下边三个方法同此方法
		Model->ModelInit();
		Message->MessageInit();
		Wealth->WealthInit();
	}
	virtual void ModuleBeginPlay();
	virtual void ModuleTick(float DeltaSeconds);
public:
	TArray<UDDModule*> ChildrenModule;
protected:
	UDDModel* Model;
	UDDMessage* Message;
	UDDWealth* Wealth;
};

CenterModule
CenterModule是中央模组,下图为CenterModule及其他Module的层级关系
模组层级关系
通过上图,我们也可以看出,其它模组位于CenterModule下。CenterModule是一个核心模组,包含其子模组引用及模组的迭代函数,Driver通过调用核心模组的迭代函数驱动所有模组的运行。

UCLASS()
class DATADRIVER_API UDDCenterModule : public UDDModule
{
	GENERATED_BODY()
public:
	void IterChangeModuleType(UDDModule* Module, FName ModuleType) {
		for (int i = 0; i < Module->GetAttachChildren().Num(); ++i) {	//获取并保存附着在Module下的子Module
			UDDModule* ChildModule = Cast<UDDModule>(Module->GetAttachChildren()[i]);
			if (ChildModule) {
				Module->ChildrenModule.Push(ChildModule);
				IterChangeModuleType(ChildModule, ModuleType);
			}
		}
	}
	void IterCreateManager(UDDModule* Module){	//迭代执行子模块,下同
		Module->CreateManager();
		for (int i = 0; i < Module->ChildrenModule.Num(); ++i)
			IterCreateManager(Module->ChildrenModule[i]);
	}
	void IterModuleInit(UDDModule* Module);
	void IterModuleBeginPlay(UDDModule* Module);
	void IterModuleTick(UDDModule* Module, float DeltaSeconds);
};

Model、Message、Wealth
这三个块是Module的三个子组件,分别负责数据、事件和资源三个内容。这节声明三个组件的Init、BeginPlay及Tick函数,并且不再适用通过继承UObject而来的相似函数。目的是为了统一他们的调用顺序。

UCLASS()
class DATADRIVER_API UDDModel : public UObject, public IDDMM
{
	GENERATED_BODY()
public:
	virtual void ModelInit(){}
	virtual void ModelBeginPlay(){}
	virtual void ModelTick(float DeltaSeconds){}
};

四、枚举匹配模组结构

枚举与Module一一对应
枚举值与字符串的互转
我们将写几个枚举类的辅助函数,实现枚举值和字符串的互转。这样,我们可以调用辅助函数方便地通过枚举值获取枚举类的字符串形式或者通过字符串获取到枚举类的值了。

	//通过枚举值获取其对应的字符串对象
	template<typename TEnum>
	FORCEINLINE FString GetEnumValueAsString(const FString& EnumName, TEnum EnumValue)
	{
		const UEnum* Enum = FindObject<UEnum>(ANY_PACKAGE, *EnumName, true);
		if (!Enum)
			return FString("InValid");
		return Enum->GetEnumName((int32)EnumValue);
	}
	//通过枚举值获取FName类型的字符串
	template<typename TEnum>
	FORCEINLINE FName GetEnumValueAsName(const FString& EnumName, TEnum EnumValue)
	{
		const UEnum* Enum = FindObject<UEnum>(ANY_PACKAGE, *EnumName, true);
		if (!Enum)
			return FName("InValid");
		return FName(Enum->GetEnumName((int32)EnumValue));
	}
	//通过FName类型的字符串获取枚举值
	template<typename TEnum>
	FORCEINLINE TEnum GetEnumValueFromName(const FString& EnumName, FName ValueName)
	{
		const UEnum* Enum = FindObject<UEnum>(ANY_PACKAGE, *EnumName, true);
		if (!Enum)
			return TEnum(0);
		return (TEnum)Enum->GetIndexByName(ValueName);
	}

	//通过FName类型的字符串获取枚举值索引
	FORCEINLINE int32 GetEnumIndexFromName(const FString& EnumName, FName ValueName)
	{
		const UEnum* Enum = FindObject<UEnum>((UObject*)ANY_PACKAGE, *EnumName, true);
		if (!Enum)
			return -1;
		return Enum->GetIndexByName(ValueName);
	}

在Module中获取并保存ModuleType的索引
当Driver的ModuleType修改后,使用PostEditChangeProperty函数迭代修改ModuleType的索引
在CenterModule中迭代获取所有Module,并按照数组索引值等于枚举值保存Module的指针。

五、注册对象到模组

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UE4中的Gameplay框架是一个强大的工具集,用于开发和实现游戏玩法和用户交互。该框架提供了许多功能和组件,以帮助游戏开发者快速构建出丰富、流畅的游戏体验。 UE4的Gameplay框架主要由以下几个方面组成: 1.输入系统:该系统可以处理各种输入设备的操作,如鼠标、键盘和游戏手柄。开发者可以轻松地设置和管理输入映射和按键绑定。此外,还提供了鼠标和手柄的即时响应和移动方向控制功能,使玩家能够自由操作游戏中的角色。 2.角色控制器:角色控制器是游戏玩家在游戏中扮演的角色,他们的控制是通过输入系统和蓝图来实现的。游戏玩家可以移动角色、执行动作、攻击敌人等。角色还可以通过动画系统实现自然的运动和交互。 3.人工智能:UE4的Gameplay框架提供了内置的人工智能系统,可以对NPC和敌人进行编程控制。开发者可以设置敌人的行为模式、路径寻找和攻击策略,让游戏中的敌人具有更真实和智能的表现。 4.物理模拟:UE4的Gameplay框架使用了物理引擎来实现真实的物理模拟效果,比如碰撞、重力和刚体运动等。这使开发者能够创建更真实和具有交互性的游戏世界,使玩家可以与环境进行互动。 总之,UE4的Gameplay框架提供了强大而灵活的工具,帮助开发者轻松地构建出丰富多样的游戏玩法和用户交互。无论是开发动作冒险游戏、射击游戏还是角色扮演游戏,该框架都能满足开发者的需求,并带来令人惊叹的游戏体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值