Unreal Engine Compilation System Summary

[USparkle Column] If you have special skills, love to "do some research", are willing to share and learn from others' strengths, we look forward to your joining, let the sparks of wisdom collide and intertwine, and let the transfer of knowledge continue!

I. Introduction

Many people have been saying that Unreal Engine is much more difficult to learn than Unity. One of the reasons is that C++ itself is difficult. Another reason is that Unreal has written its own compilation system and encapsulates C++ code in order to achieve reflection. Therefore, even if you have a good grasp of the basics of C++, it is still difficult to understand the meaning of the code. This article will introduce what Unreal Engine does for compilation and reflection, help developers who are new to Unreal Engine understand and quickly get started with development, and provide a pitfall avoidance guide. This article involves how to avoid header files, macros, etc. during the project development process. Strange error reports, solving common compilation problems, understanding Unreal Engine's modular code management method, understanding the engine compilation and startup process, creating plug-ins, referencing third-party libraries, and referring to engine code design to obtain rapid development tips, etc. The full text is about 20,000 words, so I hope you can read it patiently.

2. Introduction

When we create an ordinary C++ empty project, the general steps are to configure the platform and version, add code, create a mainfunction entry, right-click the project and click Build or regenerate, or directly click Run, and then you can test your own The code and functions are gone.

Under normal circumstances, there is no need to click Rebuild, especially when using UE4. This must be used with caution, because it will take a long, long time to compile. But unless you encounter a strange compilation error and you still have problems after ensuring that the configurations I mentioned later are correct, click Regenerate to see if it succeeds. Here is a picture to illustrate:

The same is true for UE4 running projects, but the difference lies in the process of clicking "Build". First, let’s analyze the UE4 project file structure, otherwise it will be difficult to understand.

Shown below is what a UE4 project looks like with C++ (projects without C++ will not be described here): the number of folders may be more or less, depending on the compilation target and plug-ins, it doesn’t matter.

When you right-click the project and click the Generate Visual Studio Project option ( if there is no such option, check whether the Epic Games Launcher needs to be updated ), the Visual Studio project will be generated. After the Visual Studio project is generated, .vs and Binaries folders will appear. The above mentioned about using Rebuild with caution. Here is a replacement method: delete the Binaries, Intermediate and .vs folders, and click Generate Visual Studio Project again to open the VS generated project. . This avoids problems that Rebuild may cause in recompiling the engine.

In addition, under the Config folder are the engine and project configuration files. Be careful not to delete them. The Content folder is the directory of game content resources; Plugins is the directory of plug-ins that the project depends on; Saved saves some cached data of the project, including performance debugging, files generated by the command line, saved game data and packaged Cook data, etc., which can be deleted; Source is Project C++ code directory.

Open VS to view the project structure, which are:

The Engine folder contains the engine source code; the Game folder contains project code, including plug-ins; and the Programs folder contains two important projects: UnrealBuildTool (compilation tool) and UnrealHeaderTool (header file parsing tool), namely UBT and UHT.

The amount of code in Unreal Engine is very scary, so it needs to be managed in a more professional way. Unreal Engine uses a modular approach to manage code. Each module references and depends on each other, and the corresponding modules are loaded recursively through references, and the tools for managing these modules It is UBT, UHT is used for header file generation and parsing.

Another reason to use the UBT management module is to facilitate cross-platform, so that we do not need to make corresponding configurations for each platform, which is inconvenient and prone to problems, and is not friendly to developers. With UBT, you only need to configure it once in the corresponding CS file and then apply it to multiple platforms. UBT will do the cross-platform work for you.

Detailed introduction follows.

Using Tools
The following tool environments are used for code analysis:

  • Windows10
  • UE 4.26
  • Visual Studio 2019

三、UBT(UnrealBuildTool)

So far, we have roughly understood that UBT is a tool for Unreal Engine to manage various modules, but it does not compile the code. It is only responsible for collecting information between modules and then telling the compiler to compile according to the platform and compilation target. The UBT source code can be found under Programs\UnrealBuildTool\ in the solution.

UBT samples NMake Build System. We can see the relevant settings in NMake of the project properties. What is shown here is what the Clear event will execute when we right-click Build and Rebuild, as shown below:

