Integration Unreal Engine 4 with CMake project and boost library

Today we would like to share with you a part of our solution how we integrate Unreal Engine 4 project with external libraries. In this article we will use boost library and example CMake project. We are aware that some of you do not like classes used in that project like std::share_ptr or boost::variant (it uses typeid!), but this is just an example to show you how to deal with some problems that can you meet.

You can find all sources used in this post on our GitHub.

Quick look at CMake project

In this case our CMake project is called “Game Model”. Let’s imagine that we have a turn based game and we would like to keep the logic away from game view. This gives us the possibility to make the several game view (e.g. UE4 for PCs, separate Cocos2d-X implementation for mobiles). In general our integration follows the MVC pattern.

Let’s look at the project structure:

Project
| CMakeLists.txt      # just for help working in IDE
| ue4view
| model
  | CMakeLists.txt    # main Game Model CMakeLists.txt
  | core              # directory with interfaces, using boost
  | offline           # uses core interfaces

We have two directories with code: core and offline. What we would like to achieve is building it to static library and use it in our UE4 project. The structure of CMake project can be changed during development many times, so providing paths to CMake project directories is incorrect solution. To resolve that problem we will use install and export mechanism to put static library and header files in proper directories.

Let’s look into one of CMakeLists.txt files of exported libraries:

set(LIB_NAME coreLib)

project(${LIB_NAME})

aux_source_directory(. SRC_FILES)

add_library(${LIB_NAME} STATIC ${SRC_FILES})

# we use only header, but we would like to have access
# to boost include directory
target_link_libraries(${LIB_NAME} PUBLIC Boost::boost)

# when other CMake target will link to coreLib
# provide include directory
target_include_directories(${LIB_NAME}
    INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/.>
              $<INSTALL_INTERFACE:include>)

# we list files that will be put into
# installed 'include' directory
set(PUBLIC_LIBRARY_HEADERS
        IGameModel.h IGameObserver.h GameController.h)

install(FILES ${PUBLIC_LIBRARY_HEADERS} DESTINATION include)

install(TARGETS ${LIB_NAME}
        EXPORT GameModelTargets
        ARCHIVE DESTINATION lib
        LIBRARY DESTINATION lib
        RUNTIME DESTINATION bin)

Important for us are install commands that define rules of exporting libraries and headers to install directory.

Unreal Engine 4 project

We createed a simple UE4 project with C++ option like in this UE4 turorial and named it “TurnBasedGame”.

Now we need to link Game Model Library to our project. UE4 projects build configuration are written in C#. Generated Module Rules file looks like this:

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

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

In our project this is file ue4view/TurnBasedGame/Source/
TurnBasedGame/TurnBasedGame.Build.cs. Be aware that UE4 generates also Target Rules file (ue4view/TurnBasedGame/Source/TurnBasedGame.Target.cs), but this is not the file that we would like to edit.

Firstly, let’s create some fields to help providing paths. I think that there is no surprise there:

private string ModulePath
{
    get { return ModuleDirectory; }
}

private string ThirdPartyPath
{
    get
    {
        return Path.GetFullPath(Path.Combine(ModulePath,
                                             "../../ThirdParty/"));
    }
}

Building Game Model from UE4 project

You can find more interesting stuff inside methods for creating CMake commands:

private string CreateCMakeBuildCommand(string buildDirectory,
                                       string buildType)
{
    const string program = "cmake.exe";
    // root/ue4View/TurnBasedGame/Source/TurnBasedGame
    var currentDir = ModulePath;
    var rootDirectory = Path.Combine(currentDir,
                                     "..", "..", "..", "..");
    var installPath = Path.Combine(ThirdPartyPath, "generated");
    var sourceDir = Path.Combine(rootDirectory, "model");

    var arguments = " -G \"Visual Studio 15 2017\"" +
                    " -S " + sourceDir +
                    " -B " + buildDirectory +
                    " -A x64 " +
                    " -T host=x64" +
                    " -DCMAKE_BUILD_TYPE=" + buildType +
                    " -DCMAKE_INSTALL_PREFIX=" + installPath;

    return program + arguments;
}

private string CreateCMakeInstallCommand(string buildDirectory,
                                         string buildType)
{
    return "cmake.exe --build " + buildDirectory +
           " --target install --config " + buildType;
}

CreateCMakeBuildCommand creates configuration command with assumption that cmake.exe application is in the environment variable Path. Generated command should look like this:

cmake.exe -G "Visual Studio 15 2017" -A x64 -T host=x64
-S <<SRC_DIR>> -B <<BLD_DIR>> -DCMAKE_BUILD_TYPE="Release"
-DCMAKE_INSTALL_PREFIX=<<INSTALL_DIR>>

This command configures project: check dependencies, create targets. Despite the fact that we build for 64 bit target we do not use “Visual Studio 15 2017 Win64”, because in new CMakes prefer -A x64 flag.

We use this two methods in method for building game model.

