UE4 Code Modules

What are UE4 code modules?

In UE4, a module is a distinct unit of C++ code, with an accompanying C# build file. A game project can be made up of one or more modules, as can a plugin. Under the hood, a module corresponds to a dynamic library, although by default in shipping builds all code is linked together into a single executable.

Why use multiple modules?

It's possible to create a game (or plugin) that is composed of a single module, and for smaller projects that's fine. There are a number of reasons to consider splitting up your code however.

  • Encapsulation and organisation. It's always a good idea to encapsulate code as much as possible. Building a component or system in its own module encourages you to keep dependencies down.
  • Code reuse. A module is a natural unit of code for reusing across multiple projects. Separating logically distinct systems at the code level makes it easier to reuse something, even if initially you didn't envisage it being useful outside the project for which you originally wrote it. One effective approach, if you use git, is to put your reusable module(s) into their own git repository, and then incorporate that as a git submodule in any project repository.
  • Configuration-specific code. If you want to write editor extension code for your custom classes and game systems, you should put it into a dedicated Editor module. While preprocessor definitions (#if WITH_EDITOR) can be used in some cases, any non-trivial amount of editor-specific code should go into an Editor module. It's also possible to create Development only modules, so you can have, for example, debugging code which gets automatically compiled out of shipping builds. The same goes for server/client-only code.
  • Platform-specific code. Again, preprocessor macros for platform-specific code should be kept to a minimum. It's possible to provide platform-specific implementations of project components, each in their own module, and selectively build and package based on the target.

Adding a module

For the remainder of this article, wherever you see 'YourModuleName', regardless of case, be it as part of a filename or in code, replace it with whatever you want to name your module.

Adding an extra module is essentially the same whether you're adding it to a project or a plugin. Inside the source directory, create a new folder named YourModuleName. Within that, you first need the module build file, named YourModuleName.Build.cs. A basic one will look something like this:

// YourModuleName.Build.cs

using UnrealBuildTool;

public class YourModuleName : ModuleRules
{
    public YourModuleName(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

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

Then you will want to add two folders, named Public and Private. The source (.cpp) files always go in the Private folder. Header files can go in either. If they define types or functions that you want to use from code inside other modules, put them in Public. Otherwise, put them in Private for maximum encapsulation.

A bare minimum compilable module requires a source file (YourModuleNameModule.cpp is a good standard) in the Private folder containing the following:

#include "ModuleManager.h"
IMPLEMENT_MODULE(FDefaultModuleImpl, YourModuleName);

To ensure your module gets built with your project, you can add a reference to it in your target file (YourProjectName.Target.cs/YourProjectNameEditor.Target.cs) as follows:

ExtraModuleNames.Add("YourModule");

However, in practice you will generally have a dependency chain connecting your module to the main project module (through one or more PublicDependencyModuleNames additions in .Build.cs files), in which case this step is not actually necessary.

Finally, you need to add a module reference to the .uproject (or .uplugin) descriptor, in the "Modules" array:

...
"Modules": [
    {
        ...,
        {
            "Name": YourModuleName,
            "Type": "Runtime",
            "LoadingPhase": "Default"
        }
    }
],
...

See the engine documentation for information on options for module type and loading phase. The linked page is written for plugins, but is the most up-to-date and also applies for adding modules at the project level.

Exposing code to other modules

In some cases, your module might not need to expose anything at all. It could just define some AActor/UObject types that will be picked up by the engine for use in the editor only, or it might just be registering some editor extensions. Often, however, you'll write a module that acts as a library, providing types and functionality to be used by other module code. In that case, you need to explicitly provide access to those elements.

I'll just detail the standard approach here. There is an alternative that has some benefits as well as restrictions, but I'll leave that for a later article.

In your public headers, add the YOURMODULENAME_API macro to declarations of types or functions that should be exposed.

// Exposed code from module 'YourModuleName', for use in other modules.

UCLASS()
class YOURMODULENAME_API AMyActor: public AActor
{...};

struct YOURMODULENAME_API FMyStruct
{...};

YOURMODULENAME_API void MyFunction();

Then these specific types/functions will be accessible from within the code of other modules. Your other module will need to add a static dependency in its build file. For example, if SomeOtherModule needs to use types from YourModuleName, add the following to SomeOtherModule.Build.cs:

PublicDependencyModuleNames.Add("YourModuleName");

Examples

The repository containing source code examples for other articles on this site has been written in modular form, so is a good reference. Kantan Charts, also on Github, is another example of using multiple modules, this time in the context of a plugin.

Conclusion

Reducing dependencies and keeping code organized can save you some major headaches down the line. Modules are great, use them!

I'll go into some more details, and also touch on plugins, in another article soon.