從今天開始,我們一起來學習一下,如何使用C++將一個不帶有任何初學者內容的空模板,從無到有的創建一個簡答卻完整的FPS項目,通過這幾篇文章的學習,我們大致了解到UE4 C++編程的流程,能夠成功創建一個新的游戲模式,創建出第一人稱的角色,他能夠在場景中漫游並且向周圍射擊,完成一個整個工程。
第一部分:
(一)創建項目,新建游戲模式
1.新建一個C++的空項目,沒有初學者內容,取名MyFPSProject。
2.點擊創建后,將會在VS打開項目。首先我們來看一下VS的目錄,系統自動幫我們建立了MyFPSProject的類和 MyFPSProjectGameMode的游戲模式類。等下我們會用到它。


如上圖,點擊運行,此時VS將會以調試的方式打開UE4編輯器,打開我們的工程,大概看一下內容瀏覽器,如果看過之前我的那翻譯的那篇官方文檔,就應該明白C++項目中,會有C++Classes,而之后我們創建的C++代碼,都會出現在這里。
3.新建一個Maps文件夾,保存當前map為:MyFPSProjectMap

4.執行 編輯--項目設置:更改編輯器默認場景和游戲模式:分別為剛剛保存的MyFPSProjectMap,和之前在VS工程里面看到的引擎為我們創建的空的游戲模式MyFPSProjectGameMode。

5.然后簡單介紹一下游戲模式,游戲模式包含了游戲本身的定義,包括游戲規則啊,勝利的條件等等,同時,他也指定了一些默認的游戲類(gameplay framework)類讓我們使用,包含Pawn,PlayerController,和HUD。在我們創建我們的FPS角色之前,我們首先需要創建一個游戲模式引用角色。這里,我們只要在引擎為我們自動生成的游戲模式中添加內容即可。
為了保證游戲模式已經更換成我們自己的,我們先試着輸出一個測試信息,打開VS工程中的MyFPSProjectGameMode.h,他默認應該是這樣
- #pragma once
-
- #include "GameFramework/GameMode.h"
- #include "MyFPSProjectGameMode.generated.h"
-
- /**
- *
- */
- UCLASS()
- class MYFPSPROJECT_API AMyFPSProjectGameMode : public AGameMode
- {
- GENERATED_BOD
- };
大概看一下這段代碼,有兩處可能要稍微解釋一下
1.代碼里面兩個宏命令,UCLASS和GENERATED_BOD,只要是我們使用UE4 的C++類向導添加的自定義類,它頭上都會有UCLASS這個宏,他是為了讓引擎知道他的存在,這樣我們寫的類就能被包含在序列化等等其他引擎功能中,同時,等下我們就會發現,在自定義類的頭部有UFUNCTION()宏命令,也是同樣的作用。對於properties 還有一個UPROPERTY宏,這些說明符可以再 ObjectBase.h找到。GENERATED_BOD這里先不做解釋。
2.實際的游戲模式類名為“AMyFPSProjectGameMode”,前綴'A'表示這個類最終繼承自Actor。這里在之前的那篇翻譯的文章中也有提到。
6.接着,我們要重寫一個函數 virtual void StartPlay() override:
MyFPSProjectGameMode.h
- #pragma once
-
- #include "GameFramework/GameMode.h"
- #include "MyFPSProjectGameMode.generated.h"
-
- /**
- *
- */
- UCLASS()
- class MYFPSPROJECT_API AMyFPSProjectGameMode : public AGameMode
- {
- GENERATED_BODY()
- virtual void StartPlay() override;
- };
MyFPSProjectGameMode.cpp
- #include "MyFPSProject.h"
- #include "MyFPSProjectGameMode.h"
- #include "Engine.h"
-
-
- void AMyFPSProjectGameMode::StartPlay()
- {
- Super::BeginPlay();
-
- StartMatch();
-
- if (GEngine){
- GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("HELLO WORLD"));
- }
- }
注意,在cpp文件中添加了頭文件“Engine.h”。這段代碼很簡單,就是為了顯示一行調試信息。接下來,我們要進入到編輯器中測試一下,這里需要注意,因為我們在項目代碼中添加了函數,所以要想讓我們的代碼有效,需要執行下面步驟:
1.關閉編輯器
2.VS編譯代碼
3.重新VS F5調試打開編輯器
4.打開相應項目相應地圖。
OK,以后只要在代碼中增加了函數,就需要這樣執行。

