TomLooman_ActionRoguelike_第二章項(xiàng)目與版本
該專欄用于保存對(duì)TomLooman的ActionRoguelike項(xiàng)目的學(xué)習(xí)筆記,學(xué)習(xí)過(guò)程中的思考與記錄不一定準(zhǔn)確。
教程參考:https://github.com/tomlooman/ActionRoguelike
基于UE5.0的項(xiàng)目實(shí)現(xiàn):https://github.com/CarolBaggins2023/TomLooman_ActionRoguelike_Tutorial

2023_07_24
C++基礎(chǔ)類,添加組件(第三人稱攝像機(jī)),綁定運(yùn)動(dòng)輸入,角色設(shè)置
?
建立角色的C++類,繼承自Character,

Scharacter.h文件如下,

"CoreMinimal.h"頭文件包含一套來(lái)自UE的核心編程環(huán)境的普遍存在類型(包含F(xiàn)String,F(xiàn)Name,TArray等),通常被多數(shù)引擎的頭文件包含。
?
因?yàn)镾Character類繼承自Character類,基于Character類生成源代碼,所以包含"GameFramework/Character.h"頭文件。
?
"SCharacter.generated.h"是所有C++類都會(huì)包含的頭文件(SCharacter為該類的類名),UE將生成所有反射數(shù)據(jù)并放入該文件中。您必須將該文件作為聲明類型的標(biāo)頭文件中的最后一個(gè)包含語(yǔ)句,將其包含進(jìn)去,他必須在最下方,切記!
?
Uclass()是類說(shuō)明符,類聲明的語(yǔ)法如下
UCLASS([specifier, specifier, ...], [meta(key=value, key=value, ...)])
class ClassName : public ParentName
{
??? GENERATED_BODY()
}
Specifier為類說(shuō)明符,meta為類元數(shù)據(jù),兩者合稱為描述符,被傳入CLASS宏。這里的類說(shuō)明符和類元數(shù)據(jù)為空。
?
class ACTIONROGUELIKE_API ASCharacter中class后類名前的ACTIONROGUELIKE_API是一個(gè)宏,ACTIONROGUELIKE指代模塊名。在虛幻引擎的代碼中,MYGAME_API被用來(lái)標(biāo)記一些類以及函數(shù),從而將這些類和函數(shù)從模塊或者是插件中公布出去,即被MYGAME_API標(biāo)記的類和函數(shù)可以允許被其他模塊或者插件訪問(wèn),也就是控制代碼的可見(jiàn)性和模塊間的連接。模塊的含義參考:https://docs.unrealengine.com/5.2/zh-CN/module-properties-in-unreal-engine/
?
因?yàn)槲覀儎?chuàng)建的SCharacter類繼承自Character類,所以屬于游戲性類,游戲性類的前綴如下,

我們SCharacter的實(shí)例可以直接生成到世界場(chǎng)景中,所以我們代碼中的類名不是SCharacter,而是ASCharacter。
?
GENERATED_BODY()UE4 將這個(gè)標(biāo)記替換為該類型生成的所有必要的樣板代碼。引擎在背后為我們做了很多工作,生成了很多代碼。
?
結(jié)合SCharacter.cpp文件

?
ASCharacter::ASCharacter()類的構(gòu)造函數(shù)。
?
PrimaryActorTick.bCanEverTick = true;如果為true,則這個(gè)Character在每一幀都調(diào)用一次Tick函數(shù)。設(shè)置為false后可提升性能,默認(rèn)為false。
?
virtual void BeginPlay() override;游戲開(kāi)始或Character出生時(shí)被調(diào)用。
Super::BeginPlay();先調(diào)用ACharacter父類的BeginPlay,其它類成員函數(shù)類似。
?
Tick(float DeltaTime)DeltaTime為兩次Tick之間的時(shí)間間隔
?
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;調(diào)用該函數(shù),完成該函數(shù)內(nèi)一系列語(yǔ)句,將player的某個(gè)input映射到某個(gè)函數(shù)(控制角色的某種行為)。
而且這里的UInputComponent就是上面說(shuō)的兩種游戲類種的另一類,因?yàn)椴荒苤苯硬贾玫绞澜鐖?chǎng)景中,所以不是AInputComponent,而是UInputComponent。
?

