UE4C++基础(二)

基础回顾

UE4C++基础(一)_WLSTLA-CSDN博客

1. C++与UMG交互

​ 首先我们需要设置玩家的输入模式,可以在PlayerController中的BeginPlay进行设置:

void AUIController::BeginPlay()
{
	Super::BeginPlay();
	FInputModeGameAndUI InputMode;
	InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockAlways);
	InputMode.SetHideCursorDuringCapture(false);
	SetShowMouseCursor(true);  //显示鼠标
}

​ 我们可以创建一个继承于UUserWidgetUObject,作为我们自己的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()
{
	...
}
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
    • 创建LevelLevelScriptActor(关卡蓝图)构造
    • Level中存在的Actor构造
    • GameMode构造
    • 随后执行ActorGameModePostInitializeCompoents()
    • 执行LevelActorGameModeBlueprintsGameplay框架类的BeginPlay()
    • 执行所有初始化完成类的Tick()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3HX1jDen-1638108670374)(image-20211128181138848.png)]

注意:只有当场景中的ActorGamePlay框架中设置的类(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中进行调用。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPaveYuT-1638108670378)(image-20211128193706347.png)]

    这个类中写的方法只能供蓝图中使用,如果你想在C++中使用,则需要使用下面这个方法。

  • GameEngine->GameSingleton

GameEngine->GameSingletonGEngine下的一个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};
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hsTkKRYB-1638108670380)(image-20211128195313303.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w02mEEmo-1638108670383)(image-20211128212208655.png)]

  • 调用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*类型的。原因是在UE4UClass是继承自UStructUStruct继承自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.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WxIJFa5A-1638108670385)(image-20211128212318688.png)]

  • 调用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);
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I0pBNFDO-1638108670387)(image-20211128213822829.png)]

5. UClassUObjectCDO

  • UClassUObject的关系

​ 两者都是继承自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是获取类的初值。

码字不易,如果觉得对你有帮助的话,点个免费的赞支持下博主把 😆

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值