[UE4]為專案增加一個Game Module

上一節展示了Module相關的基本概念,本節就讓我們試著為專案加入一個Game Module吧。首先,我們可以把專案下面預設的Primary Game Module複制一份出來,並重新命名成希望的Module名稱,見Figure 2.5.1。

 

Figure 2.5.1 為HorizonTutorialGame專案增加一個HorizonTutorialGameEditor作為額外的Game Module以擴充遊戲內Editor相關功能。這個Editor Game Module我們可以在之後用來客製化UPROPERTY在editor中detail pnael的顯示方式,或者是追加新的編輯模式……等等。

 

 

然後必須更改新Module下面build.cs中的類別名稱,見Code 2.5.1。

HorizonTutorialGameEditor.Build.cs
using UnrealBuildTool;

public class HorizonTutorialGameEditor : ModuleRules

{

public HorizonTutorialGameEditor(ReadOnlyTargetRules Target) : base(Target)

{

PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

PublicDependencyModuleNames.AddRange(new string[] { “Core”, “CoreUObject”, “Engine”, “InputCore” });

PrivateDependencyModuleNames.AddRange(new string[] { });

}

}

Code 2.5.1 將Build.cs中的ModuleRules類別名稱改成HorizonTutorialGameEditor。

接著我們必須要定義要給這個Module的介面,見Code 2.5.2。

HorizonTutorialGameEditor.h
#pragma once

#include “Engine.h”

#include “ModuleManager.h”

class FHorizonTutorialGameEditorModule : public IModuleInterface

{

public:

virtual void StartupModule() override;

virtual void ShutdownModule() override;

};

 

Code 2.5.2 每個Module都必須要擁有一個IModuleInterface的實作才行,其中在Module載入時會呼叫StartupModule、結束時會呼叫ShutdownModule。

HorizonTutorialGameEditor.cpp
#include “HorizonTutorialGameEditor.h”

#include “Modules/ModuleManager.h”

//IMPLEMENT_PRIMARY_GAME_MODULE( FHorizonTutorialGameEditor, HorizonTutorialGameEditor, “HorizonTutorialGameEditor” );

//使用 IMPLEMENT_GAME_MODULE 代替 IMPLEMENT_PRIMARY_GAME_MODULE

IMPLEMENT_GAME_MODULE(FHorizonTutorialGameEditorModule, HorizonTutorialGameEditor);

void FHorizonTutorialGameEditorModule::StartupModule()

{

UE_LOG(LogTemp, Log, TEXT(“FHorizonTutorialGameEditorModule::StartupModule()”));

}

void FHorizonTutorialGameEditorModule::ShutdownModule()

{

UE_LOG(LogTemp, Log, TEXT(“FHorizonTutorialGameEditorModule::ShutdownModule()”));

}

 

Code 2.5.3 使用IMPLEMENT_GAME_MODULE來完成我們的Module實作,其中第一個參數表示要使用的Module實作類別,第2個參數則傳入這個Module的名稱。

可能有些人會困惑為什麼在Primary Game Module中並沒有看見類似的IModuleInterface程式碼,其實並不是沒有,而是專案的預設模版所使用的Module實作所使用的是引擎中的內建實作『FDefaultGameModuleImpl』,。

HorizonTutorialGame.cpp
#include “HorizonTutorialGame.h”

#include “Modules/ModuleManager.h”

IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, HorizonTutorialGame, “HorizonTutorialGame” );

Code 2.5.4 Primary Game Module使用引擎內建的IModuleInterface實作「FDefaultGameModuleImpl」。

到底FDefaultGameModuleImpl是什麼?從原始碼中我們可以看出它繼承了IModuleInterface,並override了IsGameModule這個功能,見Code 2.5.5。

FDefaultGameModuleImpl實作
class FDefaultGameModuleImpl

: public FDefaultModuleImpl

{

/**

* Returns true if this module hosts gameplay code

*

* @return True for “gameplay modules”, or false for engine code modules, plug-ins, etc.

*/

virtual bool IsGameModule() const override

{

return true;

}

};

Code 2.5.5 從注釋中我們可以看到,當IsGameModule回傳true的時候,代表的是這個Module裡面存在的都是跟GamePlay相關的Code。

到底什麼是GameModule?

從前面章節中的討論中我們知道,專案source底下的Module我們會將它當成Game Module來宣告。這邊比較讓人無法理解的是,當我們的使用IMPLEMENT_PRIMARY_GAME_MODULE或IMPLEMENT_GAME_MODULE來定義Module時,並沒有真的把這個Module真的設成GameModule,反而是我們必須要去override IsGameModule這個功能並回傳true之後,才能真正的讓引擎有所認知。

