Search
  • Bob Cober

Getting Started with UE4 Source Code


One of the biggest benefits of Unreal is the access to it's source code. Source Code can be the ultimate documentation - with it you can understand, troubleshoot, and enhance literally anything about Unreal. Unfortunately, obtaining the knowledge and understanding required to leverage this to your advantage takes time and requires non-trivial effort.

This post is not designed to teach you c++ nor rehash the existing documentation. Nor will it attempt to explain the design and architecture of Unreal. Instead the goal is to simply supplement the existing resources and provide some tips and guidance on how to approach digging in to the source code, thus hoping to serve as a brief introduction to getting started....

It is meant to supplement the excellent Epic docs at:

https://docs.unrealengine.com/latest/INT/Programming/

The big picture

The UnrealEngine source code is available at https://github.com/EpicGames/UnrealEngine. It is now also distributed with the binary engine releases that you can install it directly from the launcher - this means that you do NOT need to use Github in order to be able to read, debug, and step into UE engine source code. But you do still need the Github source if you want to make changes or submit a patch.

Unreal Engine is a large codebase. It can be daunting, especially if you are new to c++ or have never had to tackle a large, existing amount of code written by other people. That being said, it is a great opportunity to learn a few things.

The engine itself consists of several core modules. These modules (typically compiled as dlls, at least on windows), when packaged along with your game specific modules and resources, represent how you distribute your game. This packaged executable is YOURGAME.exe The engine also includes modules for an editor IDE. The binary executable for this is UE4Editor.exe. You use the editor to design and build your game. When you launch your project from the epic launcher, the editor is what is launched and your project is opened.

Thus, you can launch your game through the editor, or package it up and run it without the editor. The process of packaging all of the assets for your game so they can run without the editor is commonly referred to as "cooking". This entails making sure all the binary assets (e.g. art) are converted to work on the platform you are targeting.

Your "project" is simply a directory that contains the .uproject file and all of the assets and source code. After packaging, your "game" will be in the binaries folder in the project directoy and include the .exe and all the required assets.

The pre-built engine you obtain from the launcher was historically referred to as the rocket build. Using the launcher, you can download and install multiple binary versions of the engine. These will all co-exist independently with any other engine versions you might also build from source. Any of these editors can be launched and used to edit your projects. You can switch which engine is used for your project by right-clicking the .uproject file and selecting Switch Engine Version. It is recommended to migrate one version at a time - e.g. avoid going from 4.6 to 4.16 all at once.

Building From Source

While source is available for the binary Rocket builds, in order to make changes or submit changes to the engine, you will want to download and build your own engine from source. The source code for the engine is available on github. Clone or download it, and then run setup.bat to download all the dependencies. (These dependencies are not stored in the github repo ). Once you have the source and dependencies, run GenerateProjectFiles.bat. This will create the Visual Studio .sln project file. Detailed instructions covering this are available on the github readme. You can then use the VS Project to build your own binary version of the editor.

It is worth mentioning at this point that Unreal includes it's own build system. You can (and should) use Visual Studio or XCode on Mac to view the source, but the actual process of building does not require them. These IDE Workspaces are generated by UE so humans like us can browse the project. You can build the engine from these Workspaces, but under the covers they just launch the Unreal build system for you. You can generate a workspace at any time for your project by right clicking your .uproject and selecting ...Generate Visual Studio Project File.

More information on the build system is available at:

https://docs.unrealengine.com/latest/INT/Programming/UnrealBuildSystem/

As a side note, Epic recently introduced a new tool into the build system in 4.15: BuildGraph. BuildGraph is a script-based automation system that uses XMLto define building steps.

https://docs.unrealengine.com/latest/INT/Programming/Development/BuildGraph/index.html

The entire process of compiling the engine can take anywhere from a few minutes to an hour or more. It has improved greatly in 4.15 and later. Compiling primarily depends on how fast and powerful your machine's cpu is. Compiling also takes advantage of multiple cores so a quad core or more is a good investment.

Build Targets

When compiling the source for your game (not the engine), you must choose a target. You can choose to target a build of either the editor or your game. You can also choose whether to use debug or development versions of your game modules. Debug versions include debug symbols which can help you track down bugs.

For example, common targets include:

DebugGame

DebugGame Editor

Development

DevelopmentEditor

When you run either the DevelopmentEditor or the DebugGame Editor, the UE4Editor is launched with your project opened. When you run the Development or DebugGame target, only your game is launched.

