现代游戏引擎 - 引擎工具链高级概念与应用(十四)

工具链大纲

工具链大纲

游戏制作一览(Glance of Game Production)

在现代游戏的开发过程中,设计师和艺术家往往需要使用大量的第三方工具来辅助进行游戏角色和场景的建模。
同时对于不同类型的游戏,游戏玩法和关卡设计也往往存在着巨大的差别。因此对于工具链来说,它需要实现和不同开发工具的通信也要考虑不同用户的使用需求。
除此之外WYSIWYG原则又要求开发者在引擎中的体验必须和实际游戏时完全一致的, 这对工具链的设计提出了更大的挑战。
适应不同的游戏类型
实际生产带来的挑战

世界编辑器

世界编辑器(world editor)是整合了游戏引擎中几乎所有功能的平台。以虚幻引擎为例,虚幻中的世界编辑器界面包括各种不同的面板以服务不同的开发者:
世界编辑器

编辑器视口(Editor Viewport)

viewport可以说是整个编辑器最重要的窗口,它是连接开发者与所构建的游戏世界的接口。
实际上在viewport中所展示的正是游戏世界在运行时的状态,从而方便开发者对游戏内容进行各种调整。
编辑器视口

可编辑对象(Editable Object)

在编辑器中所有的对象都是可编辑对象(editable object),开发者可以根据需要调整这些对象的位置、姿态、材质等属性。
因此游戏中所有的元素都必须抽象为这样的可编辑对象。
可编辑对象

在同一个游戏场景中可能有成千上万个对象,因此我们需要一些高效的管理工具。在编辑器中往往会使用树状结构或是分层的方式来管理场景中的对象,有时还会根据对象自身的特点设计相应的管理工具。
对象的不同视图

当开发者选中某个对象时需要使用schema来获取该对象自身的信息。
模式驱动的对象属性编辑

内容浏览器(Content Browser)

在编辑器中还需要实现content browser用来管理开发过程中设计的各种美术、场景资产。在游戏开发过程中很多的资产是可以重复利用的,
通过content browser可以方便地查看、检索以及分享现有的资产,从而提升游戏开发的效率
内容浏览器

编辑(Editing)

因此世界编辑器的核心功能是方便开发者去编辑游戏场景中的各种对象。
编辑

而在编辑器的实现中,首先也是最重要的功能是如何实现通过鼠标来选取物体。最简单的实现方法是使用渲染系统中的ray casting功能,利用鼠标的位置来发射光线并通过与物体bounding box求交来来选择物体。
这种实现的缺陷在于当物体比较复杂时bounding box是不能完全反应物体的几何形状的,此时使用ray casting的效率可能会比较低。另一种实现方法是在渲染流程中添加一个额外的选取帧,
为图像上每一个像素赋予一个物体编号,这样使用鼠标进行选取时只需根据物体编号进行查询即可。当然这种实现方式对计算机的硬件提出了更高的要求,同时需要注意在游戏发布时去掉这部分编辑环境下的代码。
鼠标选中

选取得到物体后一般还会对物体进行一些几何变换,包括平移、旋转和缩放等,这些操作的具体实现往往还需要根据使用者的习惯来进行设计。
对象转换编辑

对地形进行设计时需要结合高度场、地形纹理以及各种装饰件等。
地形

对于边界器来说地形设计最常用的工具是高度笔刷(height brush),如何对设计出的高度场进行平滑需要很多相关的经验。当然很多商业级引擎中还提供了自定义的工具来帮助设计师对效果进行定制。
高度笔刷

另一种常用的笔刷是instance brush,它通常用来在设计好的地形上添加各种装饰件。
实例笔刷

除此之外编辑器还需要提供各种环境元素的实现。
环境

规则系统(Rule System)

游戏中的各种对象需要按照一定的规则来组织起来,因此规则系统(rule system)也是编辑器的重要组件。
环境编辑中的规则系统

早期的游戏引擎往往不包括这样的系统,为了保证游戏世界的合理性需要设计师人工调整游戏中的各种对象。
而在现代游戏引擎中则需要提供自动化的规则系统,通过程序化的方式来保证游戏中的各种对象遵循相互之间的规则。
规则系统

编辑器插件架构(Editor Plugin Architecture)

显然整个世界编辑器是一个非常复杂的软件程序,我们很难直接在开发过程中实现所有需要的功能。因此在现代游戏引擎中一般会设计相应的插件系统来帮助用户根据自身的需求来丰富编辑器的功能。
实际上不仅是游戏引擎,很多现代软件都使用了类似的策略来允许用户对系统进行定制。
商业软件中的插件模块的示例

