UE4 C++程式設計基礎記錄
Hello ,I am Edwin
首先謝謝大家的支援,其次如果你碰到什麼其他問題的話,歡迎來 我自己的一個 討論群559666429
來,大家一起找答案,共同進步
一:前言
有很多人是從UE3 接觸到Unreal,如果你也對UE3非常瞭解,便能很快的上手UE4。但是,UE4的開發模式還是有所不同的。
1. 談談過往,UE1和UE2.
我知道在那個時候咱們一樣揮霍美好的童年在玩遊戲而不是做遊戲,當然做遊戲更揮霍你的青春XD
UE1和UE2是為FPS設計的,使用UnrealScript來進行程式設計。說實話UnrealScript是最好的學習面向物件程式設計的語言。
2.UE3
添加了kismet。更多的模組類。但是仍舊以FPS為核心設計的
3.UE4
UnrealScript被下崗了,恭喜你咱們下崗了~。變成了Blueprint,這時候關卡設計師們大聲叫好:”不再需要求著愚蠢的程式設計師幫我們實現系統了,哈哈“。
遊戲型別隨意,Paper2D讓你輕鬆製作2維遊戲。
還有許多工程例項供你學習。
好了咱們向前看~
5.UnrealScript vs.C++ vs.Blueprint
這是許多人的疑問,看好戲吧
6.UnrealScript
①面向物件程式設計,和C,C++,Java比較像。但是還是有區別的
②虛擬機器編譯,這和java一樣,方便移植,缺點是速度慢效率低
③添加了許多有用的特性,State(說實話狀態寫AI簡直是最完美的方案,我的遊戲AI便是強依賴State).Timers(非常方便延遲執行),Delegates(容易引起崩潰,注意使用哦)
7.Blueprint
①對藝術家和設計師來說就是逃離噁心程式設計師”這不可能實現“的最有力武器,你可以做到”你行你上啊~“。Kismet的夥計應該很容易上手
②還是虛擬機器,和UnrealScript是一樣的。因此,如果你很重視一個環節的執行效率,那麼就是用C++
③和UnrealScript一樣,但一些方面比UnrealScript強。例如你在新增Component中時可以不用關閉編輯器直接修改模型。實在是工農大解放啊…
8.C++
①UE程式設計始終可以基於C++,除非你用UDK,你這個窮鬼XD
②緊密結合虛擬機器,因為Blueprint變數和方法有時是需要和C++互動的
③為了替換UnrealScript為開發者已經大幅提升了?
9.UE4基礎元素
①Actor
我們又見面了Actor,Actor是在一個關卡中持續存在的,通常他包含幾個Actor元件。支援網路複製和多人遊戲。
Actor不包含位置,方向。這些東西在Root Component中儲存。對於UE3 中的Pawn也由PlayerCharacter繼承了,因為他有MovementComponent包含跳躍,速度等屬性
由SpawnActor() 生成
必須由Destroy來銷燬
在遊戲中不能被垃圾回收
②什麼是ActorComponent?
能被複用的功能可以新增進入Actor
包含一些最有趣的函式和事件
能被Blueprint訪問~
③元件例子
Scene Component 新增形狀和連線
Primitive Component 新增碰撞和渲染
UAudioComponent,UArrowComponent,UInputComponent,ULightComponent,UMeshComponent,UParticleSystemComponent等等…
寫過UnrealScript會對元件深有體會
④PrimitiveComponent元件事件舉例
Hit- 再碰到強的時候呼叫
Begin/EndOverlap -進出一個Trigger
Begin/EndCursorOver 沒用過
Clicked/Released 不解釋
InputTouchBegin/End
Begin/EndTouchOver
10.你就只認得個Pawn
Pawn就是你的阿凡達
這個傀儡被Controller操縱著
通常處理運動和輸入控制
實現HP的好位置
通常沒有運動或是輸入反饋的程式碼,你可以在Controller中寫
11.來見見老朋友Controller
控制Pawn的傀儡師
可以理解做就是玩家
AIController就是控制AI的
一次只能Possess一個Pawn
當Pawn死的時候可以繼續存在,在我的遊戲中讓pawn死亡,遊戲假重置,就是controller直接Possess一個新Pawn的
PlayerControlelr : 玩家阿凡達的介面
處理點選,手柄,鍵盤
顯示和隱藏滑鼠指標
不需要Pawn表示的好地方
選單,語音聊天…
一些別的有用選項
12.Character
這是使用過UE3的人學習UE4最困惑的地方,有了Pawn為什麼還要擴充套件PlayerCharacter?
這是一種能走的Pawn,因為包含了MovementComponent和上邊一樣,添加了一些有用的元件
處理碰撞
處理客戶端角色運動
比UE3有更大的升級
13.HUD
還有一些繪製API
沒有構建HUD的工具
UMG直接提供所有的東西,我討厭老舊UI系統。UMG讓人解放了
14.GameMode
還記得GameInfo嗎,實現遊戲規則用的
配置Pawn,Controller和HUD是誰
能在任何地方被訪問,我來告訴你API GetGameMode()
只在伺服器和單機例項中存在
GameState是被用來在客戶端複製狀態用的
預設遊戲型別可以在Project Settings中被設定
每個地圖都有自己的GameMode,需要你親手設定
15.Input
這是我對UE4最好的改進之一,你直接在編輯器中新增Bind而不是讓你眼花的配置檔案中
在Project Settings中設定去吧
獲取指令
PlayerController
Level Blueprint
Possessed Pawn
16.碰撞
有多種碰撞處理的方法
線性檢測 Line Trace
體積掃過 Geometry Sweeps
重疊測試 Overlap tests
簡單的碰撞
Box,膠囊,球,多邊形
在運動和物理模擬的時候都需要
複雜碰撞
實際用於多邊形
武器和反向動力
二:程式碼基礎知識
我記性不怎麼好。在學習UE4的碰到一些新的知識,記錄在這裡,算是學習筆記。都是寫基礎的,但是頻繁使用的語句
在這裡再發個牢騷就是。UE4比起U3D ,那簡直規矩太多了,他好像什麼都想給客戶弄好,但是最後弄的臃腫繁瑣。記不住的就很容易犯錯。這難道就是U3D 能火的原因之一。就像中文和英文,中文每個字差不多就代表一種具象或者抽象的事物,合起來有可以代表其他各種事物,但是這樣下來就有好多好多字得記,真佩服古代造字人的智慧,比起智慧中文完勝英文。但是比起易學,易用!有可數數量的字母,相互組合,就能拼接成各種單詞。就像給了你基本的框架,你用這些搭建就好了。這樣在 學習還有傳播 方便 佔據 優勢。所以,UE4像不像中文?博大而精神,高深而智慧? U3D 像不像 英文? 極簡歸一,實用易懂?
類命名字首
虛幻引擎為您提供在構建過程中生成程式碼的工具。這些工具擁有一些類命名規則。如命名與規則不符,將觸發警告或錯誤。下方的類字首列表說明了命名的規則。
- 派生自 Actor 的類字首為
A
,如 AController。 - 派生自 物件 的類字首為
U
,如 UComponent。 - 列舉 的字首為
E
,如 EFortificationType。 - 介面 類的字首通常為
I
,如 IAbilitySystemInterface。 - 模板 類的字首為
T
,如 TArray。 - 派生自 SWidget(Slate UI)的類字首為
S
,如 SButton。 - 其餘類的字首均為 字母
F
,如 FVector。
資料型別
數字型別
因為不同平臺基礎型別的尺寸不同,如 short、int 和 long,UE4 提供了以下型別,可用作替代品:int8/uint8 :8 位帶符號/不帶符號 整數
int16/uint16 :16 位帶符號/不帶符號 整數
int32/uint32 :32 位帶符號/不帶符號 整數
int64/uint64 :64 位帶符號/不帶符號整數
標準 浮點 (32-bit) 和 雙倍(64-bit)型別也支援浮點數。虛幻引擎擁有一個模板 TNumericLimits,用於找到數值型別支援的最小和最大範圍。如需瞭解詳情,請查閱此 連結 。
字串
FString 是一個可變字串,類似於 std::string。FString 擁有許多方法,便於簡單地使用字串。使用 TEXT() 巨集可新建一個 FString:
FString MyStr = TEXT("Hello, Unreal 4!")
FText
FText 與 FString 相似,但用於本地化文字。使用 NSLOCTEXT 巨集可新建一個 FText。此巨集擁有預設語言的名稱空間、鍵和一個數值。
FText MyText = NSLOCTEXT("Game UI", "Health Warning Message", "Low Health!")
也可使用 LOCTEXT 巨集,只需要在每個檔案上定義一次名稱空間。確保在檔案底層取消它的定義
// 在 GameUI.cpp 中 #define LOCTEXT_NAMESPACE "Game UI" //... FText MyText = LOCTEXT("Health Warning Message", "Low Health!") //... #undef LOCTEXT_NAMESPACE // 檔案末端
FName
FName 將經常反覆出現的字串儲存為辨識符,以便在對比時節約記憶體和 CPU 時間。FName 不會在引用完整字串的每個物件間對其進行多次儲存,而是使用一個對映到給定字串的較小儲存空間 索引。這會單次儲存字串內容,在字串用於多個物件之間時節約記憶體。檢查 NameA.Index 是否等於 NameB.Index 可對兩個字串進行快速對比,避免對字串中每個字元進行相等性檢查。TCHAR
TCHARs 用於儲存不受正在使用的字符集約束的字元。平臺不同,它們也可能存在不同。UE4 字串在後臺使用 TCHAR 陣列將資料儲存在 UTF-16 編碼中。使用返回 TCHAR 的過載解引用運算子可以訪問原始資料。
- 容器
類 | 說明 | 對應U3D 的 |
---|---|---|
常用的主要容器。作用與std::vector 類似 |
List | |
TMap | 鍵值對的合集,與 std::map 相似 | Dictionary |
TSet | 儲存唯一值的合集,與 std::set 相似 | HashSet |
虛幻C++部分程式碼解釋
C++和 藍圖
虛幻提供兩種建立遊戲:C++ 和藍圖。程式設計師可以通過C++ 來新增基礎遊戲性體統。設計師(美術人員)即可在這個系統上建立關卡或者遊戲的自定義遊戲性。簡單來說,程式設計師編寫的C++是一個遊戲的主要關鍵功能的構建方。而設計師所掌握的藍圖,則起到遊戲功能的輔助作用。類嚮導
而在建立一個UE4C++物件的時候。類中的會有一下結構。
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
// 設定該 actor 屬性的預設值
AMyActor();
// 遊戲開始時或生成時呼叫
virtual void BeginPlay() override;
// 每幀呼叫
virtual void Tick( float DeltaSeconds ) override;
};
欄位 | 作用 |
---|---|
BeginPlay() | 和U3D 的 Start()方法一樣。在進入遊戲狀態的時候執行 |
Tick() | 沒幀呼叫和U3D 的 Update() 一樣。如果不需要此功能。必須在建構函式中說明。如下程式碼 |
AMyActor::AMyActor()
{
// 將此 actor 設為每幀呼叫 Tick()。不需要時可將bCanEverTick設定為false以關閉此功能,來提高效能。
PrimaryActorTick.bCanEverTick = true;
}
- 使屬性出現在編輯器中
類建立好之後。現在可以建立一些屬(設計師可是虛幻編輯器中設定這些屬性)。可以使用 特殊巨集UPROPERTY()
來輕鬆的將屬性公開至編輯器中可視。比如:UPROPERTY(EditAnywhere)
巨集即可。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
int32 TotalDamage;
...
};
之後。你就可以在編輯器中看到變數TotalDamage對應的數值。並且可以對數值進行調節。
除此之外還可以加入其他的屬性。
屬性 | 作用 | 用法 |
---|---|---|
EditAnywhere | 使這個變量出現在編輯器中可編輯 | EditAnywhere |
Category | 使此屬性與相關屬性出現在一個部分 | Category=”Damage” |
BlueprintReadWrite | 使屬性為可讀取和可編寫狀態 | BlueprintReadWrite |
BlueprintReadOnly | 使屬性為可只讀取狀態 | BlueprintReadOnly |
VisibleAnywhere | 意味著屬性在虛幻編輯器中為可見狀態,但是不可編輯 | VisibleAnywhere |
Transient | 意味著無法從硬碟對其進行儲存或載入 | Transient |
除此之外,還可以通過巨集定義來是一些屬性在其關聯屬性被更改的時候也做出相應的改變:
void AMyActor::PostInitProperties()
{
Super::PostInitProperties();
CalculateValues();
}
void AMyActor::CalculateValues()
{
DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}
//通過WITH_EDITOR定義使這個函式的目標物件在編輯器中被更改時引擎將通知和執行這個函式。
#if WITH_EDITOR
void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
CalculateValues();
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
- 藍圖呼叫C++中的函式
上面說了如果對藍圖公開屬性。那函式呢?對呀函式我們又得需要另一個巨集來定義。
UFUNCTION(BlueprintCallable, Category="Damage")
void CalculateValues();
如上。 可以通過 UFUNCTION
巨集來把C++函式對反射系統公開。 而 BlueprintCallable將其對藍圖虛擬機器公開。這樣在在藍圖中,右鍵點選快捷鍵選單就可以使用這個函式。
- AActor簡單介紹
AActor 這個 類,被作為遊戲場景中一個基本的物件。所以可以放在關卡場景中的物件都可延展自這個類,比如:AStaticMeshActor、ACameraActor 和 APointLight 等等 actor。
這個類派生在 UObject。
AActor 的生命週期為:
函式 | Are |
---|---|
BeginPlay() | 和U3D 的 Start()方法一樣。在進入遊戲狀態的時候執行 |
Tick() | 每幀呼叫和U3D 的 Update() 一樣。如果不需要此功能。必須在建構函式中說明。 |
EndPlay | 物件離開遊戲程序時呼叫 |
-
再用UE4 C++ 程式設計的時候。同會看到好多非C++ 語句的欄位。例如,他會在一個類上加上UCLASS()
等等。這些都是UE4使用其自身的反射實現,來啟動動態功能,比如,垃圾回收,序列化,網路複製和藍圖/C++通訊。這些功能你可以根據需求選擇的加入,只要為相應的型別新增正確的標記就可生成反射資料。 一下是一些基礎標記:
標記 | 說明 |
---|---|
UCLASS() | 告知虛幻引擎生成類的反射資料。類必須派生自 UObject。 |
USTRUCT() | 告知虛幻引擎生成結構體的反射資料。 |
UE4 使用它替代為型別生成的所有必需樣板檔案程式碼。就是說這個類不直接使用父類的宣告。但是,你必須得去實現,自己去宣告,否則就會報錯。更多資訊 | |
UPROPERTY() | 使 UCLASS 或 USTRUCT 的成員變數可用作 UPROPERTY。UPROPERTY 用途廣泛。它允許變數被複制、被序列化,並可從藍圖中進行訪問。垃圾回收器還使用它們來追蹤對 UObject 的引用數。 |
UFUNCTION() | 使 UCLASS 或 USTRUCT 的類方法可用作 UFUNCTION。UFUNCTION 允許類方法從藍圖中被呼叫,並在其他資源中用作 RPC |
eg:
#include "MyObject.generated.h" //這個標頭檔案包含虛幻引擎所有反射資料。必須在宣告型別的標頭檔案中將此檔案作為最後的 include 包含。
UCLASS(Blueprintable)
class UMyObject : public UObject
{
GENERATED_BODY()
public:
MyUObject();
UPROPERTY(BlueprintReadOnly, EditAnywhere)
float ExampleProperty;
UFUNCTION(BlueprintCallable)
void ExampleFunction();
};
您還會注意到,可以在標記上新增額外的說明符。此處已新增部分常用說明符用於展示。通過說明符可對型別擁有的特定行為進行說明。
Blueprintable - 此類可由藍圖延展。
BlueprintReadOnly - 此屬性只可從藍圖讀取,不可寫入。
Category - 定義此屬性出現在編輯器 Details 檢視下的部分。用於組織。
BlueprintCallable - 可從藍圖呼叫此函式。
說明符太多,無法一一列舉於此,以下連結可用作參考:
- 物件/Actor 迭代器
就是 對應 U3D 的 for 迴圈。先查詢關卡中所以的派生自UObject的 類的例項。然後遍歷他們。
// 將找到當前所有的 UObjects(此處可以寫入你自定義的類的類名) 例項
for (TObjectIterator<UObject> It; It; ++It)
{
UObject* CurrentObject = *It;
UE_LOG(LogTemp, Log, TEXT("Found UObject named:%s"), *CurrentObject.GetName());
}
但是這個在PIE(Play In Editor)
中使用的話可能產生意外後果。 因為編輯器已被載入,除編輯器正在使用的物件外,物件迭代器還將返回為遊戲世界例項建立的全部 UObject。
而 Actor 迭代器 呢還和 物件迭代器的使用不一樣,但套路就那麼多。
APlayerController* MyPC = GetMyPlayerControllerFromSomewhere();
UWorld* World = MyPC->GetWorld();
// 和物件迭代器一樣,您可提供一個特定類,只獲取為該類的物件
// 或從該類派生的物件
for (TActorIterator<AEnemy> It(World); It; ++It)
{
// ...
}
這個迭代只能用於派生自 AActor 物件。
建立 actor 迭代器時,需要為其賦予一個指向 UWorld 例項的指標。許多 UObject 類(如 APlayerController)會提供 GetWorld 方法,助您一臂之力。如不確定,可在 UObject 上檢查 ImplementsGetWorld 方法,確認其是否應用 GetWorld 方法。
- Actors 和垃圾回收
Actor 通常不會被垃圾自動回收。他在生成之後,必須使用Destroy() 來刪除。而且他們不會立刻刪除,會在垃圾回收階段被清理。
常見情況下 actors 帶有 Uobject 屬性。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY()
MyGCType* SafeObject;
MyGCType* DoomedObject;
AMyActor(const FObjectInitializer& ObjectInitializer)
:Super(ObjectInitializer)
{
// 建立兩個物件。他們自動成為根集的一部分。
// 這個指定了UPROPERTY。SafeObjet將不會被垃圾回收,因為他從根集物件出到達。
SafeObject = NewObject<MyGCType>();
//DoomedObject 因為未將標記為 UPROPERTY,因此回收器並不知道其正在被引用,而會將它逐漸銷燬。
DoomedObject = NewObject<MyGCType>();
}
};
void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
World->SpawnActor<AMyActor>(Location, Rotation);
}
由上面程式碼。可知,UObject 被垃圾回收時,對其的所有 UPROPERTY 引用將被設為 nullptr。那麼 上面的 屬性 SafeObject 將會為 nullptr。 所以為了安全的檢測一個物件是否被垃圾回收。我們應該在使用的時候進行判斷:
if (MyActor->SafeObject != nullptr)
{
// 使用 SafeObject
}