We follow the path it indicates, and then we will find the Build.bat file. Open it and view the content:

@echo off
setlocal enabledelayedexpansion

REM The %~dp0 specifier resolves to the path to the directory where this .bat is located in.
REM We use this so that regardless of where the .bat file was executed from, we can change to
REM directory relative to where we know the .bat is stored.
pushd "%~dp0\..\..\Source"

REM %1 is the game name
REM %2 is the platform name
REM %3 is the configuration name

IF EXIST ..\..\Engine\Binaries\DotNET\UnrealBuildTool.exe (
        ..\..\Engine\Binaries\DotNET\UnrealBuildTool.exe %*
        popd

        REM Ignore exit codes of 2 ("ECompilationResult.UpToDate") from UBT; it's not a failure.
        if "!ERRORLEVEL!"=="2" (
            EXIT /B 0
        )

        EXIT /B !ERRORLEVEL!
) ELSE (
    ECHO UnrealBuildTool.exe not found in ..\..\Engine\Binaries\DotNET\UnrealBuildTool.exe 
    popd
    EXIT /B 999
)

The essence of discovery is to run UnrealBuildTool.exe. In other words, pass configuration parameters to UnrealBuildTool and run UnrealBuildTool.exe.

The parameters come from the above options, including project name, compilation target, 32-bit or 64-bit and other information. Then we find the entry function from the UnrealBuildTool project code, as follows:

It can be seen that the corresponding platform and other information will be accepted and parsed. The subsequent content is to read and analyze the Target.cs and Build.cs files, and then call the compilation tool to compile the project. As for the details, there is no need to go into details. It feels like understanding this step of UBT. Will suffice.

Module configuration
has been mentioned several times, but it has not been introduced in detail. Because the content of the module is very large, I will introduce it in detail later, but here I want to briefly introduce the Build.cs under each module and the Target.cs in the project. document.

Let’s look at the basic structure of the module:

Three files that must exist for each module:

  • Module name Build.cs file, used to provide module information for UBT
  • The module core class inherits from IModuleInterface and implements two core methods: StartupModule and ShutdownModule, which will be executed when the engine starts to load the module. It is used to initialize module information and generally does not need to be changed.

Target.cs only exists in the project. It is similar to project settings, configuration targets and extended dependency modules, which will be explained later.

Generate Visual Studio Project

The Generate Visual Studio Project option is very useful. When we open the VS project, we will find that the Source in the local disk file and the content displayed by VS are not consistent, because Unreal Engine compilation will ignore the SLN file, and the meaning of SLN is just It is convenient to open and write code without viewing the file structure under Source. If you delete a class in the VS project, it will still exist in the local folder on the disk. Please make sure to delete the local file at the same time, or when you change or move a class file Path , the local file has not actually changed. If you do not move it through VS at this time, but directly move the file in the local folder, VS will prompt that the file reference cannot be found. Newbies learning Unreal Engine will definitely I was troubled by this problem at first, but later I found out that it would be better to just re-Generate Project Files ~ That’s the end of the first pitfall avoidance guide.

Generally, as long as you modify the [ModuleName].Build.cs file or move the file, you need to generate a solution file for the IDE. Sometimes you don't need to. If something goes wrong, just click on it. The following three methods have the same effect:

  1. Run GenerateProjectFiles.bat.
  2. Right-click the project's .uproject file and click Generate Project Files .
  3. In the Unreal Editor, click File > Refresh Visual Studio Project .

In addition, the essence of Generate Visual Studio Project is to execute the command with parameters of UnrealBuildTool.exe, so when you click Build in Visual Studio and click Generate Visual Studio Project at the same time, you will be prompted that it is running. Of course, these two commands are different. The parameters are different.

To summarize the UBT workflow
, let’s talk about the work of the Unreal Project click-build process:

  1. UBT reads the Build.cs file of each module to obtain the previous dependencies of each module (if the code has not changed, the module will be skipped)
  2. UBT calls UHT (header file parsing) to generate generated.h and gen.cpp files based on reflection attribute tags (UCLASS, UFUNCTION, UPROPERTY, etc.), which are used to expose information of this class to the blueprint.
  3. UBT calls MSBuild to compile C++ code through dependencies