創(chuàng)建C++的藍(lán)圖子類。C++和藍(lán)圖應(yīng)該混合使用,因?yàn)橐恍┎僮鞅热缣砑觤esh,在藍(lán)圖中更方便。
?

“終于,到了第4個(gè)紀(jì)元,UE窺得一絲隔壁平行宇宙Unity的天機(jī)。下定決心,讓Actor們輕裝上陣,只提供一些通用的基本生存能力,而把眾多的“技能”抽象成了一個(gè)個(gè)“Component”并提供組裝的接口,讓Actor隨用隨組裝,把自己武裝成一個(gè)個(gè)專業(yè)能手。”
?
CapsuleComponent常用來(lái)處理碰撞和移動(dòng)。
ArrowComponent用于指示方向。
這里的mesh(網(wǎng)格)是SkeletonMeshComponent,虛幻引擎中角色的基礎(chǔ)資產(chǎn)是 骨骼網(wǎng)格體 資產(chǎn),這種資產(chǎn)包含角色的一個(gè)可視化網(wǎng)格體(即角色的幾何模型渲染結(jié)果),以及角色的一個(gè)包含骨骼數(shù)據(jù)(用于為角色制作動(dòng)畫)的骨架。
?
CharacterMovementComponent
?能夠使人身不使用剛體物理即可行走、跑動(dòng)、飛行、墜落和游泳。 其為角色特定,無(wú)法被任何其他類實(shí)現(xiàn)。?
?
每個(gè)Actor都有一個(gè)RootComponent。一般有一個(gè)SceneComponent作為RootComponent,定義Actor在世界中的transform(變換,包括位置location,旋轉(zhuǎn)rotation,縮放scale),其它所有組件都需要attach到RootComponent上。
?
在SCharacter中添加兩個(gè)組件

UPROPERTY([specifier, specifier, ...], [meta(key=value, key=value, ...)])??? Type VariableName;
UPROPERTY是屬性說(shuō)明符,在聲明屬性時(shí),屬性說(shuō)明符?可被添加到聲明,以控制屬性與引擎和編輯器諸多方面的相處方式。這里的VisibleAnywhere指此屬性在所有屬性窗口中都可見(jiàn),設(shè)置后我們可以在編輯器的藍(lán)圖中看到Camera和SpringArm的各種屬性,如下

否則紅框的部分是不可見(jiàn)的。
UPROPERTY是實(shí)現(xiàn)UE中的“反射”的一種方式。
?
虛幻引擎反射系統(tǒng)?使用宏為提供引擎和編輯器各種功能,封裝你的類。也就是
反射就是在運(yùn)行狀態(tài)中:
1.對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;
2.對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)屬性和方法;
即可以動(dòng)態(tài)的獲取信息以及調(diào)用對(duì)象的方法稱之為反射機(jī)制。
?
因?yàn)閁SpringArmComponent和UCameraComponent繼承自UObject,不能直接在世界中生成,所以類名的前綴是U。
攝像機(jī)(Camera) 代表了玩家的視角,比如玩家如何查看世界。
彈簧臂(SpringArm)組件努力與其對(duì)象(在這里是我們的Character)之間保持一個(gè)固定距離(TargetArmLength,不存在碰撞時(shí)的彈簧臂自然長(zhǎng)度)如果發(fā)生碰撞,就會(huì)使子對(duì)象收回,如果沒(méi)有碰撞,則發(fā)生回彈。
如果把SpringArm作為Camera的父項(xiàng),就可以創(chuàng)建一個(gè)第三人稱視角,且能保持Camera與SpringArm的對(duì)象(我們的Character)之間沒(méi)有遮擋物。(這里需要調(diào)用attach函數(shù),下面會(huì)做)




