转自:https://zhuanlan.zhihu.com/p/57186557
介绍
虚幻引擎是当前比较流行的游戏开发引擎之一,许多流行的游戏都是虚幻引擎开发的。
然而“引擎”这个词在行业中的定义比较模糊,对不同的人会产生不同的意思。它是一个代码库?是一个编辑器?还是一个工具箱?
可能它全都是,然而却差了很多文档来让人正确地认识它。
以前的工作中经常涉及到整合第三方库到引擎中,我花了很多时间来理解虚幻的build过程。现在把结果写在这个文章里。
如果你不理解C++是如何编译的,我推荐你读一下这个网站: LearnCpp
虚幻引擎是什么?
我想试着用我自己的理解来阐述这一点。虚幻引擎是一堆C++代码,可以在不同的平台上(操作系统)以不同的方式编译。你可以用自己的游戏代码来拓展引擎代码(即在虚幻的基础上开发游戏),然而你的代码会非常依赖引擎代码。
当你从源码编译虚幻引擎,你会生成一个游戏客户端(运行在PC或者游戏机上的东西),一个游戏服务器(用于多人游戏),一个编辑器和许多其他小程序(在引擎源码中叫做Programs,即完成特定任务的特定程序)。这个编辑器可以让你做很多事情,包含可以设计场景的工具,还可以编写(拖拉)蓝图(虚幻引擎特有的一种脚本语言)。这些小程序包含一堆工具,比如Unreal Build Tool和Unreal Header Tool,这就是这篇文章我们要讨论的。
Unreal Build Tool是什么?
为了支持代码的多种编译形式和多平台支持,甚至将相同的代码编译成一个.lib或者一个.dll(用于支持热加载),虚幻开发团队决定开发自己的构建工具,这意味着不再使用传统的makefile或者MSbuild,而是使用Unreal Build Tool。因此在虚幻中进行C++开发以及整合第三方库,与传统方式比起来会有一些不同。
Unreal Build Tool由C#编写,且作为整个虚幻编译过程中第一个编译步骤。当你运行“GenerateProjectFiles”(一个批处理文件,用于在Window平台下生成Visual Studio的解决方案和工程),这第一个步骤就是在Source/Programs/UnrealBuildTool/UnrealBuiltTool.csproj工程下执行MSBuild来编译这个“Unreal Build Tool”。
所以,Unreal Build Tool 其实就是一个命令行程序,却可以完成很多事情,比如生成工程文件、执行Unreal Header Tool、为各种不同的平台个构建风格调用编译器(Compiler)和连接器(Linker)。这个过程后面还会详细解释。
Modules(模块)
虚幻引擎中的模块其实就是一系列源文件。可以是C++模块或者C#模块。C#模块使用.csproj(Visual Studio C#工程描述文件)作为它的工程文件。
对于C++模块,就有些奇怪了。模块使用一个“模块名.build.cs”文件来定义,这个文件跟vcxproj(Visual Studio C++工程描述文件)类似,一个Private目录用于存放私有的头文件和源文件;以及一个Public目录用于存放公开的头文件和源文件。
Public目录下的头文件默认会暴露给其他模块(在添加模块依赖后可以直接include这个目录下的文件),Private目录下的则不会(除非手动添加PrivateIncludePath)。
所以,创建一个C++模块的规则就是,在Engine/Source 或者 你的游戏工程/Source 下面创建一个新的目录并添加一个.Build.cs文件。这个模块对其他模块的依赖也需要在.Build.cs文件夹中配置好。
对于整合第三方代码也是一样,创建一个目录,一个 .Build.cs,并在 .Build.cs 文件中描述该模块如何构建以及哪些头文件需要暴露给其他模块。
一个C++模块可以被编译为一个整体(monolith),也可以编译为其他模块(一个可执行文件,Dll等)的一部分,又或是一个.dll用于热加载。这就是虚幻提供的灵活性。
参考Modules
Targets(目标)
虚幻中的一个Target可以被理解为前面提到的一个“编译形式”。
UnrealBuildTool支持构建多个目标类型: - Game游戏 - 一个独立的游戏 - Client客户端 - 与游戏相同,但不包含任何服务器代码。适用于网络游戏。 - Server服务器 - 与游戏相同,但不包含任何客户端代码。适用于网络游戏中的专用服务器。 - Editor编辑器 - 扩展虚幻编辑器的目标。 - Program程序 - 构建在虚幻引擎之上的独立实用程序。
目标是通过扩展名为.Target.cs的C#源文件声明的,并存储在项目的Source目录下。 可以通过它来设置一堆Target所依赖的定义和其他属性等。即使像UnrealHeaderTool这样的Unreal实用程序也是作为Program目标构建的。
生成项目文件
首先,要做的是运行GenerateProjectFiles。但是......会发生什么?
- Unreal Build Tool被构建了。
- 批处理文件调用了类似如下的命令(取决于您的项目,Visual Studio版本,平台等)
-ProjectFiles -nodummyconfigs -game -engine -2017 "-project=PathToYourProject.uproject" -Platforms=Win64+XboxOne+UWP64 -noSolutionSuffix - Unreal Build Tool会在引擎和游戏目录搜索所有带有.Build.cs拓展的文件来发现所有定义的模块。
- Unreal Build Tool会搜索所有带有.Target.cs拓展的文件来发现所有定义的目标。
- 它将生成一个包含所有目标作为构建配置和所有模块作为项目的解决方案。
C#项目只是源文件夹中的.csproj文件。C ++项目并不完全是“标准”项目。它不再调用MSBuild,而是调用UnrealBuildTool!
构建C++项目
在Unreal中构建C++项目时,您可以看到(基于vcxproj的NMakeBuildCommandLine属性)将调用与此类似的命令行:
C:PathToYourEngineBuild.bat TargetName Win64 Debug "$(SolutionDir)$(ProjectName).uproject" -waitmutex $(AdditionalBuildArguments) -2017
它的背后其实又调用了UnrealBuildTool!
那么,UnrealBuildTool在这儿的作用是:
- 编译目标。它在运行时编译了.Target.cs代码(使用C#编译器)来获取构建属性。这是UnrealBuildTool从中获取大部分定义和平台信息的地方。某些属性(例如bBuildEditor)表示你需要的是构建编辑器。它会创建一个WITH_EDITOR定义,然后由编译器转发到源文件。以实现源代码中的条件编译:#if WITH_EDITOR 条件编译。
- 解析所有依赖模块,包含来自.Target.cs和.Build.cs(模块)的依赖。
- 将编译所有依赖模块的Build.cs,以获取有关如何构建每个模块的额外属性。
- 解析哪些模块使用了共享编译头(即.Build.cs文件中包含SharedPCHHeaderFile属性,比如CoreUObject,Core,Engine等)。
- 解析哪些模块依赖于UObject模块。
- 对所有依赖于UObject的模块运行Unreal Header Tool,这时虚幻引擎会注入一些行为到你的类中,强制你在文件中加入由Unreal Header Tool生成的“.generated.h”头文件。
- 基于Unreal Header Tool生成的代码,解析所有Include路径。
- 基于解析后的路径、定义、外部库等,生成一系列会在目标环境执行的命令列表:
- 为共享预编译头调用编译器(CL.EXE)
- 调用编译器来编译源文件(CL.EXE)
- 调用链接器(LINK.EXE)
- 调用所有这些操作
显然,这些步骤比较简略。还有更多的细节和设置,比如你是否使用CLR,是否使用Mono,是否使用Clang等等。但大致的步骤和上面所讲的一样。
结论
我希望这篇文章可以帮助那些习惯使用标准构建工具的人了解UnrealBuildTool的细节。