The inconvenience of UBT configuration code.
Since UBT is written in C# code, there is no smart prompt in the C++ project, and the code inside cannot be transferred to the definition. Although there is not much code to be written, it is still very inconvenient for novices. It's even more unfriendly. Is there a way to solve the prompt problem? have. Let’s talk about it later, this is not to be trivial, but the plug-ins to be introduced can not only be used to prompt UBT development.

4. UHT (UnrealHeaderTool)

The purpose of the text parsing tool UnrealHeaderTool is to serve the Unreal Reflection System, which is the implementation principle of the blueprint. (To put it simply, the reflection system obtains class information at runtime, including class attributes, methods, attribute types, names, access permissions, method names and return values, etc. Obtaining class information can do many things, including through the name Call class methods, etc.)

The generated.h and gen.cpp files are generated based on the reflection attribute tags (UCLASS, UFUNCTION, UPROPERTY, etc.), which are used to expose the information of the class to the blueprint. Any class that inherits from UObject will generate the generated.h and gen.cpp files.

The main job of UBT is to use the attributes, methods, classes and other macros manually marked by developers to identify class information, and then encapsulate the class information into the code of gen.cpp. Of course, it also includes modifications to the constructor, destructor and other contents. Managed using Unreal's own garbage collection system .

Let’s see how it obtains class information:

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


UCLASS()
class TEST_API AMyActor : public AActor
{
    GENERATED_BODY()

public:    
    // Sets default values for this actor's properties
    AMyActor();
    ~AMyActor();
    int a;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:    
    // Called every frame
    virtual void Tick(float DeltaTime) override;
    UFUNCTION(BlueprintCallable)
        static void StaticFun();

    UFUNCTION(BlueprintCallable)
        void SetMesh(UStaticMesh* m);
    UFUNCTION(BlueprintCallable)
        UStaticMesh * GetMesh();
    UPROPERTY(EditAnywhere,BlueprintReadWrite)
        TSoftObjectPtr<AActor> ptr;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
        UStaticMesh *mesh;
};

You can see that the attributes, methods, classes and other contents of the most basic Actor class are marked by some special macros, including: UCLASS(), UPROPERTY(), UFUNCTION(), etc. Of course, there are more than these, and there are many other less commonly used ones. At the same time, you can also see that some properties and methods are not marked by macros. Their main differences are as follows:

  1. As mentioned above, the function of these macros is to serve the reflection system and expose class information to the reflection system. It is also the basic condition for the blueprint to be accessible. If macros are not added, they cannot be recognized by the reflection system, let alone called in the blueprint. . Of course, if you need the blueprint to be able to perform operations such as changing attributes, you also need to add labels within macro brackets for further modification.
  2. When a class attribute is modified with a macro, Unreal's garbage collection system can manage the attribute. UObject* member variables that are not modified by the UPROPERTY macro or are not referenced in the AddReferencedObjects function cannot be recognized by the Unreal engine and will not be managed by the garbage collection system. , so be careful when using pointers.

Specifically:

Each item has optional parameters, which mainly tell the blueprint about the class/function/structure and other content information. Different parameters have different performances in the blueprint.

The following is an introduction to the type modifiers in the macro body, which correspond to UCLASS(), UINTERFACE(), UFUNCTION(), UPROPERTY() and USTRUCT() respectively. There are many specific parameter items, but in fact most of them are not used, so here I will introduce the commonly used ones:

For UCLASS
when marked as:

The effect after running is that blueprint subclasses cannot be created:

After changing to: Blueprintable, you can create it, which is also the default option of inheriting Actor:

If not written, the default is NotBlueprintable:

But Actor is Blueprintable by default. The reason lies in the definition of Actor:

The Actor class is marked with Blueprintable, so subclasses inherit this option. Therefore, when a subclass inherits from Actor, it is Blueprintable even if it is not written. In other words, some properties can be inherited, and the specific ones can be viewed in ObjectMacros.h .

Other commonly used ones:
BlueprintType: This class variable can be created in a blueprint.
Const: All functions and properties of this class should be Const, and the Const label can inherit.
Abstract: This class is an abstract class.

