【UE4全反射松耦合框架笔记】第一章 UE4框架基础(上)

第一章 UE4框架基础(上)

文章目录

  • 第一章 UE4框架基础(上)
    • 一、蓝图与C++交互
    • 二、UMG与C++交互
    • 三、生命周期探索
    • 四、全局类与接口
      • 全局类
      • 接口
        • 情况一:默认接口类中的函数是必须要允许在蓝图中实现
        • 情况二:函数不需要在蓝图中实现
        • 虚函数的调用
    • 五、委托与函数传递
      • 委托
      • 函数传递
        • TFunction
        • TMemFunPtrType
        • FMethodPtr

一、蓝图与C++交互

在蓝图中创建变量时,有四个选项可供选择:
创建变量
变量类型与C++分别对应的是:

AActor*					//对象引用

TSubclassOf		//类类型

TSoftObjectPtr	//对象软引用

TSoftClassPtr	//类类型软引用

TAssetPtr		//资源引用,对FStringAssetReference的封装,包含对象路径和对象弱指针
FStringAssetReference	//对象路径的引用,不用区分类型

方法与蓝图交互:

BlueprintCallable				蓝图可调用且由C++定义
BlueprintImplementableEvent		C++声明,由蓝图实现
BlueprintNativeEvent			C++实现,蓝图可覆盖,C++实现的方法需要加后缀_Implementation,但实现的方法不需要声明
Exec							控制台可调用
meta							可以限定对象属性

例如:

//限定对象属性
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(MetaClass="Actor", ToolTip="Bool Type"))
FStringAssetReference AssetRef;

UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(ClampMin=10, ClampMax=100), Category="FrameWork")
int32 CInt;

//BlueprintNativeEvent方法声明
UFUNCTION(BlueprintNativeEvent)
void NativeFunc();
//BlueprintNativeEvent方法实现
void AMyActor::NativeFunc_Implementation()
{...}

//控制台命令方法
UFUNCTION(Exec)
void ConsoleFunc(const FString& info);
/*Exec标记的函数,只有在主要的几个类中(GameInstance,GameMode等)才会执行。
我们可以在GameInstance中重写ProcessConsoleExec方法,使其可以被执行到。*/
virtual bool ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor) override;
bool UMyGameInstance::ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor)
{
	bool Res = Super::ProcessConsoleExec(Cmd, Ar, Executor);
	if(!Res)	//命令没有执行成功(没有找到相关命令需要执行)
	{
		for(TActorIterator<AActor> It(GetWorld()); It; ++It)	//遍历Actor类
		{
			Res = It->ProcessConsoleExec(Cmd, Ar, Executor);	//逐个执行
			if(Res)	break;					//如果执行成功,就退出循环并结束
		}
	}
	return Res;
}

对象获取方法:

  • 方法一:迭代器获取
for(TActorIterator<AActor> It(GetWorld()); It; ++It){...}
  • 方法二:静态方法获取
TArray<AActor*> Actors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACharacter::StaticClass(), Actors);

属性修改事件:

//编辑器模式修改变量(属性)时,将会触发此方法
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif

#if WITH_EDITOR
void AMyCharacter::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
	Super::PostEditChangeProperty(PropertyChangedEvent);
	//当对象被修改的属性名等于AMyCharacter类(一般为当前对象的类名)的Param变量(属性名)时,执行此段代码
	if(PropertyChangedEvent.Property&&
		PropertyChangedEvent.Property->GetName() == GET_MEMBER_NAME_CHECKED(AMyCharacter, Param)){...}
}
#endif

开发时,注意的几个点

  1. 声明的函数,如果不是BlueprintImplementationEvent函数,请一定要定义,否则可能会编译失败
  2. 引用头文件时,如果是此类的声明头文件,可以直接使用#include “XXX.h”,不用加路径,而其它自己创建的头文件,要注意加上路径,但路径中不用加Public。这个主要跟模块设置有关。
  3. 因IDE的关系,编译报错的点不一定就是真正的问题所在,但可以从报错的文件中查找错误

