Creating an Editor Module

What is an editor module? An editor module allows you to extend the functionality of the editor for your own custom C++ Actors, Components and other classes. You can do a variety of things such as ...

Updated about 2 years ago Edit Page Revisions

An editor module allows you to extend the functionality of the editor for your own custom C++ Actors, Components and other classes. You can do a variety of things such as customize the details panel for UPROPERTYs, add new editor modes, use component visualizers and other exciting things. This tutorial isn't going to discuss how to do any of that though, it aims to fill the gap of actually creating a module to put your customization code into.

This might seem like a lot of work just to enable you to do something else but the customization you can perform are very powerful compered to other engines.

Why not a plugin?

Whether you should use a plugin or editor module comes down to what your trying to do. If you want to extend the editor in a way that can be used across multiple projects then you probably want a plugin. If you only want to create customization specific to your game and class types then an editor module is a better design choice.

Please note that a plugin may also contain multiple modules. It is entirely possible to provide a runtime module in your plugin and accompany it with an editor plugin for convenience of use.

An editor module can be kept private from your game code meaning there are no cross dependencies. This vastly reduces the chances of having linking errors and means your editor code will not be included in your packaged application. If it is removed, then your game will still work fine and the editor will just lose the extended functionality.

Creating an editor module

First you will need to edit your .uproject file. It should already have an entry for your main game module:

{
    "FileVersion": 3,
    "EngineAssociation": "4.7",
    "Category": "",
    "Description": "",
    "Modules": [
        {
            "Name": "MyGame",
            "Type": "Runtime",
            "LoadingPhase": "Default"
        }
    ]
}

Under the existing module add an entry for your new editor module. Name is something sensible such as 'MyGameEditor' if your project is called 'MyGame'. It's important to keep this name consistent for your module to load. The type must be editor. Since we know that nothing will depend on this module we may as well load it as late as possible with 'PostEngineInit'. This is necessary to use component visualizers as of 4.7 and as far as I can tell has no other negative effects.

        {
            "Name": "MyGameEditor",
            "Type": "Editor",
            "LoadingPhase":  "PostEngineInit"
        }

Putting it together:

{
    "FileVersion": 3,
    "EngineAssociation": "4.7",
    "Category": "",
    "Description": "",
    "Modules": [
        {
            "Name": "MyGame",
            "Type": "Runtime",
            "LoadingPhase": "Default"
        },
        {
            "Name": "MyGameEditor",
            "Type": "Editor",
            "LoadingPhase":  "PostEngineInit"
        }
    ]
}

Module Folder Structure

Next you will need to add the folder structure for your module. Create a new folder under source with the same name as your new module name (we will assume this is 'MyGameEditor' for the rest of this tutorial). I would suggest creating a Public and Private folder inside this folder to organize your code but it's not strictly speaking necessary.

Add Module Build Rules

Create a file called 'MyGameEditor.Build.cs' in the Source/MyGameEditor folder, the easiest way to do this is to copy the MyGame.Build.cs and rename it. Make sure you rename any entries inside it as well. If you've used a public/private folder structure add them to the public and private include paths as below:

...
    public MyGameEditor(ReadOnlyTargetRules Target) : base(Target)
    {
        PublicIncludePaths.AddRange(
            new string[]
            {
                "MyGameEditor/Public"
            });

        PrivateIncludePaths.AddRange(
            new string[] 
            {
        "MyGameEditor/Private"
        });

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

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

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

        }
...

You'll also need to add your game module as a public dependency as displayed above:

        PublicDependencyModuleNames.AddRange(
            new string[]
            {
                "MyGame"
            });

Any types in your game module that you want to access in your editor module will need to use the export macro, eg '''MYGAME_API'''. Any classes you create using the new class wizard in the editor should already have it. However you will need to add it your self to structs and other types created outside the wizard.

You'll also need to add in specific modules that you need to access editor libraries to PrivateDependencyModuleNames. At a minimum you'll want to add 'UnrealEd'. Check the API documentation for what module a class is part of, if your having linking errors it's probably because you haven't added the module in here.