Note: The consistency between the blueprint and the C++ code itself should be maintained here. That is, if you add Abstract to UCLASS, you should also write this class as an abstract class. Otherwise, various problems will occur, including the following UFUNCTION. If it is BlueprintPure modification, you should add a Const modification function, as shown below.

For UFUNCTION
, UFUNCTION (BlueprintCallable) is commonly used. Without BlueprintCallable, the blueprint cannot be called BlueprintPure. This macro means a pure function, but it cannot be understood as related to the pure virtual function in C++. In fact, these two are not In any relationship, I was trapped in this cycle and couldn't extricate myself.

First of all, the function marked by BlueprintPure must have a return value, because there are no input and output nodes, see the figure below. Secondly, the function of BlueprintPure is somewhat similar to the function decorated with Const: that is, the member variable value of the class cannot be changed, but its restrictions are not like Const. If you change the value, an error will be reported. However, please do not use the function marked as BlueprintPure. Be sure not to change the variable value of its class, take a look at the following example:

UFUNCTION(BlueprintCallable)
void constBlueprintPure() const;

UFUNCTION(BlueprintCallable)
int constBlueprintPureWithValue() const;

UFUNCTION(BlueprintPure)
int blueprintPure();
int AMyActor1::blueprintPure()
{
return 0;
}
void AMyActor1::constBlueprintPure()const
{

}

int AMyActor1::constBlueprintPureWithValue()const
{
return 0;
}

The corresponding appearance in the blueprint: It is somewhat different from the ordinary function. It is green and has no input and output nodes. As for the return value, it is void, otherwise it cannot be called in the blueprint.

This is the true face of Pure in the function: a function marked by Const, so do not change the value of the class member variable in the function marked Pure when you use it in the future.

Introduction to
each excerpt of UPROPERTY:

  • Const: const constant
  • VisibleAnywhere: Properties panel is visible but not editable
  • BlueprintReadOnly: Read-only in blueprint
  • BlueprintReadWrite: Readable and writable
  • Category: group name
  • EditDefaultsOnly: Can only be edited in the properties panel
    ...

Each item has many attributes. For example, the attribute (UPRPOPERTY) can be modified by EditAnywhere and VisibleAnywhere, but only one of these two can exist at the same time. The reason is that these two attributes belong to the same enumeration type. We can go to the definition Check everywhere to ensure that similar errors do not occur. In addition, you will become familiar with it if you use it multiple times.

Please refer to the link below for details:

Meta
is generally used to rename blueprint nodes, set default values, limit ranges, etc., for example:

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI", meta = (ClampMin = "1", ClampMax = "100"))
int32 RowCount;

GENERATED_BODY()
You may also see GENERATED_USTRUCT_BODY or GENERATED_UCLASS_BODY, as well as GENERATED_UINTERFACE_BODY and GENERATED_IINTERFACE_BODY.

First of all, their function is to encapsulate the code written by the engine for you. Including overriding constructors, etc. What is the difference between them? Just take a look at the definition:

You will find that the four macros mentioned above ultimately point to GENERATED_BODY_LEGACY and GENERATED_BODY. The essence of GENERATED_UCLASS_BODY, GENERATED_UINTERFACE_BODY and GENERATED_IINTERFACE_BODY is GENERATED_BODY_LEGACY, and the essence of GENERATED_USTRUCT_BODY is GENERATED_BODY. Now UCLass and UStruct generally use GENERATED_BODY. If you want to use GENERATED_UCLASS_BODY and GENERATED_UST RUCT_BODY also has the same effect.

By the way, GENERATED_BODY_LEGACY() has more ATestActor(const FObjectInitializer& ObjectInitializer); than GENERATED_BODY, a constructor with parameters .

Some constructors contain FObjectInitializer & ObjectInitializer. These are early versions of Unreal and are basically no longer used. Of course, for the sake of compatibility, it is no problem to write the constructor like this. You will know the answer by looking at the definition of UObject:

As for what these macros do specifically, Teacher Dazhao has written it very clearly in his reflection article. If you are interested, you can read "InsideUE4" UObject (8) Type System Registration-CoreUObject Module Loading" .

