Unreal Engine is one of the most popular game engines. A lot of popular games has been built using this engine.
Unfortunately “engine” is a very ill-defined term in the industry and can mean different things for different engines and people. Is it a library? Is it a editor? Is it a toolset? Perhaps all these things together, with not a lot of documentation.
Whilst trying to integrate some libraries in an Unreal Engine game, I’ve spent quite a lot of time to figure out how is the build process. And the results of my findings are in this post.
If you don’t understand how C++ compilation works, it is highly recommended that you read the post Compiling C++ Code.
What is Unreal Engine?
I’ll try to define in a way I understand. Unreal Engine is a set of C++ code designed to be compiled in many different ways, in multiple platforms. You can extend that code with your game code, but not in a very decoupled way.
When you compile Unreal Code (with or without a game) you will have as a result a game client (what runs on the PC or console), game server (for multiplayer), an editor and a lot of tools (what is called Programs in the engine). The editor gives you access to many things, including tools to design levels and writing blueprints (a sort of scripting for Unreal Engine). The Programs includes a bunch of tools, including Unreal Build Tool and Unreal Header Tool, which I’ll give some more information in this post.
What is Unreal Build Tool?
To achieve this multiple flavours of compilation, platforms, and even doing some black magic like compiling the same code as a .lib or a .dll (to enable Hot Reload) a design decision by unreal tool was to build their own build tool. It means that instead of a makefile, or MSBuild you will have Unreal Build Tool in place. This changes a bit the standard way of developing C++ applications, integrating libraries, etc.
Unreal Build Tool is written in C# and it is built as the first step in the build process. When you run “GenerateProjectFiles” (a very famous batch files that generates your Visual Studio solution and projects for Windows), the first step is building the build tool. This happens invoking MSBuild on Source\Programs\UnrealBuildTool\UnrealBuiltTool.csproj.
So, Unreal Build Tool is a console application that does a lot of magic like generating project files, invoking unreal header tool, invoking the compiler and linker for different platforms and flavours of build. This process will be detailed further. Because the documentation doesn’t go deep as this, I’ve found this information after long debugging sessions on UnrealBuildTool.
Modules in Unreal Engine are sets of source files. You can have C# or C++ modules. For C# modules, the .csproj is used as its project file.
For C++, it is a bit weird. Modules are defined by having a ModuleName.Build.cs file which is similar to a vcxproj (contains a way of describing defines, includes), a Private directory for private headers and source files and a Public directory for public source files and headers.
The Public headers will be exposed to other modules. The Private won’t.
So, the rule of thumb for creating a module is adding a new directory under Engine\Source or YourGameFolder\Source and adding the .Build.cs file. Dependencies to other modules are also described in .Build.cs file.
This same process applies for 3rd party code. You create a directory, a .Build.cs describing how to build and which headers to expose.
For C++, a Module “can” compile as a monolith, as part of other module (which is an executable, DLL, etc) or as a .dll in the case of hot reload. So, this is the flexibility that Unreal introduces.
A Target in Unreal can be understood as a “build flavour”. It will basically set a bunch of defines and modules that target depends on. Usually you will have a target for your game Client (the code that runs on PC or console), the Server (multiplayer server), Editor and Programs. Even Unreal utilities like UnrealHeaderTool are built as programs. Then, some defines will control more source code to include in the compilation flavour and maybe some extra dependencies that applies only for that compilation flavour.
Targets are defined by .Target.cs files in the engine or game directory.
Generating Project Files
To get started, the first thing to do is run GenerateProjectFiles. But… what happens?
- Unreal Build Tool is built
- The batch files invokes something similar to (depending on your project, Visual Studio version, platforms, etc.)
- UnrealBuildTool will search for all files with the extensions .Build.cs in the engine and game folders to discover the existing modules.
- UnrealBuildTool will search for all files with the extensions .Target.cs and find all targets.
- It will generate a solution containing all targets as build configurations and all modules as projects.
-ProjectFiles -nodummyconfigs -game -engine -2017 "-project=Path\To\Your\Project.uproject" -Platforms=Win64+XboxOne+UWP64 -noSolutionSuffix
The C# projects are just the .csproj files in the source folders. The C++ projects are not exactly “standard” projects. Instead of invoking MSBuild, it invokes UnrealBuildTool again!
Building a C++ Project
When you build a C++ project in Unreal, you can see (based on NMakeBuildCommandLine property of the vcxproj) a command line similar to this one will be invoked:
C:\Path\To\Your\Engine\Build.bat TargetName Win64 Debug "$(SolutionDir)$(ProjectName).uproject" -waitmutex $(AdditionalBuildArguments) -2017
Under the hood, this invokes UnrealBuildTool again!
Then, what UnrealBuildTool does is:
- Compile the Target. It compiles the .Target.cs code (using the C# compiler) in runtime, to get the build properties, etc. This is from where UnrealBuildTool will get most of the defines and platform information. Some properties, for example, bBuildEditor will imply that you want the build editor. It creates a WITH_EDITOR define that will then be forwarded by the compiler to the source files.
- Will resolve all dependent modules, including dependencies that comes from the .Target.cs and dependencies that comes from the module (.Build.cs)
- Will compile all the .Build.cs for the dependent modules, to get extra properties about how to build each module.
- Resolve, based on all the dependencies which modules are shared precompiled headers (has the SharedPCHHeaderFile property on the .Build.cs, for example CoreUObject, Core, Engine).
- Resolve, based on all the dependencies which modules has headers that depends on UObject.
- Run UnrealHeaderTool for all modules with headers that depends on UObject. This is how Unreal Engine injects some behaviours in your classes, forcing you to add a “.generated.h” header in your files, which will be generated by UnrealHeaderTool.
- Resolve all the include directories, based on the code generated by UnrealHeaderTool
- Generate a list of commands to be invoked on the target environment, based on the resolved directories, defines, external libraries, etc:
- Invoking the compiler for shared precompiled headers (CL.EXE)
- Invoking the compiler for compiling source files (CL.EXE)
- Invoking the linker (LINK.EXE)
- Invoke all those actions
Obviously these steps are a simplification of what happens. There’s way more details and settings, like if you use a CLR or not, if you use Mono or not, if you clang or not and the list goes on and on. But the high-level approach is the described above.
I hope this post helps people that are used with standard tools to understand the UnrealBuildTool specifics. This can be very helpful for the next posts I’m planning to do about how to integrate test frameworks with Unreal.
I hope it helps!