以上兩個信息,分別說明了我們的游戲模式已經創建成功,並且正在運行。
(二)創建游戲角色,引用到游戲模式中
引擎有一個內置的類叫做DefaultPawn,他是一個Pawn物體,有一些簡單的漫游的功能。我們希望創建一個類似人類走在地上的角色,因此我們需要創建自己的Pawn。引擎專門有這樣一個類,名字叫做Character,它繼承自Pawn,但是已經包含一些移動的功能了,比如走,跑,和跳。我們將使用Character類來作為我們FPS角色的父類。
1.在UE4中選擇文件--添加代碼到項目,父類選擇Character,命名MyFPSCharacter。這里就用到之前說到的UE4的C++類向導了,官方這樣描述:使用C++類向導添加類,也可是手動在VS里面添加.h .cpp文件來創建一個新的類。但是C++類向導填寫標題和源模板建立虛幻的特定的宏,簡化了工藝,所以推薦使用類向導。
接下來看一下引擎自動生成的代碼:
MyFPSCharacter.h
- #pragma once
-
- #include "GameFramework/Character.h"
- #include "MyFPSCharacter.generated.h"
-
- UCLASS()
- class MYFPSPROJECT_API AMyFPSCharacter : public ACharacter
- {
- GENERATED_BODY()
-
- public:
- // Sets default values for this character's properties
- AMyFPSCharacter();
-
- // Called when the game starts or when spawned
- virtual void BeginPlay() override;
-
- // Called every frame
- virtual void Tick( float DeltaSeconds ) override;
-
- // Called to bind functionality to input
- virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
-
- };
MyFPSCharacter.cpp
- #include "MyFPSProject.h"
- #include "MyFPSCharacter.h"
-
-
- // 構造函數
- AMyFPSCharacter::AMyFPSCharacter()
- {
- // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
- PrimaryActorTick.bCanEverTick = true;
-
- }
-
- // 當產生式會被調用
- void AMyFPSCharacter::BeginPlay()
- {
- Super::BeginPlay();
-
- }
-
- // 每一幀都會調用
- void AMyFPSCharacter::Tick( float DeltaTime )
- {
- Super::Tick( DeltaTime );
-
- }
-
- // 綁定用戶輸入
- void AMyFPSCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
- {
- Super::SetupPlayerInputComponent(InputComponent);
-
- }
2.接下來,我們要做的就是把我們創建的這個空角色應用到剛剛的游戲模式之中。首先為了等下驗證游戲角色引用成功,同樣,我們寫一句測試的代碼,在BeginPlaye()函數里面,如下:
- // 當產生式會被調用
- void AMyFPSCharacter::BeginPlay()
- {
- Super::BeginPlay();
-
- if (GEngine){
- GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("We are using FPSCharacter!"));
- }
-
- }
3.接着,我們就要在游戲模式里面做修改了。我們的目的是
修改游戲模式里面的游戲角色
。要實現這個功能,我們要在游戲模式的構造函數中修改。打開游戲模式的cpp文件,添加如下的構造函數:
首先添加角色的頭文件:
- #include "MyFPSCharacter.h"
構造函數如下
- AMyFPSProjectGameMode::AMyFPSProjectGameMode(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP)
- {
- DefaultPawnClass = AMyFPSCharacter::StaticClass();
- }
這里要注意,為了能讓這個構造函數運行,游戲模式.h里面的GENERATED_BODY()宏命令要改成GENERATED_UCLASS_BODY()
- #pragma once
-
- #include "GameFramework/GameMode.h"
- #include "MyFPSProjectGameMode.generated.h"
-
- /**
- *
- */
- UCLASS()
- class MYFPSPROJECT_API AMyFPSProjectGameMode : public AGameMode
- {
- GENERATED_UCLASS_BODY()
- virtual void StartPlay() override;
- };
4.好了,這時候可以測試了,還是要注意之前提到的,因為我們添加了函數,要關閉編輯器,編譯代碼,重啟編輯器打開項目。
(三)綁定輸入,使得角色動起來!
首先我們綁定事件到WASD這幾個鍵上。
1. 創建映射:編輯--項目設置

