During the process of project development using Unreal Engine, various types of plugins are often developed and integrated to extend the engine, thereby fulfilling different requirements. For developers, understanding the operational mechanism is more important than using the tools. Thus, it is necessary to understand the principles behind the integration and development of plugins.
Previously, I also developed some tools and plugins for UE, and I hope to write a series of related articles summarizing the common technical content related to UE plugin and tool development and sharing some of my thoughts and functional scaffolding during plugin development. The goal is to achieve the desired functionality with minimal code, minimal invasiveness, and the best implementation strategies.
What is a plugin in UE?
A plugin in UE is a collection of modules organized through uplugin
. They can be conveniently enabled in the engine, developed based on the existing functionalities within the engine, and may also include modules from other plugins for extension.
Generally speaking, in terms of functionality distinction, the most commonly used module types can be categorized into the following:
- Runtime: the engine’s runtime, which runs both in the Editor and after packaging.
- Developer: involved in compilation and execution during non-shipping stages, used for some functionalities in the development stage, automatically excluded during shipping to avoid testing functionalities entering the release package.
- Editor: loaded when the Target is an Editor type of application, such as launching the engine’s editor, Commandlet, etc. Usually, Editor-type modules are used to extend editor functionalities, such as creating standalone viewports, adding function buttons to certain resources, and configuration panels, etc.
A single plugin can contain multiple different types of modules, such as Runtime/Developer/Editor, which can simply be described in the uplugin
.
In the organizational structure of the engine, plugins are located in the Engine/Plugins
directory:
The project’s engineering files are located in the PROJECT_DIR/Plugins
directory:
There is a certain dependency hierarchy between plugins in the engine and the project, and it is necessary to manage the dependency hierarchy well to avoid confusion.
- Modules in the engine should only depend on the built-in modules of the engine and should not include modules from other plugins.
- Modules in engine plugins can depend on built-in engine modules and modules from other built-in plugins of the engine, but should not include project-specific modules.
- Modules in project plugins can contain
engine built-in modules
/engine plugin modules
/modules from other project plugins
, but should not include modules defined within the project. - Modules in the project can include a collection of all the above modules.
The dependency relationships are shown in the following diagram:
Plugins must rely on a specific project to be initiated, where a project does not solely refer to a game project, but instead refers to a code unit defined by target.cs. A project can contain both plugins and its own modules. The Target configuration of the project will affect the loading of modules within plugins.
Taking the default project created by UE as an example:
In the Source directory, two target*.cs
files are created to distinguish between the packaged Runtime and Editor. Certainly, if there’s a DS project, there will also be a corresponding target.cs; here, each definition of target.cs is collectively referred to as the project compilation target.
Their differences are related to file content, not naming. Comparing the two:
The TargetType
specifies the type of the current Target.
TargetType
is an enumeration defined in UBT with optional values:
1 | namespace UnrealBuildTool |
This represents five different project Target types in UE.
When selecting different BuildConfigurations in the IDE, different Targets will be used:
For instance:
- Development Editor will use the configuration defined in
Blank427Editor.Target.cs
(TargetType.Editor
) - Development will use the configuration defined in
Blank427.Target.cs
(TargetType.Game
)
During compilation, the BuildConfiguration of the project will also be passed to plugins to determine which Modules within the plugins participate in the compilation.
In the plugin’s build.cs, the current Target’s information can be checked to handle different behaviors during the compilation of different Targets:
1 | public LearnPlugin(ReadOnlyTargetRules Target) : base(Target) |
How to describe a plugin?
uplugin
In UE, plugins are described through the uplugin
file, which is a configuration that represents the organizational structure and key information of a plugin based on JSON syntax.
- Basic information such as the name of the plugin, the author, etc.
- The type of the plugin module
- The timing of module activation
- Dependencies on other plugins
- Platform whitelists and blacklists
For example, the uplugin of HotChunker could look like this:
1 | { |
From this information, the quantity, types, and activation timing of modules in the plugin can be clearly known.
- During compilation, UBT will also read the plugin’s Type to decide whether the module participates in the compilation.
- When starting the engine, the module definitions will dictate the loading based on the information. For instance, the LoadingPhase configuration loads the plugin at different startup stages.
Modules
The Modules
element in uplugin is an array used to describe the current plugin’s modules, with the following basic information for each Module:
- Module name, which is the name defined in Build.cs
- Type, the type of the module such as Runtime/Developer/Editor/Program, dictating when it participates in compilation and loading for which Targets.
- Loading phase, the module’s activation timing, as the modules in the engine are loaded in sequence; by adjusting this configuration, the loading timing of the plugin module can be controlled, which can be very important for processes that depend on execution order. For instance, some modules in the engine might detect startup parameters. If these parameters are not wanted to be manually specified but achieved through plugin code, a module that starts before can be created to append the startup parameters to FCommandLine, thus automating the process without manual intervention.
Furthermore, for some special modules, such as the Type
of the HotChunker module being Program
, this indicates it will only participate in compilation and startup when the Target is Program, and can specify which Programs are valid through ProgramAllowList
.
Similarly, the supported values for LoadingPhase in the engine (4.27+) are:
1 | namespace ELoadingPhase |
Taking the following module description as an example:
1 | { |
This module indicates that: HotChunker is a Program-type module, and its startup timing is after the configuration file is loaded (PostConfigInit), only allowed to be used in the UnrealPak
Program, and will only participate in compilation on the Win64 platform.
By analyzing the contents of the uplugin file, one can describe all the modules in the plugin based on development needs, guiding UBT to find the corresponding modules for compilation and load them during the engine startup.
Note: The white and black list description of modules on platforms (
WhitelistPlatforms
). If a module A is marked with a platform whitelist, and an all-platform module B references A, A will be brought in to participate in the full-platform compilation, regardless of A’s own definition. This is an issue that arises from the referencer.
Directory Structure of Plugin
Typically, a plugin will contain the following directories and files:
- Content: optional, the plugin’s uasset resources, similar to the Content directory of game projects. Requires
uplugin
to haveCanContainContent=true
. - Resources: other resources that the plugin depends on, such as icons within the plugin, etc.
- Config: optional, the plugin’s configuration files, similar to the project’s Config directory, used for storing some ini configuration items.
- Source: directory for the plugin’s code.
- *.uplugin: the current plugin’s uplugin description.
Particular attention should be given to the Source
directory, which is key for implementing the code plugin. Usually, a corresponding directory for each Module is created under Source to store the code of different Modules, isolating the file organization of different Modules.
For example, if the uplugin defines two Modules:
1 | "Modules": [ |
Two corresponding directories named after them are created under the Source directory:
And their respective build.cs and actual C++ code files are created.
Compilation Environment
The compilation environment of UE is determined by UBT, which analyzes participating compiler target.cs as the basic compilation environment.target.cs
controls the entire project and affects every module that will participate in compilation.
*.Target.cs
and *.Build.cs
are the actual controllers of the Unreal build system, and UBT determines the entire compilation environment by scanning these two files. They are also the focus of this article.
Their roles differ:
-
*.Target.cs
controls the external compilation environment of the generated executable program, known as the Target. For example, it defines whatType
(Game/Client/Server/Editor/Program) the generated target is, whether to enable RTTI (bForceEnableRTTI
), how CRT is linked (bUseStaticCRT
), etc. -
*.Build.cs
controls the Module compilation process, governing dependencies on other Modules, file inclusions, linking, macro definitions, and so on.*.Build.cs
tells UE’s build system that it’s a Module and what needs to be done during compilation.
In short: everything related to the external compilation environment is managed by *.target.cs
, while everything related to modules themselves is managed by *.build.cs
.
I have more detailed descriptions in my article UE Build System: Target and Module.
C++ Definition of Module
C++ plugins usually contain a class that inherits from IModuleInterface
, which registers itself with the engine. When this module is activated, it serves as the execution entry point of the module, and upon the engine’s shutdown, it handles the module’s unloading and resource cleanup.
The definition is as follows:
1 |
|
And the implementation:
1 |
|
The key element here is the last IMPLEMENT_MODULE
macro, which exposes the module to the outside.
There are two linking modes for executable programs compiled in UE, namely Modular
and Monolithic
.
This can be controlled in target.cs
, but by default, the Editor is Modular
, while other target types (like Game, Program) are Monolithic
:
1 | public TargetLinkType LinkType |
Modular Mode
Modular mode refers to a segmented mode where each module is compiled into an independent executable file. The advantage is that it allows for incremental compilation of changed modules without needing to compile the entire project.
For example, in non-Monolithic mode under Editor (modules are compiled as DLLs), the macro definition is:
1 |
After macro expansion:
1 | // for not-monolithic |
Viewing the export table of the DLL will reveal the InitializeModule
function and other exported symbols (which are all name-mangled):
Monolithic Mode
Monolithic mode means that all C++ code in the project is statically linked and compiled into the same executable program (excluding added dynamic libraries; extra static libraries are also compiled into the executable program).
By default, packaging in UE uses the Monolithic mode, where both the engine and the project code are compiled into one file, such as:
- WindowsNoEditor:
WindowsNoEditor/GameName/Binaries/Win64/GameName.exe
- Android:
lib/arm64-v8a/libUE4.so
- IOS:
Payload/GameName.app/GameName
The advantage of this approach is that there are no extra PE file lookup and loading overhead, and all operations are executed in the current process space.
The definition of modules also differs accordingly, in the Not-Monolithic mode, to enable compilation as an independent executable file, a DLLEXPORT
marked InitializeModule
function will be created, but this is not needed in Monolithic mode. Instead, it uses static symbols:
1 | // If we're linking monolithically we assume all modules are linked in with the main binary. |
After macro expansion:
1 | static FStaticallyLinkedModuleRegistrant< FNewCreateModule > ModuleRegistrantNewCreateModule( TEXT("NewCreateModule") ); |
In fact, it defines a static object that leverages a C++ feature: the initialization of objects with static storage duration will occur before the first statement of the main function!
[ISO/IEC 14882:2014(E)] It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main.
Thus, when the engine starts, all modules create such a static object for initialization.
In the constructor of FStaticallyLinkedModuleRegistrant
, a singleton of the registered module class is created and registered with the ModuleManager
:
1 | /** |
This is used by the engine to subsequently call based on the startup timing information of the modules defined in the uplugin.
Thus, UE unifies the interface layer between Modular and Monolithic modes.
Conclusion
This article introduces the basic information about plugins in UE, as well as how to describe a plugin (uplugin), and provides an introduction to the directory structure of the Plugin and the definition of Modules.
Previously, I wrote articles related to the C++ compilation model and the UE build system on my blog:
- UE Build System: Target and Module
- UE Modules: Find the DLL and load it
- UE Modules: Load and Startup
- Build flow of the Unreal Engine4 project
- Macro defined by UBT in UE4
- Why is extern “C”?
These are the foundational concepts for tool development in UE, understanding and maximizing the use of UE’s own implementation mechanisms to achieve the desired functionality with minimal code.