一、什麼是插件與模塊
模塊是實現某一個或一類功能的集合,當模塊足夠獨立和龐大、複雜之後,可以將其提升爲插件。UE4引擎就是由衆多模塊組成,而插件也可以包含一個或多個模塊,但模塊卻不能包含插件。相對於模塊來說,插件具有更高的獨立性,除使用引擎模塊外,一般不使用其它插件或模塊。並且插件可以非常方便地移植到不同項目中使用。
二、創建插件
我們可以在插件窗口(Edit → Plugins)選擇創建新的插件。
以下爲UE4提供的默認插件類型:
三、插件目錄介紹
我們創建了一個帶有獨立窗口的插件,並命名爲SlateUI。SlateUI插件的目錄:
插件被放置在Plugins目錄下,這個目錄包含的是項目插件。
在Source目錄下有個SlateUI的文件夾,這個文件夾就是SlateUI插件下的SlateUI模塊,每個插件有且至少有一個模塊,這個SlateUI模塊就是創建插件時生成的默認模塊。每個模塊擁有在Source目錄下的獨立文件夾,並且還有一個“ModuleName.Build.cs”的模塊配置文件。
四、配置文件
1、插件
SlateUI.uplugin文件:
{
"FileVersion": 3,
"Version": 1, //版本號
"VersionName": "1.0", //版本名
"FriendlyName": "SlateUI", //插件名
"Description": "", //插件描述
"Category": "Other", //插件目錄,這個會將其分類到插件啓用頁面的相應目錄下
"CreatedBy": "", //作者
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": false, //是否包含Content目錄
"IsBetaVersion": false,
"Installed": false,
"Modules": [ //插件包含的模塊,新創建的插件會默認包含一個同名的模塊
{
"Name": "SlateUI", //模塊名,這裏就是創建插件時,默認創建的模塊SlateUI
"Type": "Editor", //模塊類型,表示模塊在什麼場景下使用,類型爲EHostType
"LoadingPhase": "Default" //模塊加載的階段,類型爲ELoadingPhase
}
]
}
Type可填寫的值範圍:
namespace EHostType
{
enum Type
{
Runtime, //運行時,任何情況下
RuntimeNoCommandlet,
RuntimeAndProgram,
CookedOnly,
Developer, //開發時使用的插件
Editor, //編輯器類型插件
EditorNoCommandlet,
Program, //只有運行獨立程序時的插件
ServerOnly,
ClientOnly,
Max
};
}
LoadingPhase的值範圍:
namespace ELoadingPhase
{
enum Type
{
PostConfigInit, //引擎完全加載前,配置文件加載後。適用於較底層的模塊。
PreEarlyLoadingScreen, //在UObject加載前,用於補丁系統
PreLoadingScreen, //在引擎模塊完全加載和加載頁面之前
PreDefault, //默認模塊加載之前階段
Default, //默認加載階段,在引擎初始化時,遊戲模塊加載之後
PostDefault, //默認加載階段之後加載
PostEngineInit, //引擎初始化後
None, //不自動加載模塊
Max
};
}
2、模塊
SlateUI.build.cs文件:
using UnrealBuildTool;
public class SlateUI : ModuleRules
{
public SlateUI(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
//填入引擎模塊的某個子目錄後,引用包含的頭文件可以省去前面的路徑
PublicIncludePaths.AddRange(new string[] {/* ......*/});
//填入項目或項目插件某個模塊的子目錄後,引用包含的頭文件可以省去前面的路徑
PrivateIncludePaths.AddRange(new string[] {/* ......*/});
//如果此模塊依賴其它模塊,需要將其添加到下面兩個變量中的一個,區別如下
//如果其它模塊依賴此模塊,則其也可以訪問Core模塊
PublicDependencyModuleNames.AddRange(new string[]{"Core",});
//如果其它模塊依賴此模塊,但其不可以訪問下面的模塊
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Projects", "InputCore", "UnrealEd", "LevelEditor", "CoreUObject", "Engine", "Slate", "SlateCore",
}
);
//動態加載的模塊,動態加載和靜態加載不在本節討論範圍
DynamicallyLoadedModuleNames.AddRange(new string[]{/* ......*/});
}
}
3、項目
如果我們想使用插件中的某個模塊,首先要啓用這個插件,我們可以在插件窗口選擇Enable插件,或者在文件中配置屬性。
Game.uproject文件:
{
"FileVersion": 3,
"EngineAssociation": "4.21",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "StartGame",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine"
]
}
],
"Plugins":[ //添加插件
{
"Name": "BlankP",
"Enabled": true
}
]
}
並且,我們需要在項目模塊中添加依賴的模塊名:
Game.build.cs文件:
using UnrealBuildTool;
public class StartGame : ModuleRules
{
public StartGame(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
//我們這裏添加"BlankP"模塊的依賴
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "MoviePlayer", "UMG", "BlankP" });
PrivateDependencyModuleNames.AddRange(new string[] { });
}
}
五、創建模塊
1. 插件中創建模塊
我們創建了一個名爲BlankP的插件,它會默認創建一個包含BlankP的模塊,我們在此插件下再創建一個名爲PluginM的模塊。
首先在BlankP插件Souce目錄下,將BlankP目錄複製一份,並將其命名爲PluginM,並且修改其配置文件與代碼,將所有BlankP修改爲PluginM。並且在插件配置文件中包含PluginM模塊。
添加新模塊後的插件目錄:
BlankP.uplugin文件:
"Modules": [
{
"Name": "BlankP",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{ //添加新模塊
"Name": "PluginM",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
另外,我們還需要重新生成這個它的VS項目文件。
所以,如果是在插件中創建模塊,則除了創建模塊內必須的類和文件,只需要在插件配置文件中包含模塊即可。
2. 項目中創建模塊
前邊與創建插件模塊相同,先創建模塊所需的模塊加載類以及模塊配置文件。
項目目錄:
創建完模塊後,我們需要在StartGame.uproject中添加模塊,這步操作類似在插件中添加模塊:
"Modules": [
{ //項目包含的模塊,因爲我沒有創建新的項目模塊,所以這裏只有一個默認的StartGame
"Name": "StartGame",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine"
]
}
]
其次,我們要在StartGame.Target.cs和StartGameEditor.Target.cs中添加模塊,如果模塊只是在編輯器中有效,則只需要在StartGameEditor.Target.cs中添加。在此處添加模塊後,模塊纔會被鏈接編譯。
StartGame.Target.cs文件:
using UnrealBuildTool;
using System.Collections.Generic;
public class StartGameTarget : TargetRules
{
public StartGameTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
ExtraModuleNames.AddRange( new string[] { "StartGame" } ); //新模塊添加在此處
}
}
StartGameEditor.Target.cs文件:
using UnrealBuildTool;
using System.Collections.Generic;
public class StartGameEditorTarget : TargetRules
{
public StartGameEditorTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
ExtraModuleNames.AddRange( new string[] { "StartGame" } ); //新模塊添加在此處
}
}
在遊戲項目中,我們可以按照LoadingScreen模塊、AI模塊、Gameplay模塊等來將不同的模塊分類。
六、模塊加載與卸載
在模塊文件中,有個繼承IModuleInterface的類,其中定義了兩個方法,分別是StartupModule()和ShutdownModule()。這兩個方法分別在模塊加載和卸載時執行,所以,我們可以在這兩個方法中執行加載任務和內存清理的功能。
當然,我們也可以將其添加到遊戲邏輯模塊中,執行遊戲模塊加載和卸載的一些必要任務。
遊戲模塊的頭文件:
//一般模塊類繼承的是IModuleInterface,FDefaultGameModuleImpl是IModuleInterface的封裝,遊戲模塊可繼承此類
class FStartGameModule : public FDefaultGameModuleImpl
{
public:
virtual void StartupModule() override; //模塊加載完成後執行此方法
virtual void ShutdownModule() override; //模塊卸載期間執行此方法
};
定義:
#define LOCTEXT_NAMESPACE "FStartGameModule" //這個是爲語言國際化用的
void FStartGameModule::StartupModule()
{
//模塊加載完成後執行此方法
}
void FStartGameModule::ShutdownModule()
{
//模塊卸載期間執行此方法
}
#undef LOCTEXT_NAMESPACE
//這個宏只有唯一的遊戲模塊可以使用,其它模塊使用註釋掉的宏,否則打包會失敗!
//這個宏負責將模塊註冊,模塊的加載與卸載進入生命週期流程
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, StartGame, "StartGame" );
//IMPLEMENT_MODULE(FNewMModule, NewM)
七、插件封裝
如果插件允許暴露類的定義給使用者的話,我們可以直接將Plugins下的插件目錄直接提供給插件的使用者。
但,如果不想要插件中類的定義暴露給使用者,則需要進行一些處理。
首先,需要編譯插件的不同版本。然後,編譯好的動態庫文件以及反射文件會分別被保存在Binaries和Intermediate文件夾中。
插件目錄:
然後,在模塊的配置文件中,將預編譯變量設爲true,這樣再編譯項目的時候,使用預編譯的模塊將跳過編譯。
using UnrealBuildTool;
public class BlankP : ModuleRules
{
public BlankP(ReadOnlyTargetRules Target) : base(Target)
{
bUsePrecompiled = true; //使用預編譯設爲true,模塊將跳過編譯
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(new string[] {});
PrivateIncludePaths.AddRange(new string[] {});
PublicDependencyModuleNames.AddRange(new string[]{"Core",});
PrivateDependencyModuleNames.AddRange(new string[]{"CoreUObject", "Engine", "Slate", "SlateCore",});
DynamicallyLoadedModuleNames.AddRange(new string[]{});
}
}
然後,我們先預編譯一遍各個版本的插件,然後將使用預編譯設爲true。這樣,我們即使刪除類定義文件(.cpp)也不會影響插件的使用。
但是,需要注意的是,項目不能被重新編譯(Rebuilt),一旦項目被重新編譯,則預編譯的插件動態庫及反射文件也會被清理。還有一點是,如果將插件封裝、隱藏類的定義,有時會在團隊開發時,因看不到源碼,變得有些棘手。
八、模塊工具
開發者工具裏面有一個模塊工具,可以用加載、重載和編譯模塊,另外也可以查詢模塊名信息。
我們可以在這裏編譯模塊。與關卡編輯器中工具欄的熱編譯不同的是,這個可以只編譯單個模塊,當然,如果是編輯器模塊就需要重啓才能看到效果。