Editing Target.cs

Note: SetupBinaries was depreciated in 4.16. See 1 for guidance how to transition to the new system.

If you look in the Source folder again you'll see two files, MyGame.Target.cs and MyGameEditor.Target.cs. You'll need to make a small edit to both of these files inside the SetupBinaries function:

In MyGame.Target.cs add (the if statement makes sure this only gets included in the editor build):

        if (bBuildEditor)
        {
            ExtraModuleNames.AddRange( 
                new string[]
                {
                    "MyGameEditor"
                });
        }

In MyGameEditor.Target.cs add:

        // Game editor
        ExtraModuleNames.AddRange(
            new string[]
            {
                "MyGameEditor"
            });

4.16+ users (Depreciated use of bBuildEditor in MyGame.Target.cs)

        //if (bBuildEditor) //this will include UnrealEd and will throw errors when packaging
        if (Target.Type == TargetType.Editor)//4.16+
        {
            ExtraModuleNames.AddRange( 
                new string[]
                {
                    "MyGameEditor"
                });
        }

Source Files

We need a minimum of a header and source file to get the module to compile. Create a file called MyGameEditor.h and place it in the Public folder (if you created one), this is the only file you will need to put in Public, the rest should go in Private.

This file will act as the pre compiled header for the module so be sure to include anything that you think you'll need in most files here for faster compilation times.

We're also going to create a custom module class so we can load our customization once they're made, this is fairly similar to what you would see in a plugin. To do this create a class that extends IModuleInterface and at minimum override the StartupModule() and ShutdownModule() functions.

I also like to declare a log category for use with the module. Since we know that this isn't going to be in the final build we can afford to use verbose log outputs.

This will look something like this:

#pragma once

#include "Engine.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "UnrealEd.h"

DECLARE_LOG_CATEGORY_EXTERN(MyGameEditor, All, All)

class FMyGameEditorModule: public IModuleInterface
{
public:
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;

};

Finally create a file called MyGameEditor.cpp in the private folder. There's a few things to do here.

At the top of the file use the IMPLEMENT_GAME_MODULE macro, the first argument is the name of the module class you created in the header file, the second argument is the name of the module as you declared it in the uproject file.

#include "MyGameEditor.h"
#include "Modules/ModuleManager.h"
#include "Modules/ModuleInterface.h"

IMPLEMENT_GAME_MODULE(FMyGameEditorModule, MyGameEditor);

Under this make a basic implementation of the startup and shutdown functions, if you add in a log call then you can check that the module is being loaded correctly on startup. As you make your editor customizations this is where you will load and unload them. The exact method for this differs depending on the customization type but it usually involves accessing the appropriate module though the module manager and calling a registration function.

DEFINE_LOG_CATEGORY(MyGameEditor)

#define LOCTEXT_NAMESPACE "MyGameEditor"

void FMyGameEditorModule::StartupModule()
{
    UE_LOG(MyGameEditor, Warning, TEXT("MyGameEditor: Log Started"));
}

void FMyGameEditorModule::ShutdownModule()
{
    UE_LOG(MyGameEditor, Warning, TEXT("MyGameEditor: Log Ended"));
}

#undef LOCTEXT_NAMESPACE

Generate project files

Almost done! Lastly right click on your .uproject file and select generate project files. This will re-generate your project files including your new module. If you get an error you probably have a name inconstancy, make sure you're using 'MyGameEditor' or whatever you chose in all the right spots.

Compile and run your project. If you check your output log you should see the log output from above. You have now successfully created your editor module!

Source code

What next?

Now you actually need to create some customizations and register them in StartupModule(). Here are a few good resources to start with:

Editor customizations do not get hot reloaded. You will need to close and re-open the editor to see changes you make in this module.

Conclusion

An editor module allows you to easily organize editor only code and separate it from your game code. If you are going to customize the editor for your specific types this is a good starting point. It might seem like a lot of work but you'll be glad you did it later!