二、UMG与C++交互

文件目录:

Game
Common
Gameplay
UI
HUD
Widget

在PlayerController中设置输入模式:

//输入模式
//输入模式有三种类型:FInputModeGameAndUI,FInputModeGameOnly,FInputModeUIOnly
FInputModeGameAndUI InputMode;
InputMode.SetLockMouseToViewBehavior(EMouseLockMode::DoNotLock);
InputMode.SetHideCursorDuringCapture(false)
SetInputMode(InputMode);

//显示鼠标
bShowMouseCursor = true;

创建Widget:

//方法1:
TSubclassOf<UMyWidget> MyWidgetClass;			//可以在蓝图中选择UMyWidget及继承自UMyWidget的子类
UMyWidget* MyWidget = CreateWidget<UMyWidget>(GetWorld(), MyWidgetClass);
MyWidget->AddToViewport();		//添加到界面

//方法2:在Widget内部,通过WidgetTree创建。可用于创建子组件
UImage* DynImage = WidgetTree->ConstructWidget<UImage>(UImage::StaticClass());

从蓝图获取组件:

//方法一:从根节点获取
UCanvasPanel* RootPanel = Cast<UCanvasPanel>(GetRootWidget());
if(RootPanel)
{
	UImage* BGImage = Cast<UImage>(RootPanel->GetChildAt(0));
}

//方法二:GetWidgetFromName
UButton* ButtonTwo = (UButton*)GetWidgetFromName(TEXT("ButtonTwo"));	//通过方法获取到UMG中变量名为ButtonTwo的控件

//方法三:反射绑定
UPROPERTY(meta=(BindWidget))
UButton* ButtonOne;		//变量名应与UMG中控件名一致

给控件绑定函数(事件):

//方法一:__Internal_AddDynamic
Button->OnClick.__Internal_AddDynamic(this, &UMyWidget::ButtonEvent, FName("ButtonEvent"));

//方法二:FScriptDelegate
FScriptDelegate BtnEventDel;
BtnEventDel.BindUFuncion(this, "ButtonEvent");
Button->OnReleased.Add(BtnEventDel);

//方法三:声明BlueprintCallable,蓝图调用
UFUNCTION(BlueprintCallable)
void ButtonEvent();

添加和移除组件:

//添加子组件
UWidget->AddChildXXX(...);
//移除子组件
UWidget->RemoveChild(...);
//从父组件移除
UWidget->RemoveFromParent();

设置布局:

//在Canvas中添加子组件会返回slot插槽对象,通过设置这个对象可以改变布局
//UCanvasPanel* MyCanvasPanel;
//UImage* MyImage;
UCanvasPanelSlot* ImgSlot = MyCanvasPanel->AddChildToCanvas(MyImage);
ImgSlot->SetAnchors(FAnchors(0.f));	//设置锚点
ImgSlot->SetOffsets(FMargin(100.f, 100.f, 50.f, 50.f));	//设置位移

三、生命周期探索

Actor的初始化过程:

  1. Actor的构造函数
  2. 初始化Components
  3. PostInitializeCompoents()
  4. BeginPlay()
  5. Tick(float DeltaSeconds)

UserWidget的初始化过程:

  1. 构造器函数 UUserWidget(const FObjectInitializer& ObjectInitializer);
  2. Initialize()
  3. NativeTick(const FGeometry& MyGeometry, float InDeltaTime);

世界的创建:

  1. GameInstance实例化,执行Init函数
  2. 执行关卡蓝图构造器
  3. 执行场景中Actors的构造器
  4. 执行GameMode构造器
  5. 执行关卡蓝图、场景中Actors及GameMode的PostInitializeComponents函数(子组件构造完成后执行)
  6. 执行Controller、HUD、Character等在GameSettings中设置的Gameplay框架类的构造器及PostInitializeComponents函数
  7. 执行场景中Actor、GameMode、关卡蓝图、Gameplay框架类的BeginPlay函数
  8. 若有Widget在执行HUD类的BeginPlay函数时被创建,将执行Widget的构造器及Initialize函数
  9. 执行所有已经初始化完成类的Tick函数