If you run the DevelopmentEditor you will not have debug symbols for the engine code. If you run the DebugGame Editor of your project, you will be able to set breakpoints and step into live engine source code! (Note - In order to set breakpoints in engine code, you will still need the actual debug symbols from the engine .PDB files. If you are compiling from source, you will need to compile a debug build of the engine. If you are using a binary build from the launcher, you will need to download these build symbols - they are available as an option from the launcher - see below)

Also - A useful plugin for Visual Studio is UnrealVS.

https://docs.unrealengine.com/latest/INT/Programming/Development/VisualStudioSetup/UnrealVS/index.html

If you use Perforce for Source Control, and you really should, then the Perforce VS plugin is your ticket

https://www.perforce.com/product/components/visual-studio-plug-in

If you right-click a .uproject in Windows Explorer, there a few options including

-Launch Game

-Generate Project Files

-Switch Engine Version

InstalledBuild

If you are using a custom engine build and working on a team, it can be very useful to create an InstalledBuild of the engine. An installed build is a binary build of the engine, similar to what is downloaded with the launcher. This enables your artists on your team to use your custom engine without having to use Visual Studio. It also keeps your game's Visual Studio Workspace free of any additional engine cruft.

https://docs.unrealengine.com/latest/INT/Programming/Development/InstalledBuildReference/index.html#bookmark1

http://jackknobel.com/buildgraph/2017/02/06/Building-an-installed-ue4-from-source/

For example, to create an installed build from the Github Source, simply run:

>RunUAT.bat BuildGraph -target="Make Installed Build Win64" -script=Engine/Build/InstalledEngineBuild.xml -set:WithIOS=false -set:WithMac=false -set:WithTVOS=false

This will create a binary engine build and put it in LocalBuilds\Engine\Windows.

Jumping Into The Weeds

Now that we have some of the preliminary stuff out of the way, lets dig in a little deeper.

Create a new c++ project or open an existing one and open Visual Studio. (You can use a custom engine or a binary engine from the launcher)