private bool BuildModel(ReadOnlyTargetRules Target)
{
    const string buildType = "Release";

    var buildDirectory = "model-build-" + buildType;
    var buildPath = Path.Combine(ThirdPartyPath,
                                 "generated",
                                 buildDirectory);

    var configureCommand = CreateCMakeBuildCommand(buildPath,
                                                   buildType);
    var configureCode = ExecuteCommandSync(configureCommand);
    if (configureCode != 0)
    {
        Console.WriteLine(
            "Cannot configure CMake project. Exited with code: "
            + configureCode);
        return false;
    }

    var installCommand = CreateCMakeInstallCommand(buildPath,
                                                   buildType);
    var buildCode = ExecuteCommandSync(installCommand);
    if (buildCode != 0)
    {
        Console.WriteLine("Cannot build project. "
                          "Exited with code: " + buildCode);
        return false;
    }
    return true;
}

Build and install directories are placed in ThirdParty/generated. A good idea is adding this path to .gitignore file to avoid versioning build artifacts, libraries and headers. When repository will be cloned and the user will open TurnBasedGame.uproject UE4 will ask about rebuilding project and will generate it.

Finding boost

Despite the fact that for now we do not use boost in UE4 C++ files, we need to find and provide the include path for boost. Why? Because we include Game Model headers, which includes boost headers. In result, we need to provide the path for boost include directory:

private string getBoostIncludePath(string variable)
{
    var boostDirectory = "boost";
    var exampleBoostFile = "optional.hpp";
    var result = Environment.GetEnvironmentVariable(variable)
        .Split(';')
        .FirstOrDefault((path) => {
            var filePath = Path.Combine(path,
                                        boostDirectory,
                                        exampleBoostFile);
            return File.Exists(filePath);
    });
    return Path.Combine(result);
}

What is important, boost library needs to be also in the Path environment variable. In this function, we get Path variable and iterate over all paths and check if boost/optional.hpp it there.

Linking compiled binaries

Now we need to run all methods and check if compilation of Game Model succeeded. In case of success we add libraries to PublicAdditionalLibraries collection and include paths to PublicIncludePaths. If you are more interested about this or other fields in ModuleRules check it’s documentation.

private bool LoadGameModel(ReadOnlyTargetRules Target)
{
    if (Target.Platform != UnrealTargetPlatform.Win64)
    {
        return false;
    }
    var isModelBuilded = BuildModel(Target);

    if (!isModelBuilded)
    {
        return false;
    }

    const string envVariableName = "Path";
    PublicIncludePaths.Add(getBoostIncludePath(envVariableName));

    var LibrariesPath = Path.Combine(ThirdPartyPath,
                                     "generated", "lib");

    foreach (var libName in getGameModelLibraries())
    {
        var libraryPath = Path.Combine(LibrariesPath,
                                       libName + ".lib");
        PublicAdditionalLibraries.Add(libraryPath);
    }
    PublicIncludePaths.Add(Path.Combine(ThirdPartyPath,
                                        "generated", "include"));

    return true;
}

We put the LoadGameModel method inside our Module Rule constructor. It is finished? We hope so… but unfortunately no. Why? Let’s try to use Game Model code in UE4 C++ class, e.g. in Actor. When we include IGameModel. h we can get a huge list of build errors, mainly from boost library. Why?

Including headers of external libraries

UE4 sets a lot of compille options flags, which can make compilation failed. OK, we can adjust Game Model code, but what about boost? For that case UE4 provide THIRD_PARTY_INCLUDES_START and THIRD_PARTY_INCLUDES_END macros. Here is example usage:

#pragma once

#include <memory>

THIRD_PARTY_INCLUDES_START
#include <GameController.h>
#include <OfflineGameModel.h>
THIRD_PARTY_INCLUDES_END

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "AGameViewController.generated.h"

UCLASS()
class TURNBASEDGAME_API AAGameViewController : public AActor
{
    GENERATED_BODY()
	
public:	
    AAGameViewController();

private: // members
    models::OfflineGameModel gameModel;
    // no default constructor for GameController,
    // so we need to wrap it to prevent UE4 generator fail
    std::unique_ptr<core::GameController> gameController;
};

Ok, maybe now it is all over? No xD We can get compilation error: LNK2038 mismatch detected for 'boost__type_index__abi':
value 'RTTI is used' doesn't match value 'RTTI is off
- typeid() is used only for templates'

Enabling RTTI for Module Rules

Default configuration Unreal Engine 4 has disabled RTTI (dynamic_cast, typeid etc.). To allow external libraries using RTTI we need to set bUseRTTI variable to true. Please be aware that there is a different option that enables RTTI for target in Target Rules (bForceEnableRTTI variable). Another option that maybe you will consider is enabling exceptions mechanism (bEnableExceptions variable).

I promise that was the last thing that we needed to have our example working:

public TurnBasedGame(ReadOnlyTargetRules Target) : base(Target)
{
    PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

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

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

    bUseRTTI = true;
    bEnableExceptions = true;

    LoadGameModel(Target);
}

Potential problems in the future

It was just a simple example. What problems can you meet in the future? For example, compiling for other platforms, e.g. for Linux. If you would like to cross compile it on Windows you need to extend CMake configuration command by adding toolchain configuration files or setting other variables like CMAKE_CXX_COMPILER. We saw other solutions like setup Conan package manager, but basing on our experience that solution fails if Game Model evolves all the time. What about your solution? We encourage you to put comments on our LinkedIn and Facebook post about this article.

Copyright © Quick Turn Studio GmbH. All rights reserved.

Copyright © Quick Turn Studio GmbH. All rights reserved.

Log in with your credentials

Forgot your details?