提前预置Actor和设置中的类(如Gameplay框架类)等会在都执行完成Construct和PostInitializeComponents后才会执行BeginPlay,然后一起执行Tick。另外Gameplay框架类会在GameMode执行完 PostInitializeComponents后,才会开始构造。

【UE4全反射松耦合框架笔记】第一章 UE4框架基础(上)_第1张图片

上图中,因为UserWidget是在HUD执行BeginPlay时才被创建,所以会在HUD后执行构造器函数和Initialize函数

四、全局类与接口

全局类

方法一:UBlueprintFunctionLibrary
继承自UBlueprintFunctionLibrary的类,主要是给蓝图提供静态函数访问,如果只在C++中使用,不必继承此类。常用的C++类,例如UGameStatics、UGameUserSettings类等,均是继承自UBlueprintFunctionLibrary类。

方法二:GameEngine->GameSingleton
GEngine->GameSingletion是GEngine下的一个UObject指针,可以作为全局变量提供给所有对象调用,这个变量可以在编辑器中指定其类型,UE4建议在这个对象中放置不需要修改的变量。
【UE4全反射松耦合框架笔记】第一章 UE4框架基础(上)_第2张图片
GameSingleton类

/*
UCLASS中Blueprintable及BlueprintType解释
Blueprintable/NotBlueprintable
	此说明符表示蓝图可继承此类,默认情况下是NotBlueprintable。但如果此类的父类包含此说明符,在默认情况下将继承父类的说明符,允许蓝图继承此类。因为UObject没有此标识符,所以若要蓝图可以继承和使用此类,需要声明此标识符。
BlueprintType
	表示蓝图可以创建此类类型的变量。但其实,只要声明Blueprintable,就可以作为变量在蓝图中使用了。所以,结构体和枚举可能更需要使用此标识符。此处,还可以再探究。
*/
UCLASS(Blueprintable, BlueprintType)
class XXX_API UMyGameSingleton: public UObject
{
	GENERATED_BODY()
	...
}

获取GameSingleton的变量

UMyGameSingleton* MySingleton = Cast<UMyGameSingletonClass>(GEngine->GameSingleton);

其它引擎全局类
UGameInstance:生命周期一直伴随整个游戏周期,从打开程序直到关闭程序。
UGameplayStatics:可以获取常用的Gamplay对象
UGameUserSettings:可以获取到配置信息

接口

一般,我们创建接口类时,尽量继承UInterface类。继承自UInterface的接口类,可以通过函数反射,提供给蓝图实现和使用。
当创建继承自UInterface的类时,引擎帮我们生成的头文件会有两个类,我们在第二个类中(以I开头)声明函数。
如果接口中的抽象方法需要在蓝图中调用,我们实现的自定义接口类一般有两种情况:1. 函数可以在C++子类和蓝图类中实现和调用 2.函数只能在C++中实现,在蓝图类中调用。

情况一:默认接口类中的函数是必须要允许在蓝图中实现

通过集成UInterface类,引擎自动生成的接口类的宏不带标识符meta=(CannotImplementInterfaceInBlueprint)。所以,我们在接口类中声明的方法的UFUNCTION宏必须使用BlueprintNativeEvent或者BlueprintImplementableEvent标识符。
自定义接口类头文件:

//默认情况下,UINTERFACE标识符只有MinimalAPI
UINTERFACE(MinimalAPI)
class UMyInterface : public UInterface
{
	GENERATED_BODY()
}

