【UE4】插件與模塊

一、什麼是插件與模塊

模塊是實現某一個或一類功能的集合,當模塊足夠獨立和龐大、複雜之後,可以將其提升爲插件。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),一旦項目被重新編譯,則預編譯的插件動態庫及反射文件也會被清理。還有一點是,如果將插件封裝、隱藏類的定義,有時會在團隊開發時,因看不到源碼,變得有些棘手。

八、模塊工具

開發者工具裏面有一個模塊工具,可以用加載、重載和編譯模塊,另外也可以查詢模塊名信息。

打開模塊工具
我們可以在這裏編譯模塊。與關卡編輯器中工具欄的熱編譯不同的是,這個可以只編譯單個模塊,當然,如果是編輯器模塊就需要重啓才能看到效果。
模塊工具

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章