觀察上圖,就會發現這里有兩種映射,一種是虛擬軸映射,一種是動作映射,其實和Unity一樣,我也框出了二者的不同,我們的動作和攝像機的控制是使用的坐標軸映射,這樣可以實現他們處理持續不間斷輸入功能,此外,動作映射為非連續性的事件處理輸入信息,比如等下我們要添加的Jump跳躍的動作映射。
上面的填表很簡單,具體步驟不做贅述,唯一一點要注意的是,兩個相反的方向值為-1.
2.添加函數:我們要為角色添加連個函數:MoveFoward()和MoveRight()兩個函數,來獲取角色的方向,並在相應方向上添加位移。
MyFPSCharacter.h
- #pragma once
-
- #include "GameFramework/Character.h"
- #include "MyFPSCharacter.generated.h"
-
- UCLASS()
- class MYFPSPROJECT_API AMyFPSCharacter : public ACharacter
- {
- GENERATED_BODY()
-
- public:
- // Sets default values for this character's properties
- AMyFPSCharacter();
-
- // Called when the game starts or when spawned
- virtual void BeginPlay() override;
-
- // Called every frame
- virtual void Tick( float DeltaSeconds ) override;
-
- // Called to bind functionality to input
- virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
-
-
- UFUNCTION()
- void MoveForward(float Val);
- UFUNCTION()
- void MoveRight(float Val);
- };
MyFPSCharacter.cpp
- void AMyFPSCharacter::MoveForward(float Value)
- {
- if ((Controller != NULL) && (Value != 0.0f))
- {
- // 找到前進的方向
- FRotator Rotation = Controller->GetControlRotation();
-
- // 限制前進或者降落時的角度
- if (CharacterMovement->IsMovingOnGround() || CharacterMovement->IsFalling())
- {
- Rotation.Pitch = 0.0f;
- }
-
- // 添加位移
- FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X);
- AddMovementInput(Direction, Value);
- }
- }
- void AMyFPSCharacter::MoveRight(float Value)
- {
- if ((Controller != NULL) && (Value != 0.0f))
- {
- // 找到前進的方向
- FRotator Rotation = Controller->GetControlRotation();
-
- const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
-
- // 添加位移
- AddMovementInput(Direction, Value);
- }
- }
這兩個函數道理都是一樣的,在經典的FPS游戲中,這里的向前和向右都是基於相機的實際方向的。所以我們首先必須從PlayerController獲得當前相機的方向。同樣,還有一個問題,即使是當我們向上或向下看的時候,也需要沿着地面移動,所以MoveForward函數要忽略相機角度的最高點並且限制我們的輸入在XY平面。
簡單來說,就是都需要這么兩步
(1)獲取方向:先找到前進方向,然后對方向做處理
FRotator Rotation = Controller->GetControlRotation();
const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
(2)在對應方向上添加位移:
AddMovementInput(Direction, Value);
3.綁定映射和函數。
接下來我們需要把剛剛創建好的映射和添加的函數綁定,很簡單,只需要在角色的SetupPlayerInputComponent設置用戶輸入組件的函數里面添加兩行即可。
- // 綁定用戶輸入
- void AMyFPSCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
- {
- Super::SetupPlayerInputComponent(InputComponent);
-
- //軸名字,xx,函數名字
- InputComponent->BindAxis("MoveForward", this, &AMyFPSCharacter::MoveForward);
- InputComponent->BindAxis("MoveRight", this, &AMyFPSCharacter::MoveRight);
-
- }
4.OK,完成了這三步,就可以測試了,注意之前說的,我們添加了函數,所以進入游戲檢測需要關閉編輯器,編譯代碼,重啟編輯器,打開工程。沒有問題!
5.繼續處理輸入,接下來,我們為鼠標創建映射,用鼠標的移動來控制視角,即控制攝像機的旋轉。同樣,還是上面的三步:
(1)創建映射,注意Y這里是-1