Also, be sure to note that GENERATED_BODY must be placed at the beginning of the class, in addition to adding friend class such as:


The function of the module name_API macro in front of other classes: used to expose this class for other modules to access. If not, other modules cannot obtain the information of this class, even if the module is introduced in Build.cs .

5. Create modules

Module is the basic unit of engine management code. Plug-ins and games must contain at least one module. When the project is created, the main game module will be automatically generated.

When a plug-in is created in the engine, there will also be a default module, like this: Currently there is no efficient way to manually create a module, other than creating the corresponding folder and template file.

This is the most basic structure of a module. To create a new module, you can only create a folder first, and then add the files corresponding to the module name, Build.cs, ModuleName.h and cpp.

After creating this template, remember to right-click the project's .uproject file and click Generate Project Files. It’s said above.

Write Build.cs file.
Contents in standard Build.cs:

using UnrealBuildTool;
public class Menu3 : ModuleRules
{
    public Menu3(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
        PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore","GameModule"});
        //PrivateDependencyModuleNames.AddRange(new string[] {  });
        // Uncomment if you are using Slate UI
    PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
    }
}

The core thing that needs to be changed is the dependent module array PublicDependencyModuleNames or PrivateDependencyModuleNames.

Here is a quote from Teacher Dazhao: Each module has Public and Private folders. If a class is written on the outside (not in the Public folder), it will be considered Private . If it contains a module, it still cannot reference a class. To check if the header class file is in the Public folder:

If you can use Private inclusion, try to use Private to reduce module dependencies and reduce compilation time.

Other sequences:

  • PublicIncludePathModuleNames和PublicDependencyModuleNames

Both enable this module to include header files in the Public directory of other modules to call functions. However, PublicIncludePathModuleNames can only call functions whose definitions are all in the header file. Here we need to distinguish between what is a declaration (without implementation) and what is a definition (with implementation). However, if it is defined in a header file, redefinition errors will easily occur and will slow down the compilation unless modified with Static or Inline.

example:

// Class.h
voidA(){B();// 假设B的定义在.cpp文件中,那么这时A的函数实现由B组成,但B不在头文件中.
}
//则此时使用IncludePathModuleNames时,无法调用函数A。(报错:无法解析的外部符号B)

So the summary is that PublicIncludePathModuleNames is deprecated.

  • PublicIncludePaths和PublicDependencyModuleNames

PublicIncludePaths only includes the corresponding directory, not the header file inside. If the path in your #include is very long, you can use this method to avoid writing the corresponding header file path when including:

   PrivateIncludePaths.AddRange(
            new string[] {
                 "ShooterGame/Private",
                 "ShooterGame/Private/UI",
                 "ShooterGame/Private/UI/Menu",
                 "ShooterGame/Private/UI/Style",
                 "ShooterGame/Private/UI/Widgets",
            }
        );

Equivalent to appending the include path in a normal C++ project:

These are the commonly used parameters. The specific parameters can be accessed:
https://docs.unrealengine.com/5.0/zh-CN/module-properties-in-unreal-engine/

DLL and LIB library imports are introduced separately below. The module attribute DLL and LIB library imports are introduced separately below.

Write ModuleName.h and cpp files

