转自: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目标构建的。

参考TargetFiles

生成项目文件

首先,要做的是运行GenerateProjectFiles。但是......会发生什么?

  • Unreal Build Tool被构建了。
  • 批处理文件调用了类似如下的命令(取决于您的项目,Visual Studio版本,平台等)
    -ProjectFiles -nodummyconfigs -game -engine -2017 "-project=Path\To\Your\Project.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:\Path\To\Your\Engine\Build.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的细节。

理解UnrealBuildTool的更多相关文章

  1. 理解CSS视觉格式化

    前面的话   CSS视觉格式化这个词可能比较陌生,但说起盒模型可能就恍然大悟了.实际上,盒模型只是CSS视觉格式化的一部分.视觉格式化分为块级和行内两种处理方式.理解视觉格式化,可以确定得到的效果是应 ...

  2. 彻底理解AC多模式匹配算法

    (本文尤其适合遍览网上的讲解而仍百思不得姐的同学) 一.原理 AC自动机首先将模式组记录为Trie字典树的形式,以节点表示不同状态,边上标以字母表中的字符,表示状态的转移.根节点状态记为0状态,表示起 ...

  3. 理解加密算法(三)——创建CA机构,签发证书并开始TLS通信

    接理解加密算法(一)--加密算法分类.理解加密算法(二)--TLS/SSL 1 不安全的TCP通信 普通的TCP通信数据是明文传输的,所以存在数据泄露和被篡改的风险,我们可以写一段测试代码试验一下. ...

  4. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  5. 如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念

    一.前言     DDD(领域驱动设计)的一些介绍网上资料很多,这里就不继续描述了.自己使用领域驱动设计摸滚打爬也有2年多的时间,出于对知识的总结和分享,也是对自我理解的一个公开检验,介于博客园这个平 ...

  6. 学习AOP之透过Spring的Ioc理解Advisor

    花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...

  7. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  8. JS核心系列:理解 new 的运行机制

    和其他高级语言一样 javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过 ...

  9. 深入理解JS 执行细节

    javascript从定义到执行,JS引擎在实现层做了很多初始化工作,因此在学习JS引擎工作机制之前,我们需要引入几个相关的概念:执行环境栈.全局对象.执行环境.变量对象.活动对象.作用域和作用域链等 ...

随机推荐

  1. 剑指Offer_栈的压入序列是否有对应的弹出序列

    题目: 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序. 如:假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是 ...

  2. SprintBoot 实现上传下载

    本人在 .NET 转JAVA 的路上 ,也在学习SpringBoot相关知识,这里记录一下在Springboot中实现文件上传下载的核心代码 package com.file.demo.springb ...

  3. 阿里云监控RDS

    RDS性能监控API https://help.aliyun.com/document_detail/26280.html?spm=a2c4g.11186623.6.1576.341d7159uzLD ...

  4. ES6-面向对象

    1.老版的面向对象: function User(name,pass){ this.name=name; this.pass=pass; } User.prototype.showName=funct ...

  5. 在verilog中使用格雷码

    格雷码的一些知识: https://baike.baidu.com/item/%E6%A0%BC%E9%9B%B7%E7%A0%81/6510858?fr=aladdin 绿色框起来的是0--15的格 ...

  6. service---七月十九号实验

    目录 service---七月十九号实验 1 startService.bindService 2 分析生命周期变化 问题思考: service---七月十九号实验 1 startService.bi ...

  7. 5种处理Vue异常的方法

    原文: Handling Errors in Vue.js 译者: Fundebug 本文采用意译,版权归原作者所有 去年一整年,我都在使用最爱的-Vue.js- 来做项目.最近突然意识到,我竟然从来 ...

  8. javascript之BOM对象(二location对象)

    一.location对象提供和当前加载的文档相关的信息还有一些导航功能.location对象是window对象的属性,同时也是document对象的属性.window.location和documen ...

  9. Linux使用pt-archiver工具自动备份MySQL

    操作系统: CentOS 6.9 脚本语言: shell https://github.com/iscongyang/Practical/blob/master/shell-scripts/pt-ar ...

  10. 深浅拷贝、集合set、函数、日志

    #-----深浅拷贝---- import copy a = ["xiaoming",111,[5000,2000]] b = a print("b:%s" % ...