這邊的流程可以明顯的看出一些缺陷,或許在引擎以後的版本會將這部份進行優化,但目前我們能做的,就是盡可能的照著它的流程來進行,以減少往後升級引擎版本的困擾。

或許有人會開始思考,實際上當引擎知道這個Module是屬於GameModule時會做什麼事?試著在引擎的源始碼中搜尋IsGameModule這個關鍵字之後,會發現,其大部份都是在處理C++ HotReload相關的邏輯。

在實作完Module Interface之後,接著來便是讓我們的Target知道在編譯的時候,需要將新增加的Module編譯進到最後的執行檔中。

HorizonTutorialGameEditor.Target.cs
using UnrealBuildTool;

using System.Collections.Generic;

public class HorizonTutorialGameEditorTarget : TargetRules

{

public HorizonTutorialGameEditorTarget(TargetInfo Target) : base(Target)

{

Type = TargetType.Editor;

ExtraModuleNames.AddRange( new string[] { “HorizonTutorialGame” } );

ExtraModuleNames.Add(“HorizonTutorialGameEditor”);

}

}

 

Code 2.5.6當TargetType為Editor時,直接將該Module加入編譯。

HorizonTutorialGame.Target.cs
using UnrealBuildTool;

using System.Collections.Generic;

public class HorizonTutorialGameTarget : TargetRules

{

public HorizonTutorialGameTarget(TargetInfo Target) : base(Target)

{

Type = TargetType.Game;

ExtraModuleNames.AddRange( new string[] { “HorizonTutorialGame” } );

if (UEBuildConfiguration.bBuildEditor)

{

ExtraModuleNames.Add(“HorizonTutorialGameEditor”);

}

}

}

 

Code 2.5.7 當TargetType為Game時,只有帶有Editor時才將該Module加入編譯(執行GenerateProject指令時會觸發這個特殊條件)。

在將Module編譯進Target之後,我們還必須要告訴引擎,Runtime時要什麼時機點才將該Module載入,見Code 2.5.8。這邊要注意的是,若我們沒有在Target中加入需要的Module,則會跳出編譯失敗的錯誤訊息。

HorizonTutorialGame.uproject
{

“FileVersion”: 3,

“EngineAssociation”: “4.16”,

“Category”: “”,

“Description”: “”,

“Modules”: [

{

“Name”: “HorizonTutorialGame”,

“Type”: “Runtime”,

“LoadingPhase”: “Default”

},

{

“Name”: “HorizonTutorialGameEditor”,

“Type”: “Editor”,

“LoadingPhase”: “PostEngineInit”

}

]

}

Code 2.5.8 在uproject中指定要載入的Host Type為Editor,時機點在PostEngineInit。

從Code 2.5.8中我們可以看出,uproject整個檔案是用json格式來表示,在Module中的Name表示要載入的Game Module名稱、Type為Module所載入的環境、LoadingPhase則是載入的時機點。下面的表格將所有的可用的參數做了整理:

EHostType
Runtime Runtime代表遊戲中任何時機點都可以使用,在shipping或editor中都可以存取到該Module中的類別。
RuntimeNoCommandlet 基本上同Runtime,但只有在build沒有加入Commandlet時才能存取。
RuntimeAndProgram Runtime跟Program都能夠存取
CookedOnly 只有在做Cook時才能存取。
Developer 只有在Development這個config時才會將Module載入(editor跟runtime),Shipping時就無法存取。
Editor 只有在Editor中能夠存取。
EditorNoCommandlet 基本上同Editor,但只有在build沒有加入Commandlet時才能存取。
Program 只有Program能夠存取。
ServerOnly 只有Server Build才能存取。
ClientOnly 只有Client Build才能存取。

Table 2.5.1 Module在載入時,所有可用的目標環境,用來指定該Module適合哪些類型的應用使用。

ELoadingPhase
PostConfigInit 在引擎完全初始化之前,緊接在Config系統初始化之後。通常只有非常底層的功能需要。
PreLoadingScreen 在引擎完全載入所有的Module之前,跳出LoadingScreen之前。
PreDefault Default Phase之前。
Default 引擎初始化期間、GameModule讀入之後。
PostDefault Default Phase之後。
PostEngineInit 在引擎完全做完初始化之後。
None 該Module不自動載入。

Table 2.5.2 Module在載入時,所有可用的時機點。

最後,讓我們將上面的步驟做個總整理:

  1. 建立{MODULE_NAME}.build.cs。

  2. 繼承IModuleInterface並依需求對裡面的function進行override實作。

  3. 呼叫IMPLEMENT_GAME_MODULE,並將把在第二步實作的類別當作參數傳入。

  4. 在Target.cs中將該新增的Module加入編譯

  5. 在${PROJECT_NAME} .uproject中決定該Module所要載入的環境與時機點。

Leave a Reply

Your email address will not be published. Required fields are marked *