//h
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FModule1Module : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
//cpp
#include "Module1.h"
#define LOCTEXT_NAMESPACE "FModule1Module"
void FModule1Module::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FModule1Module::ShutdownModule()
{
// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FModule1Module, Module1)

The two methods here are basically fixed writing methods: StartupModule and ShutdownModule, which are executed when the module is loaded and unloaded respectively. Generally, there is no need to modify anything here. You can also choose to write logic such as loading DLL when the module starts. As for when the module is loaded, it is configured in ulpugin or uproject.

Pay attention here to the IMPLEMENT_MODULE(FModule1Module, Module1) macro, which is preceded by the class name and followed by the module name.

Other things to note

  • When you need to reference a class of another module in this module, after configuring Build.cs correctly, you must also include the header file correctly before it can be used.

In order to do the above two steps correctly, the way I usually choose is to query the official website API, like this: FDesktopPlatformModule

The official website will tell you the name of the imported module and the way to include it. However, here I cannot find the header file using the official website method. Only by writing everything after the path Developer can the header file be included correctly. Like this, the red ones are errors. The way to write it is the way it is written on the official website. The following is the correct way to write it:

path:

This example tells us: Don’t trust the official website’s prompts too much. It may be that they are not updated or are missing.

Generally, the content in include is the path name in classes/public/private after the module name. The Classes folder is a legacy of history and represents Public. Now it is generally Public/Private. See the path in the picture above.

  • In addition, try not to copy the code, especially the overall class, as it is too easy to cause problems, and copying the code can easily cause many modules to be recompiled, which is extremely slow.

Reference external LIB, DLL libraries

  • How to reference LIB in ordinary Visual Studio projects

Introducing the LIB library requires header files and LIB libraries.

The header file is added to:

LIB added to:

LIB path (optional: you can also add it in front of glfw.lib above, no need to write it below)

  • UE reference method

Introduce the LIB library and add: to the module's Build.cs:

PublicIncludePaths.Add(Path.Combine(ThirdPartyPath));//头文件路径
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "CaptureScreen.lib"));//LIB库路径

Introduce the DLL library and add in Build.cs:

PublicIncludePaths.Add(Path.Combine(ThirdPartyPath));//头文件路径
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "CaptureScreen.lib"));//LIB库路径
PublicDelayLoadDLLs.Add("CaptureScreen.dll");//DLL名字

Then dynamically load the DLL somewhere. Before use, you can write it into the StartupModule function:

FString BaseDir = IPluginManager::Get().FindPlugin("ThirdPartyLibrary")->GetBaseDir();
// Add on the relative location of the third party dll and load it
FString LibraryPath;
 #if PLATFORM_WINDOWS
  LibraryPath = FPaths::Combine(*BaseDir, TEXT("Source/ThirdParty/ThirdPartyLibraryLibrary/x64/Release/TestDLL.dll"));
 #elif PLATFORM_MAC
        LibraryPath = FPaths::Combine(*BaseDir, TEXT("Source/ThirdParty/ThirdPartyLibraryLibrary/Mac/Release/libExampleLibrary.dylib"));
 #endif // PLATFORM_WINDOWS
  vois * ExampleLibraryHandle = !LibraryPath.IsEmpty() ? FPlatformProcess::GetDllHandle(*LibraryPath) : nullptr;
        if(ExampleLibraryHandle)
          //load successfully

6. Create plug-ins

Plug-ins are generally created through the engine to create a lot of engines.

In fact, a plug-in is a collection of modules, with at least one module. The basic structure is as follows: Module1 was added by myself:

The management of modules by plug-ins is written in the uplugin file. Example:

{
        "FileVersion": 3,
        "Version": 1,
        "VersionName": "1.0",
        "FriendlyName": "MyPlugin",
        "Description": "",
        "Category": "Other",
        "CreatedBy": "",
        "CreatedByURL": "",
        "DocsURL": "",
        "MarketplaceURL": "",
        "SupportURL": "",
        "CanContainContent": true,
        "IsBetaVersion": false,
        "IsExperimentalVersion": false,
        "Installed": false,
  "Modules": [
    {
      "Name": "MyPlugin",
      "Type": "Runtime",
      "LoadingPhase": "Default"
    },
    {
      "Name": "Module1",
      "Type": "Runtime",
      "LoadingPhase": "Default"
    }
  ]
}

Most of the above information describes plug-in information. The key is the Modules section, which defines key information such as the name of the module and the loading time.

In addition, you can also add the following information: (but generally not required)

The last AdditionalDependencies should be configured in the module's Build.cs as much as possible, and do not write them here (PublicDependency ones).

Type describes whether this module should be loaded when packaging to a certain platform. For example, when packaging a game, you do not need Editor-related module code.

Specifically:

namespace EHostType
{
    enum Type
    {
        Runtime,
        RuntimeNoCommandlet,
        RuntimeAndProgram,
        CookedOnly,
        UncookedOnly,
        Developer,
        DeveloperTool,
        Editor,
        EditorNoCommandlet,
        EditorAndProgram,
        Program,
        ServerOnly,
        ClientOnly,
        ClientOnlyNoCommandlet,
        Max,
    }
}

