基础回顾
1. C++与UMG交互
首先我们需要设置玩家的输入模式,可以在PlayerController
中的BeginPlay
进行设置:
void AUIController::BeginPlay()
{
Super::BeginPlay();
FInputModeGameAndUI InputMode;
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockAlways);
InputMode.SetHideCursorDuringCapture(false);
SetShowMouseCursor(true); //显示鼠标
}
我们可以创建一个继承于UUserWidget
的UObject
,作为我们自己的UMG
基类,复写Initialize()
函数,随后在蓝图中创建UMG
中继承该UObject
。
UCLASS()
class REFLECTIONFRAME_API UMyUIWidget : public UUserWidget
{
GENERATED_BODY()
public:
UMyUIWidget(const FObjectInitializer& ObjectInitializer);
virtual bool Initialize()override;
...
};
创建Widget
的方法:
// 1. 使用CreateWidget
TSubclassOf<UUserWidget> NewWidget;
UUserWidget* CurrentWidget;
CurrentWidget = CreateWidget(GetWorld(),NewWidget);
CurrentWidget->AddToViewport(); //显示到窗口
/* 删除
CurrentWidget->RemoveFromParent();
CurrentWidget->nullptr;
*/
// 2. 使用WidgetTree->ConstructWidget方法 创建子组件
#include "Blueprint/WidgetTree.h"
#include "Components/TextBlock.h"
UTextBlock* SpawnText;
SpawnText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass());
从蓝图获取组件的方法:
-
从根组件开始获取
假如我们需要获取根组件(
CanvasPanel
)下第一个子组件TextBlock
:
UCanvasPanel* Root = Cast<UCanvasPanel>(GetRoot());
if(Root)
{
UTextBlock* Text = Cast<UTextBlock>(Root->GetChildAt(0));
}
- 根据
Widget
名字来获取。(名字与UMG
中命名的相同)
UBorder* BorderPanel = (UBorder*)GetWidgetFromName(TEXT("Panel"));
if(BorderPanel)
{
UE_LOG(LogTemp,Warning,TEXT("Get BorderPannel"));
}
- 使用
UPROPERTY()
,中的meta=(BindWidget)
获取。和根据名字来获取相同,都是基于反射来获取,这里属性的命名需要和UMG中相同。
UPROPERTY(meta=(BindWidget))
class UButton* Button3; //名字要与绑定的名字一样
Button
组件绑定代理的方法
- 使用
FScriptDelegate
进行绑定
UButton* Button = GetWidgetFromName(TEXT("Button2"));
FScriptDelegate ClickDelegate;
ClickDelegate.BindUFunction(this,FName("ButtonClickEvent"));
Button->OnClicked.Add(ClickDelegate);
// Button2->OnClicked.AddUnique(ClickDelegate);
void UMyUIWidget::ButtonClickEvent()
{
...
}
- 传统绑定方式(关于UE4代理可以查看我的这篇博文(UE4委托_WLSTLA-CSDN博客)
UPROPERTY(meta=(BindWidget))
class UButton* Button3;
Button3->OnClicked.AddDynamic(this,&UMyUIWidget::Button3ClickEvent);
//Button3->OnClicked.AddUniqueDynamic(this,&UMyUIWidget::Button3ClickEvent);
void UMyUIWidget::Button3ClickEvent()
{
...
}
2. 生命周期
这里的生命周期我们只探讨至Tick
,参考于梁迪的全反射零耦合框架UE4全反射零耦合框架开发坦克游戏
-
Actor
的生命周期- 构造函数
- 初始化
Components
PostInitializeComponents()
BeginPlay()
Tick(float DeltaSeconds)
-
Level
- 初始化
GameInstance
- 创建世界
World
- 创建
Level
,LevelScriptActor
(关卡蓝图)构造 Level
中存在的Actor
构造GameMode
构造- 随后执行
Actor
、GameMode
的PostInitializeCompoents()
- 执行
Level
中Actor
、GameMode
、Blueprints
、Gameplay
框架类的BeginPlay()
- 执行所有初始化完成类的
Tick()
- 初始化
注意:只有当场景中的Actor
和GamePlay
框架中设置的类(PlayerController、PlayerState..
)都执行完成Construct、PostInitializeComponents
后才执行BeginPlay()
, GamePlay
中的框架类需要等待GameMode
执行完PostInitializeComponents
才会进行Construct
。
3. 全局类与接口
-
UBlueprintFunctionLibrary
相当于蓝图中的蓝图函数库,在
C++
中继承于UBlueprintFunctionLibrary
。UCLASS() class REFLECTIONFRAME_API UMyFunctionLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION(BlueprintPure) static FORCEINLINE int32 GetMaxNum(const int32& val1,const int32& val2) { return val1>val2?val1:val2; } };
同时其中函数需要是静态函数。之后你便可以在
Blueprint
中进行调用。这个类中写的方法只能供蓝图中使用,如果你想在
C++
中使用,则需要使用下面这个方法。 -
GameEngine->GameSingleton
GameEngine->GameSingleton
是GEngine
下的一个UObject
指针,作为全局单例提供给C++
使用,需要在编辑器中指定类型,UE4
的建议这里存放的对象里面的变量最好是不需要修改的。也就是存放一些const
变量,函数也不能修改类变量。
UCLASS(Blueprintable,BlueprintType)
class REFLECTIONFRAME_API UMySingletonObject : public UObject
{
GENERATED_BODY()
public:
FORCEINLINE int32 GetGloableVal()const
{
return _GloableInt;
}
private:
const int32 _GloableInt{10};
};
// 获取Singleton
UMySingletonObject* mySingle = Cast<UMySingletonObject>(GEngine->GameSingleton);
mySingle->GetGloableVal();
其他的全局类:
-
UGameInstance
:生命周期伴随整个程序的启动到结束,可以跨关卡交流数据 -
UGamePlayStatics
:头文件#include "Kismet/GameplayStatics.h"
,包含一些GamePlay
框架开发中常用的方法(例如:GetAllActorsOfClass()
)…
4. 反射的应用
反射是UE4
的一大特性,这里我们可以使用反射来实现获取UEnum、UPROPERTY、UFUNCTION
。
UENUM()
enum class EState:uint8
{
Default,
Run,
Walk
};
UCLASS()
class REFLECTIONFRAME_API USpawnObject : public UObject
{
GENERATED_BODY()
public:
UFUNCTION()
int32 ReflectFunc1(const FString&str,int val);
UPROPERTY()
int32 IntVal{10};
};
int32 USpawnObject::ReflectFunc1(const FString& str, int val)
{
UE_LOG(LogTemp,Warning,TEXT("str:%s,val:%d"),*str,val);
return val+10;
}
如上述UObejct
类,我们需要利用反射来调用其中的EState、IntVal、ReflectFunc1
。
- 调用
Enum
// 通过反射获取UEnum
UEnum* enumPtr = FindObject<UEnum>(ANY_PACKAGE,TEXT("EState"));
if(enumPtr)
{
UE_LOG(LogTemp,Warning,TEXT("enum index:0 is %s"),*FName(enumPtr->GetNameByIndex(0)).ToString());
}
- 调用
UPROPERTY()
// 通过反射获取UPROPERTY
// 1. 通过FindField
USpawnObject* SpawnObj = NewObject<USpawnObject>();
FIntProperty* IntPropPtr = FindFieldChecked<FIntProperty>(SpawnObj->StaticClass(),FName("IntVal"));
if(IntPropPtr)
{
int32 intval = IntPropPtr->GetPropertyValue_InContainer(SpawnObj);
UE_LOG(LogTemp,Warning,TEXT("get intval val is %d"),intval);
IntPropPtr->SetPropertyValue_InContainer(SpawnObj,20); //set val
intval = IntPropPtr->GetPropertyValue_InContainer(SpawnObj);
UE_LOG(LogTemp,Warning,TEXT("get intval val is %d(After)"),intval);
}
// 2. 通过TFieldIterator进行迭代
for (TFieldIterator<UProperty> Ite(YourObjectClass); Ite; ++Ite) {
UProperty* Property = *Ite;
if (Property->GetNameCPP() == "YourPropertyName") {
UBoolProperty* BoolPro = Cast<UBoolProperty>(Property);
if (BoolPro) {
void* ValuePtr = Property->ContainerPtrToValuePtr<bool>(this);
// 获取值
bool GetValue = BoolPro->GetPropertyValue(ValuePtr);
// 设置值
BoolPro->SetPropertyValue(ValuePtr,false);
}
}
}
这里FindFieldChecked
的函数原型如下:
template<typename T>
T* FindFieldChecked( const UStruct* Scope, FName FieldName )
可知这里第一个参数是UStruct*
类型的,但我们传入的却是一个UClass*
类型的。原因是在UE4
中UClass
是继承自UStruct
。UStruct
继承自UField
,UField
继承自UObject
。这个类中包含了反射、以及序列化相关的数据。
class COREUOBJECT_API UClass : public UStruct
class COREUOBJECT_API UStruct : public UField //Base class for all UObject types that contain fields.
class COREUOBJECT_API UField : public UObject //Base class of reflection data objects.
-
调用
UFUNCTION()
- 通过
FindFunction
调用
UFunction* functor = SpawnObj->FindFunction(FName("ReflectFunc1")); struct { const FString str{"unreal"}; int32 val{20}; }ParamStruct; //这里第二个参数是传入的是函数参数,void* 类型的 我们需要传入一个struct* 传入后参数会根据变量的内存地址偏移进行自动赋值。 SpawnObj->ProcessEvent(functor,&ParamStruct); // 通过地址偏移计算返回值 uint8* RetValPtr = (uint8*)&ParamStruct + functor->ReturnValueOffset; int32* RetVal = (int32*)(RetValPtr); if(RetVal) { UE_LOG(LogTemp,Warning,TEXT("Get Return Val:%d"),*RetVal); }
- 通过
TDelegate
调用
TDelegate<void(FString,int32)> FuncDelegate = TDelegate<void(FString,int32)>::CreateUFunction(SpawnObj,FName("ReflectFunc1")); FuncDelegate.Execute(TEXT("Unity"),10); // 注意TDelegate如果绑定了一个函数其中形参是引用的,改变引用的值不会生效
- 通过
FScriptDelegate
调用
//3. FScriptDelegate 注意FScripDelegate没有返回值,需要改变传入的值可以传入引用, // 有返回值的代理请使用TDelegate或者UFunction FScriptDelegate scriptDele; scriptDele.BindUFunction(SpawnObj,FName("ReflectFunc1")); struct { const FString str{"Cocos"}; int32 val{2021}; }ParamStruct2; scriptDele.ProcessDelegate<USpawnObject>(&ParamStruct2);
- 通过
5. UClass
、UObject
与CDO
UClass
与UObject
的关系
两者都是继承自UObejct
的类,同时都拥有自己的实例对象,UClass
保存着有反射数据,两者可以通过转换获取对方的实例:
CDO
全称为类默认对象(Class Default Object
),每个继承自UObject
的类都有CDO
,其中记录着该类默认的信息(例如属性值Int32 val
一开始为10,后面修改为其他数值,CDO
中记录的便是10这个值。前提是获取类的CDO
)。
获取UClass
的方法分为GetClass()
和StaticClass()
,随后可以通过GetDefaultObject()
方法获取CDO
,但这通过这两个方法获取的CDO
会有所不同。关于具体的细节可以查看这篇博文,【UE·底层篇】一文搞懂StaticClass、GetClass和ClassDefaultObject_水鸡的游戏开发学习笔记-CSDN博客,这里直接给出我自己的理解:
UCLASS()
class REFLECTIONFRAME_API ACommonActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="CDOTestArg")
int32 val{0};
}
//1.GetClass获取CDO 获取对象初值
UE_LOG(LogTemp,Warning,TEXT("GetClass() val:%d"),GetClass()->GetDefaultObject<ACommonActor>()->val); //实例的值
//2.StaticClass获取CDO 获取类初值
UE_LOG(LogTemp,Warning,TEXT("StaticClass() val:%d"),ACommonActor::StaticClass()->GetDefaultObject<ACommonActor>()->val); //0
结论:通过GetClass()
获取的CDO
是获取对象实例的初值,通过StaticClass
获取的CDO
是获取类的初值。