而对于游戏引擎而言,插件系统需要考虑的一个问题是如何对插件种类进行划分。我们可以按照游戏对象的种类(网格、粒子、动画等)对插件进行分类,也可以根据对象的内容(NPC、建筑、人群等)进行分类。
因此现代游戏引擎的编辑器往往需要支持这种矩阵式的分类方法,允许用户根据喜好来选择和定制插件。
系统和对象之间的交叉矩阵

在对插件系统进行整合时还需要考虑不同插件之间的版本问题。 不同的版本之间可以按照覆盖(covered)或是分布式(distributed)的方式进行协作。
多个插件的组合

除了上面提到的两种模式外,对于几何数据往往还会使用流水线(pipeline)的模式将几何体的操作进行分解;
而对于更加复杂的对象有时还会使用洋葱圈(onion ring)的模式同时和其他的插件进行协作。
多个插件的组合2

随着编辑器以及各种插件之间版本的迭代,插件系统一般还需要考虑版本控制的问题。
版本控制

设计叙事工具(Design Narrative Tools)

除了游戏资产的设计外,叙事(story telling)在整个游戏开发流程中同样是非常重要的一环。
叙事可以看做一个线性的过程,相关的游戏资产需要在一个时间轴上按照顺序进行调度。
游戏引擎中的讲故事

在虚幻引擎中使用了sequencer来跟踪游戏对象及其属性在时间轴上的变化。当我们把不同的对象利用sequencer在时间轴上组织起来就实现了简单的叙事。
音序器

要使用sequencer首先需要选中对象然后把sequencer绑定到对象上,然后选择相关的属性并利用关键帧设置这些属性的变化,最后利用插值就完成了叙事。
将对象绑定到轨迹
将对象属性绑定到属性轨迹
设置关键帧
设置关键帧2
沿着关键帧插入属性

反射和游戏逻辑(Reflection and Gameplay)

反射(reflection)是sequencer乃至整个游戏引擎编程中都非常重要的技术,通过反射我们可以让游戏引擎在运行阶段获取操作对象具有的各种属性。
实际上对于游戏开发流程而言,游戏引擎的开发者很难实现预判用户的需求。因此反射对于现代游戏引擎而言几乎是一个必备的工具。
反射和游戏逻辑

早期的游戏引擎是不基于反射技术来实现的,这导致开发者在进行编程时必须手动为游戏对象添加所有需要的属性和数据,使得工具链的开发很难与游戏开发进行匹配。
为获取更多特性而采用的硬代码方法
为获取更多特性而采用的硬代码方法2

反射是现代高级语言中引入的一个非常实用的概念。通过反射,系统可以获知每个新实现的类提供的接口。
一个常见的解决方案 - 反射

反射可以理解为沟通代码和工具之间的一座桥梁。
反射建立了代码和工具之间的桥梁

在C++中默认是不支持反射的,不过可以基于编译器来实现反射的功能。
如何在C++中实现反射
如何从代码中获取类型信息
如何从代码中获取类型信息2

在本课程提供的小引擎中使用了clang编译器来实现反射。
为什么Piccolo使用Clang
从AST生成架构
反射范围的精确控制
使用Marco来添加反射控件
反射访问器

除此之外,在小引擎中还使用了代码渲染(code rendering)的技术来自动生成相关的代码。
代码渲染
代码渲染2
代码渲染 - Mustache

协同编辑(Collaborative Editing)

协同编辑(collaborative editing)是现代游戏在开发过程中所需要面对的挑战。
在复杂的游戏场景中往往需要大量的开发工作者进行协作,如何保证协作开发中各种资源的合理调度并避免相互之间的冲突是一个相等复杂的问题。
大型项目的瓶颈
合并冲突是最大的问题

避免冲突最简单直接的方法是对整个任务按照内容进行分解,然后安排不同的开发者独立地进行开发。
但这种管理方式的问题在于有时很难进行合理的划分,而且在很多情况下不同的任务之间存在相互依赖。
如何减少冲突
拆分资产-分层的世界
拆分资产-分层的世界2

另一种常见的处理方法是直接对任务进行分解,但这种处理方法的效果同样不够理想,容易产生场景的割裂。
拆分资产-分割世界
拆分资产-分割世界2

在虚幻引擎中提出了OFPA的策略来生成大量的文件来进行管理。
每个参与者一个文件
一种特殊的拆分资产方法 - OFPA

实际上协同编辑的终极目标是允许不同的开发者同时在同一个系统上进行编辑。尽管这个问题仍然非常复杂,但它已经成为了现代大型软件系统的研究热点。
在一个场景中进行坐标编辑
如何同步我的操作
这是一个非常大的挑战
两个用户不能同时编辑同一个实例
两个用户不能同时编辑同一个资产
但锁并不是万能的
如何彻底解决这些问题
传统的工作流 VS 协作编辑工作流
服务器是最重要的角色

引用

参考文章

课程视频

课件PPT

查看评论