Commonly used ones are:
Runtime (any compilation target will load this module)
Editor (this module will only be loaded under the editor)
LoadingPhase, which describes when to load the module. This is more important. Modules that depend on other modules should be loaded after the dependent modules. Otherwise, an error will be reported and the corresponding module cannot be found.
https://docs.unrealengine.com/4.27/en-US/API/Runtime/Projects/EHostType__Type/

Specifically:

namespace ELoadingPhase
{
    enum Type
    {
        EarliestPossible,
        PostConfigInit,
        PostSplashScreen,
        PreEarlyLoadingScreen,
        PreLoadingScreen,
        PreDefault,
        Default,
        PostDefault,
        PostEngineInit,
        None,
        Max,
    }
}

This is sorted by time, and the default is Default. By the way, Default usually loads these modules when the engine is started to about 75%, which is when:

EarliestPossible is loaded before seeing this black loading interface, so it is almost never used. If you want to know when other loading interruptions are made, just test it.

The last is the management plug-in, which is implemented in uproject and is introduced below.

7. Create multiple game modules

The way to create a module in a game project is the same as in a plug-in. The basic structure of the module is the same, but the only thing to note is that the macros in the corresponding main module class will change:

The game module (macro used by non-main module) is IMPLEMENT_GAME_MODULE(FDefaultGameModuleImpl, GameModule);, not IMPLEMENT_MODULE(FMyPluginModule, MyPlugin) in the plug-in. For other changes, you need to pay attention to the correct name matching.

The macro used in the main game module (default game module) is IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, Test, "Test" );.


Let’s take a look at the contents of uproject :

{
"FileVersion": 3,
"EngineAssociation": "4.26",
"Category": "",
"Description": "",
  "Modules": [
    {
      "Name": "Test",
      "Type": "Runtime",
      "LoadingPhase": "Default"
    },
    {
      "Name": "GameModule",
      "Type": "Runtime",
      "LoadingPhase": "Default"
    }
  ]
}

The above version information is not the focus of the introduction, but mainly the modules inside. Here I created a GameModule game module. The attribute configuration inside is consistent with the above. The key is that if the GameModule module is not written in it, the game engine will not recognize it. to this module (there will be no GameModule module code in the engine C++ folder, so the module content cannot be referenced, even if the GameModule module is referenced in Build.cs of the main module), therefore, the module you wrote must be added here . Consider a question here: If GameModule is not referenced by the main module, what will happen during packaging.

Target.cs

using UnrealBuildTool;
using System.Collections.Generic;
public class MyProjectTarget :TargetRules
{
    public MyProjectTarget(TargetInfo Target) : base(Target)
    {
        Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V2;
        // 此处为其他属性
    }
}

In articles about Unreal modules, few talk about the role of the Target.cs file. Projects generally create two by default, one is Target.cs and the other is Editor.Target.cs.

I have just been saying that the module reference method depends on Build.cs, but no module depends on the main game module. How is the main game module loaded into memory? This relies on ExtraModuleNames.AddRange( new string[] { in Target.cs } );. By default, the name of the main game module is added, which is why the main game module can be added to the engine.

Going back to the question mentioned above, if the module you write is not referenced by the main module, what will happen when packaging.

I tested several situations:
uproject adds module GameModule information, main module Build.cs adds GameModule reference, ExtraModuleNames does not add GameModule module, test package exists GameModule:

uproject adds module GameModule information, the main module Build.cs does not add GameModule reference, the GameModule module is not added to ExtraModuleNames, and the GameModule does not exist in the test package:

uproject adds module GameModule information, the main module Build.cs does not add GameModule reference, the GameModule module is added to ExtraModuleNames, and the test package contains GameModule.

uproject does not add module GameModule information, the main module Build.cs does not add GameModule references, and the GameModule module is added to ExtraModuleNames. When opening the engine, an error is reported directly.

From the comparison of the above situations, we can see that if the custom module is not referenced, it will not be loaded during packaging. uproject only determines whether the module is visible to the engine and has nothing to do with packaging configuration.

Then according to different compiled Targets, the engine will execute different Target.cs. If it is not implemented, it will execute the base class content in Target.cs .