(2)添加函數,這里有一點要注意,Character 類已經為我們定義了兩個非常重要的函數:AddYawInput()和AddPitchInput(),也就是說我們不用自己再添加了。如果我們想要添加更多功能,比如控制鼠標靈敏度或者坐標軸的倒置,我們需要在把他們傳遞給這兩個函數之前,自己去寫函數來調整他們的值。但是,在這個案例中,我們直接把我們的輸入綁定給他們。好的,這一步跳過!
(3)綁定函數和映射
- // 綁定用戶輸入
- void AMyFPSCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
- {
- Super::SetupPlayerInputComponent(InputComponent);
-
- //軸名字,xx,函數名字
- InputComponent->BindAxis("MoveForward", this, &AMyFPSCharacter::MoveForward);
- InputComponent->BindAxis("MoveRight", this, &AMyFPSCharacter::MoveRight);
-
- InputComponent->BindAxis("Turn", this, &AMyFPSCharacter::AddControllerYawInput);
- InputComponent->BindAxis("LookUp", this, &AMyFPSCharacter::AddControllerPitchInput);
-
- }
(4)可以測試啦!這里還有一點要注意,想清楚,我們這次只是在函數中添加了幾行代碼,並沒有像之前,添加了函數,所以我們並不需要像以前一樣,為了讓新添的代碼工作,關閉編輯器,VS編譯,調試打開編輯器,打開工程這么麻煩,而是直接是用編輯器的代碼熱更新!

右下角會有編譯狀態的提示。好的編譯成功后,進入游戲測試,沒問題!
6.再添加一個功能吧:角色可以跳躍!處理過程還是一樣:
(1)添加映射

(2)添加函數
這里有一點要說明,不知道大家是否還記得之前的那篇翻譯的官方文章,里面有提到Character類的優勢,它里面其實已經為我們添加好了 一些函數,比如跳啊,走啊,跑啊之類, 查看Character.h文件,我們會發現里面內置了跳躍的函數,它綁定了bPressedJump 變量。所以我們 僅僅需要當跳躍事件被觸發時設置標志位1,當跳躍鍵被釋放時,設置為0.我們需要兩個函數來實現這些。
- UFUNCTION()
- void OnStartJump();
- UFUNCTION()
- void OnStopJump();
- void AMyFPSCharacter::OnStartJump()
- {
- bPressedJump = true;
- }
-
-
- void AMyFPSCharacter::OnStopJump()
- {
- bPressedJump = false;
- }
(3)綁定映射和函數
- InputComponent->BindAction("Jump", IE_Pressed, this, &AMyFPSCharacter::OnStartJump);
- InputComponent->BindAction("Jump", IE_Released, this, &AMyFPSCharacter::OnStopJump);
(4)測試吧!沒得問題!
(四)總結一下今天的內容
今天的內容就到這里,不急着更新,先一起總結一下今天做了哪些事情吧:
1.使用空模板,創建空白C++工程。
2.創建了一個簡單的游戲模式,到目前為止這個游戲模式僅僅只完成了引用游戲角色的任務。
3.創建了一個簡單的角色,他可以完成一些動作,但是還沒有網格。這部分內容會在下一篇文章中說明。
4.處理了輸入。輸入處理很簡單,就是簡單的三步:
(1)創建映射
(2)創建函數
(3)綁定映射和函數
我們總共用UE4的類向導創建了兩個類:MyFPSGameMode和MyFPSCharacter,這里還沒有涉及到藍圖的內容,C++和藍圖將會在下一篇文章 中做總結,並且為角色添加網格等更多功能。
最后說明,博客的主人俺只是一個大三的學生,這些是我花費了自己大量的課余時間整理的,制作分享,不會考慮任何商業因素,如果大家喜歡,歡迎大家支持,如果文中有錯誤,還望大家指出,我們一同學習一同進步。如需轉載,請注明作者,注明CSDN本文鏈接 http://blog.csdn.net/u011707076/article/details/44180951