class XXX_API IMyInterface
{
	GENERATED_BODY()
public:
	/*默认下的接口类,在声明UFUNCTION宏时,必须使用BlueprintNativeEvent或BlueprintImplementableEvent标识符。
	第一和第三个函数可以在C++文件中实现;这四个函数均可在蓝图中实现;前两个函数可以在蓝图中调用。
	这四个函数没有使用virtual和=0,第一和第三个函数在C++中实现见下面代码*/
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
	void FuncOne();

	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent)
	void FuncTwo();

	UFUNCTION(BlueprintNativeEvent)
	void FuncThree();

	UFUNCTION(BlueprintImplementableEvent)
	void FuncFour();
}

继承上述接口类的子类头文件:

class XXX_API AMyActor : public AActor, public UMyInterface
{
	GENERATED_BODY()
public:
	//注意这里的实现,需要加virtual、override关键字,且函数名加_Implementation后缀
	virtual void FuncOne_Implementation() override;

	virtual void FuncThree_Implementation() override;
}

如果接口中的方法只在C++中实现和调用,那就在接口中声明抽象方法并实现,比如:

virtual void GetIndex() {}

情况二:函数不需要在蓝图中实现

如果虚函数不能在蓝图中实现,我们必须在自定义接口类的宏中添加meta=(CannotImplementInterfaceInBlueprint)标识符。
只有加上此标识符,虚函数的UFUNCTION宏才可以不添加BlueprintNativeEvent和BlueprintImplementableEvent标识符,也就不需要在蓝图中实现。

UINTERFACE(MinimalAPI, meta=(CannotImplementInterfaceInBlueprint))
class UMyInterface : public UInterface
{
	GENERATED_BODY()
}

class XXX_API IMyInterface
{
	GENERATED_BODY()
public:
	//此类需要使用virtual关键字,函数后边的=0表示继承的子类需要实现此方法。如果允许蓝图调用,可以加上BlueprintCallable,否则可以不加
	UFUNCTION(BlueprintCallable)
	virtual void FuncOne() = 0;
}

继承UInterface的接口,不能创建UPROPERTY()变量

虚函数的调用

允许在蓝图实现的函数和不允许在蓝图实现的函数的调用方法不同。

//调用第一步是相同的,就是首先我们需要获取一个继承定义接口类的对象
TArray<AActor*> Actors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AMyInterfaceActor::StaticClass(),  Actors);
if(Actors.Num() > 0)
{
	IMyInterface* MyInterfacePtr = Cast<IMyInterface>(Actors[0]);	//强制转换为父类接口
	/*
	因为使用了BlueprintNativeEvent和BlueprintImplementableEvent标识符的函数的实现与一般C++不同,所以调用方法不同。
	下面代码的调用就是允许在蓝图中实现的函数的调用。
	*/
	MyInterfacePtr->Execute_FuncOne(Actors[0]);		//函数名添加前缀Execute_,第一个参数为子类对象,后续参数为函数实际参数
	//不在蓝图实现的虚函数与一般C++相同,所以按照常规方法调用即可
	//MyInterfacePtr->FuncOne();		//而一般的虚函数可以在转换后直接调用,或者直接调用对象的函数即可。Actors[0]->FuncOne();
}

五、委托与函数传递

委托

UE4委托参见另一篇笔记《【UE4笔记】委托》

函数传递

TFunction

声明TFunction模板变量

public:
	//设置TFunction模板变量的值,这里主要说明如何将函数作为参数传入
	void SetFuncOne(TFunction<void(FString)> Func)
	{		//变量实现应在cpp中,这里为方便说明,所以在声明中实现
		FuncOne = Func;
	}
	
public:
	//TFunction模板声明
	TFunction<void(FString)> FuncOne;  	//void是函数返回类型,FString为函数参数类型

通过Lambda表达式传参,调用SetFuncOne函数

public:
	void MyFunc(FString Str);

//SetFuncOne的调用
XXX->SetFuncOne([this](FString InfoStr){MyFunc(InfoStr);});

使用TFunction定义统一接口

声明函数模板
template<typename RetType, typename... VarTypes>
void PassFunc(TFunction<RetType(VarTypes...)> TarFunc)
{
	bool Result = TarFunc(FString("Hello"), 1024);
}