The difference between them is Type = TargetType.Client;

          Type = TargetType.Game; 
        //Type = TargetType.Editor; 
        //Type = TargetType.Client;
        //Type = TargetType.Server; 
        //Type = TargetType.Program;
        //UnrealTargetConfiguration(Unknown, Debug, DebugGame, Development, Shipping, Test)
        //UnrealTargetPlatform(Win32, Win64, Linux, Mac, PS4..)
          DefaultBuildSettings = BuildSettingsVersion.V2;

For details, please refer to:
https://docs.unrealengine.com/4.27/zh-CN/ProductionPipelines/BuildTools/UnrealBuildTool/TargetFiles/

8. Build.cs, Target.cs, uplugin and uproject

Summarizing the compilation of UE4 from these files:
Modules are determined by Build.cs to determine interdependencies. If the module is in a plug-in, the loading timing and compilation target are defined in uplugin. If it is a game project module, they are defined in uproject.

Target.cs is a file that will work when packaged to the corresponding platform. If your module is not dependent, you need to add it here. Of course, if the module is dependent on other modules, you do not need to add it.

In addition to the game module, uproject also controls whether the plug-in is activated. If you change whether the plug-in is activated in the engine, uproject will dynamically generate the corresponding content. Generally, there is no need to manually add plug-in information.

9. Introduction to development tools

The latest version of Visual Studio
2022 has very smart prompts for C# code, but it is a pity that Unreal needs to be developed in C++.

VA_X
is the most common C++ code prompting tool. It has good prompting capabilities for unreal projects, but it is limited to the C++ part.

Resharper
uses C# to write code in UBT, so there is no prompt when configuring the module. Although the relevant code can be copied in the engine module, it is still very unfriendly. The powerful Resharper plug-in is needed here. The power of this plug-in is not only Not only can it prompt various macros in C++ and ordinary C++ syntax prompts, but it can also prompt configuration files such as Build.cs. With it, you no longer need to worry about module configuration issues, but its power It doesn't stop there:

  • Intelligent prompts for USF and HLSL files to facilitate and quickly write Shader code
  • It intelligently prompts whether the header file is used. If it is not used, it will be grayed out (IWWY, include what you use). The prompting ability is simply terrifying.
  • Prompts for all Unreal Engine macros, UBT code prompts, quick jumps, completion logic, etc.
  • Quickly create Actor and UObject template classes in VS. In the past, they were created from the engine, or copied and changed a lot of content. If they were wrongly corrected, a bunch of strange compilation errors would occur. With this function, development speed is greatly improved

Shown below:
Macro error or missing prompts:

Garbage collection tips:

Quickly generate class templates

Quickly create templates:

Wait, there are too many functions!

Speaking of which is enough to prove the power of this plug-in, but it also has problems, that is, it seriously slows down the startup speed of Visual Studio and consumes performance, so decide whether to use it according to your needs. Not to mention downloading, you need to install two plug-ins, resharper C++ and resharper C#.

Rider for Unreal Engine 2021.3
is a new tool. The prompts are the same as Resharper, and it is lightweight. The engine now supports it, but I am still used to using Visual Studio. Attached is the URL. If necessary, apply by yourself.
Rider for Unreal Engine

10. Summary

It took quite a long time to complete this article, but I still feel it is necessary to write it. It should be more or less helpful to developers who are new to Unreal Engine. In fact, there are still many things that have not been written, including PCH and some macro introductions. But what I hope everyone can get is my method of learning these things. Regarding the learning method, I mainly refer to the official website and forums, and then if you have the ability to circumvent the wall, read foreign tutorials, which are much better than domestic ones. Of course, Bilibili and Zhihu There are also many excellent tutorials on the Internet. Finally, if there are any errors or shortcomings, please give me your advice.


This is the 1454th article of Youhu Technology. Thanks to the author Xue Liuxing for contributing. Welcome to forward and share, please do not reproduce without the author's authorization. If you have any unique insights or findings, please feel free to contact us and discuss it together.

Author's homepage: Snow Meteor - Zhihu

Thank you again Xue Meteor for sharing. If you have any unique insights or findings, please feel free to contact us and discuss it together.

Guess you like

Origin blog.csdn.net/UWA4D/article/details/132597846