【導圖】:

【前言】:
奈何水準有限,本篇有多處扯淡的環節,謹慎參考。
【1.】:視訊教程
迪迪老濕哪——UE4純C++與Slate開發沙盒遊戲ke.qq.com
【2.】:UE4 C++基礎系列
- 【UE4】C++基礎【00】——通俗易懂 用藍圖來學習 C++ 基礎知識
- 【UE4】C++基礎【01】——預設代碼扒拉拉(初學者向)
- 【UE4】C++基礎【02】——添加Slate至視窗
【3.】:UI實作原理:
【4.】:常混淆關鍵詞梳理
常混淆的關鍵詞,Slate、UMG、UI、HUD、Widget,前言中先梳理一下。
- Slate ——
- 定義——Slate是完全自定義、與平台無關的使用者界面架構。簡而言之, Slate是跨平台的UI架構 。
- 功能實作——
- 可以用來做應用程式Application的UI(如UE4 Editor 獨立exe可執行程式)
- 工具架Toolbar的UI
- 更可以做遊戲當中的UI (Slate代碼建立 VS UMG可視化建立)
- UMG(Unreal Motion Graphics UI Designer) ——它是 工具 ,一個可視化的UI建立工具,就是WidgetBlueprint的那個Designer環境(Designer負責可視化、Graph負責控件事件定義)
- 作用——可用來建立UI元素,如HUD、菜單等
- 核心——Widgets控件
- UI(UserInterface) ——使用者界面
- 含義——菜單和其他互動元素,像Menu主菜單、Pause暫停菜單、NPC頭頂的對話框等。
- HUD(Head Up Display) ——
- 典故——來源于飛機飛行員,指擡頭就能看到儀表盤上飛機的飛行資料。
- 含義——遊戲期間(Runtime)在螢幕上覆寫的狀态和資訊
- 目的——告知玩家目前遊戲狀态,如分數、生命值、遊戲時間等。
- 定義——HUD通常不可互動,意味着玩家不能單擊HUD的元素
- Widget ——控件
- 含義 ——它們是一系列預先制作的函數
- 作用 ——可用于建構界面(如Button按鈕、Slider滑塊、ProgressBar進度條、Vertical/Horizontal Box 水準/豎直框。
- 大環境 ——這些Widget控件在專門的控件藍圖(UserInterface-> WidgetBlueprint )中編輯。它使用兩個頁籤進行構造
- Designer ——控件可視化布局,位置、旋轉、縮放、父子等
- Graph ——實作控件背後的功能,如細節面闆中的Events事件,像Button的OnClicked、Hovered事件等。
【5.】:小貼士
我感覺初學者學C++就是個先不斷找尋
對應英文語句和各種符号的意思的過程,跟閱讀了解差不多。
我覺得學代碼應該分為這些階段:
- 新手
- Happiness——知道這麼寫能不報錯,哈哈哈哈,有一定的獎勵回報以支撐頭發繼續戰鬥下去。(What?)
- Effect——簡單知道這麼寫能起什麼作用(How?)
- 高手
- Benifit——深入了解引擎為什麼要這麼寫,有什麼好處。(Why?)
在學習期間肯定有很多不會的地方,可以按以下方式查詢。
- VS中滑鼠懸浮語句出Tooltip提示框快速檢視對應意思。
2.網頁端虛幻官方C++ API Reference (或像上面滑鼠Hover的時候,點選SearchOnline,會自動用Bing搜尋關鍵詞,跟内容浏覽器資源右鍵View Documentation/See Full Documentation差不多)
3.深入了解的話,就右鍵PeekDefinations在目前文檔内出小視窗快速浏覽或 GoToDefinition到源檔案檢視相應代碼。
一、 預備工作
【1.1】建立基礎C++類(GameMode、PlayerController、HUD)
- 建立地圖—— Content->Map->建立
MenuMap
- 設定項目預設地圖——Project Settings ->Maps & Modes 設定
為項目預設地圖MenuMap
- 建立C++ GameModeBase 為
ShitGameMode
- 確定 Public—— 頭檔案public、源檔案private
- Public路徑後添加
以将這個/GamePlay
放在 GamePlay檔案夾下,規整一點(如果報錯不能生成檔案,後期VS處理)ShitGameMode
4. 建立 PlayerController 為
ShitController
,同樣Public
5. 建立 HUD (跟UI一樣放在螢幕上,但是不用手動AddToViewport)為
ShitHUD
,同樣為Public。
- GameController
- GameMode
- HUD
如若出現報錯,請按照提示在cpp中添加檔案路徑字首:UE4添加C++類失敗 如何解決?
【1.2】世界設定GameMode為新建立自定義的GameMode類
- ShitGameMode
二、VS代碼編輯器配置GameMode
【2.1】給ProjectName.Build.cs添加Slate、SlateCore子產品
當然内部原理更複雜,我等菜鳥簡單了解一下具體的功能即可,後期深入再了解原理。
- Unreal Build Tool (UBT)——負責 各個子產品的編譯 并處理 各子產品之間的依賴關系,Build.cs和Target.cs就是服務于UBT的。Build.cs中包含了定義的子產品的依賴關系、額外的庫、路徑等資訊,這些檔案被編譯成dll動态連結庫檔案,并通過單一的exe可執行檔案進行加載。
- Unreal Header Tool (UHT)——C++代碼解析工具
- 搜集帶U的,如UCLASS()、UPROPERTY() 生成反射資料并注入到 GENERATED_BODY()中。
籃子悠悠:UE4 C++基礎教程 - 工程目錄結構zhuanlan.zhihu.com
如果我們要使用Slate UI 我們隻需要Uncomment取消注解就可以了。
新添加格式為字元串,名字為Slate、SlateCore,到 私有從屬組塊名中。
PrivateSlate語句行,
Ctrl+K,Ctrl+U(Uncomment)(Ctrl+C(comment))
PrivateDependencyModuleNames.AddRange(new string[] {"Slate"、"SlateCore"});
【2.2】使用VAssistX插件在Gamemode.h頭檔案中新Declare構造函數
- VAssistX
- 快速追蹤,
- Alt+G ——Declaration和Definition的快速跳轉;
- Alt+O ——cpp 和 h 檔案快速切換
- 快速建立,添加成員變量的Set/Get方法,如構造函數,CreateImplementation
- Alt+C (自定義)——CreateImplementation 快速建立實作,直接在頭檔案函數聲明(Declaration)處快捷鍵,然後就跳轉到了源檔案中,并自動寫好函數實作語句(Definition)。
- 快速追蹤,
【2.3】 GENERATED_BODY()
和 GENERATED_UCLASS_BODY()
的差別
GENERATED_BODY()
GENERATED_UCLASS_BODY()
-
_Body()
- 辨別的類的成員預設是Private的
- 辨別的類 需要聲明 無參數的構造函數 (Public:如AxxxActor)
- _
UCLASS_Body()
- 可以不聲明構造函數(如果要實作構造函數需要加上
參數 (ObjectInitializer,對象構造器/對象初始化操作)const FObjectInitializer&ObjectInitializer:Super(ObjectInitializer)
- 可以不聲明構造函數(如果要實作構造函數需要加上
我們之前學過Super()的作用就是調用這個函數之前會先調用父類中的函數。
預設的是GENERATED_BODY()
,本節隻是簡單了解一下二者差別。 【2.4】GameMode源檔案中#include HUD和Controller的頭檔案
- 我們之前說過h是上司,外面接活,就是#include一堆引擎的東西,啥架構、反射生成啦這些玩意兒。
- cpp是員工,給上司幹活,#include一堆我們自己建立的上司。
如Gamemode cpp員工要#include自身Gamemode上司,還有其他兩個上司。注意路徑,Gameplay或UI檔案夾,否則 #include下面一堆姨媽紅。
//ShitGameMode.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "GamePlay/ShitGameMode.h"
#include "GamePlay/ShitController.h"
#include "UI/HUD/ShitHUD.h"
AShitGameMode::AShitGameMode()
{
}
【2.5】GameMode cpp 源檔案中 補充構造函數定義,細化GameMode配置
說明:
-
作用域限定符(範圍解析運算符) 。類外實作函數定義 (如藍圖學習C++ 【8.1】中類成員函數的類内類外定義 和【8.2】中構造函數的聲明與定義)::
- StaticClass() 是UE4引擎自己通過反射建立的檔案下面的一個函數,運作時可以傳回這個類的UClass對象。
- 文法:類名Class=繼承類名::StaticClass();
- 如 HUDClass=AShitHUD::StaticClass();
代碼中的
=
等号其實就跟在編輯器ComboBox中選枚舉一樣,像完善GameMode詳細參數,如HUD、Controller這些。我們在代碼中直接寫死,不讓在編輯器中枚舉選了。
// ShitGameMode.cpp
#include "GamePlay/ShitGameMode.h"
#include "GamePlay/ShitController.h"
#include "UI/HUD/ShitHUD.h"
AShitGameMode::AShitGameMode()
{
PlayerControllerClass = AShitController::StaticClass();
HUDClass = AShitHUD::StaticClass();
}
【2.6】Build檢驗
編輯器中看到在VS中定義的GameMode的屬性都傳過來了,且不可改動
三、VS代碼編輯器配置PlayerController
【3.1】ShitController 構造函數定義聲明
- 來到ShitController 頭檔案。
- 跟GameMode一樣頭檔案中public聲明構造函數
- 再滑鼠懸浮構造函數處 Alt+C通過VAssistX插件在cpp中快速定義構造函數基本文法,即CreateImplementation建立聲明的實作。
// ShitController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ShitController.generated.h"
/**
*
*/
UCLASS()
class TESTGAME_API AShitController : public APlayerController
{
GENERATED_BODY()
public:AShitController(); //在此Alt+C
};
//ShitController.cpp
#include "GamePlay/ShitController.h"
AShitController::AShitController() //Alt+C 結果
{
}
【3.2】ShitController中詳細定義函數
【1】
bShowMouseCursor=true;
顯示滑鼠
- 藍圖一般是關卡藍圖中getcontroller、ShowMouse變量、打勾不打勾 或PlayerController藍圖中勾選顯示滑鼠。( 構造函數中寫的這個就跟Controller藍圖中Class Settings勾選是一樣的)
- C++直接b布爾類型變量、ShowMouse變量、=True/False 是否啟用
【2】設定隻接受滑鼠輸入并将滑鼠鎖在視窗内
- 頭檔案中跟預設代碼一樣聲明 BeginPlay初始函數 虛函數——
virtual void BeginPlay() override;
- 一樣Alt+C CreateImplementation建立函數聲明。
- 源檔案中定義鎖定滑鼠在視窗内
- FInputModeUIOnly—— 用于設定僅允許UI響應使用者輸入的輸入模式的資料結構
// Fill out your copyright notice in the Description page of Project Settings.
#include "GamePlay/ShitController.h"
AShitController::AShitController()
{
bShowMouseCursor = true;
}
void AShitController::BeginPlay()
{
FInputModeUIOnly InputMode;
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockAlways);
SetInputMode(InputMode);
}
【3.3】Build檢驗
- VS Game Build
- Hot 熱更新過來可以看到定義的Class都過來了
然後Standalone Game獨立遊戲模式 Alt+P 運作檢驗結果:
看到滑鼠是顯示出來的且卡在視窗内。四、VS代碼編輯器配置SlateWidget
【4.1】建立兩個SlateWidget
【操作】:- 需要注意的是SlateWidget并不會在ContentBrowser中出現,包括檔案夾,它隻會在VS中出現。(我還各種Build、Rebuild、重新開機測試,結果是真的沒有, 大家别費那個勁兒在Content Browser中找了 )
- UE4 C++基礎 篇中我們講到過UE4采用Pascal命名法,S字首代表SlateWidget。比如我們藍圖建立了一個名為ShitWidget的SlateWidget,VS中會變成SSlateWidget.h/.cpp。
- HUD是不帶S字首的,SlateWidget才帶S字首,注意一下。
【4.2】HUD基礎代碼配置
- 跟上面GameMode和Controller一樣,在HUD 頭檔案中同樣public聲明,Alt+C源檔案定義構造函數。
- ShitHUD cpp源檔案中#include 引用
- SShitMenuHUDWidget.h頭檔案。
- SlateBasics.h頭檔案。(否則會報錯)
// ShitHUD.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "ShitHUD.generated.h"
/**
*
*/
UCLASS()
class TESTGAME_API AShitHUD : public AHUD
{
GENERATED_BODY()
public:
AShitHUD(); //聲明構造函數
}
//ShitHUD.cpp
#include "UI/HUD/ShitHUD.h"
#include "UI/Widget/SShitMenuHUDWidget.h"
#include "SlateBasics.h"
AShitHUD::AShitHUD()
{ };
【4.3】綁定Widget至HUD并添加到GameViewport遊戲視窗中。
參考官方文檔——在遊戲中使用Slate
【1.】:頭檔案聲明指針
// ShitHUD.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "ShitHUD.generated.h"
/**
*
*/
UCLASS()
class TESTGAME_API AShitHUD : public AHUD
{
GENERATED_BODY()
public:
AShitHUD();
TSharedPtr<class SShitMenuHUDWidget>MenuHUDWidget;
};
上面的
TSharedPtr<class SShitMenuHUDWidget>MenuHUDWidget;
跟下面的藍圖差不多。
- TSharedPtr 聲明共享指針。
- 指針類為
(用UMG建立的自定義UI,繼承自UserWidget)ShitMenuHUDWidget
- 指針名為
MenuHUDWidget
- TSharedPtr——等同于在藍圖細節面闆中的變量欄建立一個變量(指針變量)
- ToSharedRef()——将指針指的Class類調用過來。
源檔案定義指針
// ShitHUD.cpp
#include "UI/HUD/ShitHUD.h"
#include "UI/Widget/SShitMenuHUDWidget.h"
#include "SlateBasics.h"
AShitHUD::AShitHUD()
{
if (GEngine && GEngine->GameViewport)
{
SAssignNew(MenuHUDWidget, SShitMenuHUDWidget);
GEngine->GameViewport->AddViewportWidgetContent(
SNew(SWeakWidget).PossiblyNullContent(MenuHUDWidget.ToSharedRef())
);
}
}
【解釋說明】: - 【1】if語句
- && 邏輯與運算符,兩個表達式必須都為true,整個表達式才為true
- 判斷GEngine和GameViewport是否存在,存在則執行括号中的語句
- 【2】建立SlateWidget語句
- SAssignNew為SlateWidget的一種建立方式,要 搭配頭檔案中聲明的指針 一起才管用。
TSharedPtr<class SShitMenuHUDWidget>MenuHUDWidget;
SAssignNew(MenuHUDWidget, SShitMenuHUDWidget);
兩種建立方式的差別是:
- SNew(xxx)
- SAssign(指針,SlateWidget類名)
SNew和SAssignNew
- 【 3】添加到遊戲視窗語句
GEngine->GameViewport->AddViewportWidgetContent(
SNew(SWeakWidget).PossiblyNullContent(MenuHUDWidget.ToSharedRef())
);
-
添加到遊戲視窗(預設代碼 中我們還學過GEngine也是可以在螢幕上Print String列印資訊)GEngine->GameViewport->AddViewportWidgetContent();
- 為了在遊戲中顯示一個Slate控件,就必須把這個控件添加到遊戲視口(Viewport)中。
- 重疊的控件會按照Z-Order索引進行排序,大的數置頂在上面,小的數在下面,跟PS的圖層一樣。
- 遊戲視口GameViewport是GameViewportClient的一個執行個體,可通過使用到遊戲目前 U Engine執行個體的“ G Engine”(Game Engine?)指針通路——
GEngine->GameViewport
- GameViewport的AddViewportWidgetContent函數可以将控件添加到視口中。
SNew(SWeakWidget).PossiblyNullContent(MenuHUDWidget.ToSharedRef());
将上面通過SAssignNew建立的SlateWidget添加到視口中 【實作流程】: - PossiblyNullContent(MenuHUDWidget.ToSharedRef()——如果這個指針指向的widget類存在,那就
- SNew(SWeakWidget)——把它傳到這個新建立的SWeakWidget SlateWidget中
- GEngine->GameViewport->AddViewportWidgetContent(....),将SWeakWidget添加到螢幕上。
- SWeakWidget 是什麼鬼?——實作一個小部件,該小部件持有指向一個子小部件的弱指針(Weak Pointer)。它封裝一段内容而不擁有它。 可以了解為一種綠綠的暧昧的關系。
- Tooltip=A,女一号
- Hovered Widget =B,男一号。
- Floating Window=C,男二号。
(哈哈,就好比男一号心裡有女一号,然而女一号要通過跟男二号潛規則才能上位亮相,但是男二号心裡并沒有女一号。那是因為男二号還有很多可潛規則的對象。)
- 項目編輯器中 ——
- 我們在項目編輯器中建立的是 兩個特定SlateWidget類 ,這裡SlateWidgets就代表添加到螢幕上面的那些UserWidget(藍圖)。
- VS中 ——
- 而我們在HUD中通過
建立SlateWidgets,指定我們建立的SlateWidgets以填充至螢幕中。SNew()或SAssignNew()
- 在 兩個特定SlateWidgets類 中我們同樣也可以通過
來建立SNew()或SAssignNew()
元件,以在Widgets中添加Button按鈕。SButton
- 而我們在HUD中通過
【4.4】: 解決小Bug
虛幻的Bug真神奇:
- 有這種白色棱形問号的,不用思考,肯定是中文的問題,系統語言更改真的有時候能夠解決很多問題的,比如微信定位位址,電腦系統英文會預設用Google打開,中文會預設用騰訊地圖打開。 系統語言改為英文後,就到了第二個編号了,明顯少了一個bug。
- 第二個它提示不能夠删除dll動态連結庫,直接關閉VS,然後在ContentBrowser中随便打開一個Class,然後再Build,就好了,編譯成功!虛幻就是這麼地神奇!
【4.5】SlateWidget中引入SButton元件
ShitMenuHUDWidget 源檔案中新引入
SButton.h"
,添加SButton元件。
- [ ]中括号是重載運算符
- ChildSlot就是槽,可以放子控件。
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/Widget/SShitMenuHUDWidget.h"
#include "SButton.h"
#include "SlateOptMacros.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SShitMenuHUDWidget::Construct(const FArguments& InArgs)
{
ChildSlot
[
SNew(SButton)// 建立一個Button Slate
];
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
注意一下這個路徑的問題,開頭我是直接 在HUD cpp源檔案中
#include"SButton.h"
,後來一直報錯,結果檢查到還是路徑的問題呢。個人覺得Build.cs中引入子產品名其實就跟檔案夾定位的意思差不多,Build.cs定一次,一勞永逸,省得在自定義類的源檔案中再多次#include這個檔案夾了。
- 然後VS中右鍵SButton.h找到它的定義,然後再檔案資料總管中打開,檢視路徑。
是以最終
#include"Widgets/Input/SButton.h"
就解決問題了,因為路徑對了嘛,UE可以找到SButton啦。
最終效果:可以看到Button充滿了整個螢幕。不過我們還沒有定義事件,我們下節再定義。
待續。添加樣式啥的。