SpringArm始終保持遮擋物(白色方塊)不會(huì)擋住Camera拍攝Character的畫面。
?
在SCharacter.cpp中實(shí)例化SpringArm和Camera,
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>("SpringArmComp");
CameraComp = CreateDefaultSubobject<UCameraComponent>("CameraComp");
形式如下,
CreateDefaultSubobject<類名>(在UE編輯器中顯示的名字)
1.CreateDefaultSubobject必須寫在Actor的無(wú)參構(gòu)造函數(shù) 中,否則crash;
2.CreateDefaultSubobject中的編輯器中顯示的名字參數(shù)在同一個(gè)Actor中不能重復(fù),否則crash;
?
SpringArmComp->SetupAttachment(RootComponent);
CameraComp->SetupAttachment(SpringArmComp);
將SpringArm附加到角色,將Camera附加到SpringArm。A“附加”到B,指A成為B的子組件。子組件的位置、旋轉(zhuǎn)和縮放是相對(duì)于其父組件或Actor。Attach的函數(shù)原型如下,
void SetupAttachment(USceneComponent* InParent, FName InSocketName = NAME_None);
如果不進(jìn)行attach,則會(huì)是如下的層級(jí)關(guān)系,SpringArm不會(huì)隨Character移動(dòng),Camera不會(huì)隨SpringArm移動(dòng)。


?
在SCharacter.h中聲明
void MoveForward(float val);
void MoveRight(float val);
在SCharacter.cpp中實(shí)現(xiàn)
void ASCharacter::MoveForward(float val) {
??? FRotator ControlRot = GetControlRotation();
??? ControlRot.Pitch = 0.0f;
??? ControlRot.Roll = 0.0f;
???
??? AddMovementInput(ControlRot.Vector(), val);
}
void ASCharacter::MoveRight(float val) {
??? FRotator ControlRot = GetControlRotation();
??? ControlRot.Pitch = 0.0f;
??? ControlRot.Roll = 0.0f;
??? FVector RightVector = FRotationMatrix(ControlRot).GetScaledAxis(EAxis::Y);
???
??? AddMovementInput(RightVector, val);
}
并在SetupPlayerInputComponent中調(diào)用
PlayerInputComponent->BindAxis("MoveForward", this, &ASCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &ASCharacter::MoveRight);
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
?
BindAxis的函數(shù)接口為template<class UserClass>
FInputAxisBinding& BindAxis( const FName AxisName, UserClass* Object, typename FInputAxisHandlerSignature::TMethodPtr< UserClass > Func )
第一個(gè)參數(shù)是在Project Setting里設(shè)置的(Axis)軸映射名如下(將外部輸入與該輸入下要觸發(fā)的事件綁定),第二個(gè)參數(shù)是調(diào)用此函數(shù)的對(duì)象?(涉及UE的委托機(jī)制),第三個(gè)參數(shù)是一個(gè)函數(shù)指針(將觸發(fā)的事件與成員函數(shù)綁定)。