========================================================
调用函数模板
bool Func(FString, int32);
XXX->PassFunc<bool, FString, int32>([this](FString InfoStr, int32 Count){return Func(Info, Count);});

TMemFunPtrType

使用这种方法传递的函数,函数的返回值和传入参数都已经在函数参数指明,所以传入时,只需要按照函数要求传入即可。

//声明单播委托,这里主要是测试传递函数,所以将接收的函数绑定到这个单播委托上
DECLARE_DELEGATE(FMyDelegate);

//声明模板函数
public:
	template<typename UserClass>
	void BindAndExecDel(UserClass* TarObj, typename TMemFunPtrType<false, UserClass, void(FString, int32)>::Type InMethod);
public:
	FMyDelegate MyDelegate;


//实现模板函数,TMemFuncPtrType模板作为参数类型,用于传递函数参数
template<typename UserClass>
void XXX::BindAndExecDel(UserClass* TarObj, typename TMemFunPtrType<false, UserClass, void(FString, int32)>::Type InMethod)
{
	//因为MyDelegate是一个无参委托,所以调用ExecuteIfBound时是不需要传入参数的。但是绑定的函数却是有参,所以我们可以在BindUObject函数调用,即绑定函数到委托时,固定参数。
	MyDelegate.BindUObject(TarObj, InMethod, FString("Hello World!"), 1024);
	MyDelegate.ExecuteIfBound();
}

//调用模板函数,这里调用的时候,可以传入函数地址
void Func(FString, int32);
XXX->BindAndExecDel(this, &XXX::Func);		//允许传入的函数为返回类型void,参数为FString和int

FMethodPtr

使用TMehtodPtr时,我们需要声明一个委托,然后通过委托的静态成员获取到函数类型。这种方法可以用于函数委托绑定的传参。下面也将介绍如何不用声明委托,即可使用此方法传递函数。
使用这种方法传入的函数,它的返回值和参数类型都是在委托中声明的。

//声明单播委托
DECLARE_DELEGATE_TwoParams(FMyDelegate, FString, int32)

//声明模板函数
public:
	//参数使用了委托,通过委托的静态成员获取函数声明类型
	template<typename UserClass>
	void BindAndExecDel(UserClass* TarObj, typename FMyDelegate::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod);
public:
	FMyDelegate MyDelegate;

//实现模板函数
template<typename UserClass>
void BindAndExecDel(UserClass* TarObj, typename FMyDelegate::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod)
{
	//委托的参数是在调用ExecuteIfBound或者Broadcast时传入的。此处注意与上面无参委托的区别。
	MyDelegate.BindUObject(TarObj, InMethod);
	MyDelegate.ExecuteIfBound(FString("Hellow, World!"), 1024);
}

void Func(FString, int32);
XXX->BindAndExecDel(this, &XXX::Func);

不声明委托,使用FMethodPtr传递函数(泛型定义统一接口)
使用这种方法,基本可以传递任意返回值和参数的函数,只需要在调用函数时,按照需要的函数声明委托即可。

函数模板定义:
public:
	//上例中的委托类型被模板参数替代
	template<typename DelegateType, typename UserClass, typename... VarTypes)
	inline void PassFunc(UserClass* TarObj, typename DelegateType::template TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, VarTypes... Vars)
	{
		//这里的委托声明只是为了测试函数的传递。
		DECLARE_DELEGATE(FTempDelegate);
		FTempDelegate TempDelegate;
		TempDelegate.BindUObject(TarObj, InMethod, Vars...);		//传入多个参数,参数名后有...,不要忘记
		TempDelegate.ExecuteIfBound();
	}
=======================================================================================================
函数模板调用:
void Func(FString, int32);

//函数调用时,函数模板第一个参数传入委托类型
DECALRE_DELEGATE_TwoParams(FTempDelegate, FString, int32);
XXX->PassFunc<FTempDelegate>(this, &XXX::Func, FString("Hello"), 1024);

你可能感兴趣的:(UE4)