After opening the Visual Studio Solution Workspace, there are two projects: One for the engine and one for your game (this game was called (Hangar18)

Typically you will spend most of your time in your Game Project(Hangar18 in this case), but the UE4 project is where we can look at engine code.

Config is for configuration (.ini) files.

Plugins are for modular code, tools or utilities that you can use across multiple projects.

Source is where the source files are.

(for more, see https://docs.unrealengine.com/latest/INT/Engine/Basics/DirectoryStructure/)

For this article, let's focus on the UE4/Source:

Within Source, Runtime/Engine contains most of the code we will be looking at:

Clearly, there are very many files and directories. The first thing to come to terms with when dealing with a large codebase like this is to get comfortable with the fact that you will not ever really understand everything. There are simply too many files. And that is ok.

Instead, as you slowly dig into a certain component or sub-system, you will begin to accumulate a certain degree of familiarity and understanding.

The more time you spend reading it, the more familiar and easier it will become. You will become more adept at reading c++ source. This takes time and practice, but eventually it becomes fairly easy. It requires a fundamental understanding of core c++ and UE coding conventions.

If a certain tech interests you, it can be very beneficial just to start digging around and looking at stuff. Don't hesitate to just look around and explore.

If you are interested in rendering, FDeferredShadingSceneRenderer::Render in Runtime\Renderer\Private\DeferredShadingRenderer.cpp isa good place to start. https://docs.unrealengine.com/latest/INT/Programming/Rendering/Overview/index.html

If Physics piques your interest, check out Runtime\Engine\Private\PhysicsEngine. This is contains much of the PhysX integration.

It is often useful to have a context to start digging in with. A good place to start might be a specific problem you are troubleshooting, or perhaps a new feature you want to add,

Let's start with a simple example. You have probably used the Print String command from blueprints.

Let's look under the covers and discover how it is implemented.....

Right-click the Print Screen node in your blueprint and select ...Goto Code Definition

This should open up Visual Studio and take you to the file where the function is implemented. In this case: Engine\Source\Runtime\Engine\Classes\Kismet\KismetSystemLibrary.h

It doesn't actually take you to the function - to get there you need to use ctl-f to search for Print String.

This is the declaration of the function in the header file. (Kismet is the internal name for Blueprint) .

Notice the UFUNCTION macro - it enables this function to be exposed to blueprints. There is a lot of boilerplate code that is always there. Over time, you get used to seeing past it and it blends into the background.

Right-click on the function and select Go To Definition.

This will take you to the implementation of the function in the .cpp

(Sorry for the small font, lack of syntax highliighting, and lack of line numbers - I may move this article to a differnt blog platform)

void UKismetSystemLibrary::PrintString(UObject* WorldContextObject, const FString& InString, bool bPrintToScreen, bool bPrintToLog, FLinearColor TextColor, float Duration) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // Do not Print in Shipping or Test

UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, false); FString Prefix; if (World) { if (World->WorldType == EWorldType::PIE) { switch(World->GetNetMode()) { case NM_Client: Prefix = FString::Printf(TEXT("Client %d: "), GPlayInEditorID - 1); break; case NM_DedicatedServer: case NM_ListenServer: Prefix = FString::Printf(TEXT("Server: ")); break; case NM_Standalone: break; } } } const FString FinalDisplayString = Prefix + InString; FString FinalLogString = FinalDisplayString;

static const FBoolConfigValueHelper DisplayPrintStringSource(TEXT("Kismet"), TEXT("bLogPrintStringSource"), GEngineIni); if (DisplayPrintStringSource) { const FString SourceObjectPrefix = FString::Printf(TEXT("[%s] "), *GetNameSafe(WorldContextObject)); FinalLogString = SourceObjectPrefix + FinalLogString; }

if (bPrintToLog) { UE_LOG(LogBlueprintUserMessages, Log, TEXT("%s"), *FinalLogString); APlayerController* PC = (WorldContextObject ? UGameplayStatics::GetPlayerController(WorldContextObject, 0) : NULL); ULocalPlayer* LocalPlayer = (PC ? Cast<ULocalPlayer>(PC->Player) : NULL); if (LocalPlayer && LocalPlayer->ViewportClient && LocalPlayer->ViewportClient->ViewportConsole) { LocalPlayer->ViewportClient->ViewportConsole->OutputText(FinalDisplayString); } } else { UE_LOG(LogBlueprintUserMessages, Verbose, TEXT("%s"), *FinalLogString); }

// Also output to the screen, if possible if (bPrintToScreen) { if (GAreScreenMessagesEnabled) { if (GConfig && Duration < 0) { GConfig->GetFloat( TEXT("Kismet"), TEXT("PrintStringDuration"), Duration, GEngineIni ); } GEngine->AddOnScreenDebugMessage((uint64)-1, Duration, TextColor.ToFColor(true), FinalDisplayString); } else { UE_LOG(LogBlueprint, VeryVerbose, TEXT("Screen messages disabled (!GAreScreenMessagesEnabled). Cannot print to screen.")); } } #endif }

Depending on your experience with c++ and Unreal, this may be easier or harder to read. But it is fairly straight forward to understand... ultimately it prints to the log file and/or the screen.

GEngine->AddOnScreenDebugMessage is the actual call that shows the text on the screen.

GEngine is a pointer to the global engine singleton.

Alson note the entire function is enclosed in

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // Do not Print in Shipping or Test

which removes it from shipping or test builds. So dont rely on it - if you package up your game and ship it, it will not print anything.

Now lets look at a few other key techniques we can use to help us on our journey. (Again, these are probably very obvious to seasoned developers)

Searching -

Ctrl-F : Find in Document

Ctrl-Shift-F : Find in Project or Solution

Being able to search across the codebase is invaluable. If you want to see where a function is defined, how many times it is called, who uses it.. then searching is your friend. This is probably your number one code spelunking tool. You can search the current project (game or engine), or the entire solution.

Not only can you search the current file, you can search the current project or across the entire solution. For example, if you are interested in the SetSimulatePhysics BP node on the SkeletalMeshComponent, then you can search for all instances of it across the entire codebase.

You will have a large number of results:

When you scan the results, notice it lists what file the search hit is in. In this case, noticethat there are really only two results on the Source\Runtime\Private\SkeletalMeshComponentPhysics.cpp.

Breakpoints

Breakpoints are another important tool. You can use breakpoints to stop Unreal during execution. While on a breakpoint, you can inspect the current values of variables and see the call stack.

Remember - To be able to set breakpoints in engine code, you need to run a Debug Build and have symbols available:

Select a DebugGame Editor build target using the VS toolbar :

If you are using a Launcher(Rocket) engine, to get the symbols you must download the ProgramDatabase (.pdb) files which contain the symbols

For illustrative purposes, lets set a breakpoint in PrintString (just click on left side of code window to set):

Now if you run you project in the editor and call PrintString from a BP node, the breakpoint will fire and you can see a stack trace and use the watch widow (just drag a variable to the window to inspect:

This concludes the first article - I may update it with more info or possibly post an additional article ...

(Also - let me know if you have any suggestions/corrections and I will update)


4,060 views

© 2016 by Casual Distraction Games, LLC