?
BindAxis后,外部輸入會(huì)觸發(fā)類的成員函數(shù),所以要聲明、實(shí)現(xiàn)該成員函數(shù),并在函數(shù)內(nèi)進(jìn)行響應(yīng)。
在MoveForward的實(shí)現(xiàn)中,AddMovementInput的函數(shù)接口為
UFUNCTION(BlueprintCallable, Category="Pawn|Input", meta=(Keywords="AddInput"))
virtual void AddMovementInput(FVector WorldDirection, float ScaleValue = 1.0f, bool bForce = false);
該函數(shù)在所給的WorldDirection上增加一個(gè)外部輸入的變化量ScaleValue(ScaleValue<0則反向移動(dòng))
?
FRotator(旋轉(zhuǎn)類)是三個(gè)歐拉角Pitch、Roll、Yaw的封裝,所以Frotator類的ControlRot中有它們?nèi)齻€(gè)成員變量。在MoveForward和MoveRight中我們只需要用外部輸入控制Character的Yaw,所以把Pitch和Roll設(shè)為0(在UE中要顯式地用0.0f)。
GetControlRotation()返回Pawn的控制器的旋轉(zhuǎn)向量(相對(duì)世界坐標(biāo))。
ControlRot.Vector()將FRotator類型的變量轉(zhuǎn)換為Vector類型的變量。
FVector是三維向量結(jié)構(gòu)體。
?
將Fvector轉(zhuǎn)為FrotationMatrix(FRotationMatrix(ControlRot)),并獲取Y軸方向的方向向量(.GetScaledAxis(EAxis::Y)),從而得到指向當(dāng)前Controller朝向的右方的方向向量。
否則,由于Pawn自身朝向的朝右向量,一旦右轉(zhuǎn)就會(huì)一直在變化,導(dǎo)致會(huì)原地打轉(zhuǎn)。(參考:https://blog.csdn.net/surkea/article/details/127104682)
?
我們不把事件"Turn"和"LookUp"綁定到自定義的函數(shù)上,而是綁定到Pawn類自帶的APawn::AddControllerYawInput和APawn::AddControllerPitchInput上。這兩個(gè)函數(shù)將輸入值增加到控制器的Rotation上(Pawn相比Actor,增加了一個(gè)Controller,這里的Pawn就是我們的Character,Character是Pawn的子類)。
?
為了獲得正確的Camera與Pawn移動(dòng)表現(xiàn),我們還要在構(gòu)造函數(shù)ASCharacter::ASCharacter()中設(shè)置,
SpringArmComp->bUsePawnControlRotation = true;(SpringArm的Rotation跟隨它所attach的Pawn的Controller(我們的鼠標(biāo))的Rotation。若為false,則因?yàn)镾pringArm實(shí)際上控制著Camera(它是Camera的父組件),所以此時(shí)Camera固定朝著一個(gè)方向,不會(huì)隨著鼠標(biāo)的轉(zhuǎn)動(dòng)而轉(zhuǎn)動(dòng))——>控制Camera朝向
GetCharacterMovement()->bOrientRotationToMovement = true;(Character朝向加速方向(Character中有個(gè)Arrow組件表示方向)。若為false,則Character在不調(diào)用Turn時(shí)始終面朝一個(gè)方向。該選項(xiàng)與下一項(xiàng)沖突,為下一項(xiàng)為true,則該項(xiàng)無(wú)效。)
this->bUseControllerRotationYaw = false;(類似SpringArmComp->bUsePawnControlRotation,Character用Controller的Rotation控制朝向。)
第一句設(shè)置Camera朝向,二三句控制Character朝向(所以兩者沖突)。
GetCharacterMovement()->bOrientRotationToMovement = true; this->bUseControllerRotationYaw = false; ——>黑魂、老滾5,可看到角色正面(Controller控制Character朝X軸反方向移動(dòng)時(shí),Character面朝玩家)
GetCharacterMovement()->bOrientRotationToMovement = false; this->bUseControllerRotationYaw = true; ——>光明記憶、一些大逃殺,只能看到角色背面(因?yàn)榻巧冀K朝向Controller的X軸方向)
另外,因?yàn)閎OrientRotationToMovement和bUseControllerRotationYaw都是bool類型的變量,所以它們的名字都是b開(kāi)頭的。

給Character的mesh組件的skeletal mesh一個(gè)具體的東西,否則我們看不到角色。
這里還要把導(dǎo)入的角色與碰撞體適配,并且角色朝向與ArrowComponent方向一致。

加入角色動(dòng)畫,否則角色默認(rèn)擺大字。角色動(dòng)畫會(huì)根據(jù)角色的狀態(tài)、速度等因素渲染一個(gè)姿勢(shì),不同姿勢(shì)組成的時(shí)間序列形成了我們看到的動(dòng)畫效果。