游戏引擎:让一切可以配置

游戏引擎:让一切可以配置

by Goncely

 

  :接触3D世界快10个月了,目前正在设计开发自己的游戏引擎(纯属个人爱好),结合自己的学习和思考,谈一些体会和感受,纯属抛砖,欢迎指正和探讨。

1 引子

评判一个游戏引擎的重要指标是其通用性和可扩展性。个人认为,通用性可以理解为游戏引擎可适用于多种场合;可扩展性则能理解为当遇到游戏引擎不适合的场合时,能够很容易的对其进行扩展以满足新的要求。通用性和可扩展性的目的是为了让游戏引擎在不改变主体代码的情况下,尽可能满足不同场合和不同用户的定制需求,包括不可预见的需求。而成就这一切的根源都来自引擎的可配置性。

2 游戏引擎简析

当前游戏引擎,从功能上分,主要包括资源管理子系统、图形子系统、物理子系统、声音子系统、AI子系统、网络子系统等;从流程上分,主要包括以下三个部分:载入游戏世界,更新状态,渲染。

3 可扩展性

游戏引擎的可扩展性取决于其框架的通用性以及如何让各子系统成为可配置的。当代游戏引擎通常实现一个Core或者Framework层,用她来适配或粘合各子系统。各子系统采用插件技术实现,适配层通过配置信息动态加载相应的子系统。有的引擎为了获得更大的扩展性常采用反射技术,使游戏对象的属性和方法也成为可配置的,UEOSG都是这样做的。个人则更欣赏Smoke引擎采用的技术(不知道该叫什么名字),各子系统均有自己的对象扩展(ISystemObject),适配层通过UObject管理所有的对象扩展。Smoke引擎的构架如下图:

 

1  Smoke引擎构架

这种构架类型还非常适合多线程(游戏引擎的必然发展方向,为什么?硬件基础决定上层软件),以后有机会会详细探讨多线程游戏引擎的结构。

为了让以上讨论更加直观,贴出一个我采用的引擎配置文件:

 

<Systems>

<System name = “RenderSystem”  lib = “D3D11RenderSystem.dll”>

<System name = “PhysicSystem”  lib = “PhysXSystem.dll”>

<System name = “ScriptSystem”  lib = “LuaScriptSystem.dll”>

<System name = “GUISystem” lib = “CEGUISystem.dll”>

</Systems>

 

这样当已有子系统不能满足要求时,只要开发新的子系统,更改配置文件即可,不需要修改引擎的主代码。即使需要添加新的功能子系统,这种结构也非常灵活,只要设计得当,可以在不修改主程序代码的情况下实现功能扩展。

4 通用性

刚开始学习D3D3D API的一定有感受,什么东西都要从头开始自己输入,包括顶点缓存、索引缓存、对象的创建和状态设置、着色器等等。而且这些大多固化在程序代码中,当有不同的需求时,不得不修改源代码并重新编译,即使想改变一下背景颜色也不得不如此。

需要不断修改代码和重复编译的系统基本是不可用的,游戏引擎的一大作用就是将这些易变的内容抽象出来,让他们独立于代码之外,成为可以配置的。下面按照游戏引擎的3个主要流程进行分析。

4.1 游戏世界载入

游戏世界载入的主要功能是加载游戏世界的对象,以及对象的配置信息,从而构建出丰富多彩的游戏世界。实现游戏世界载入的可配置性,需要对对象及其状态进行抽象,抽象的结果通常是模型文件和场景文件。模型文件抽象了对象本身特有的信息,一些可以共用的信息以及对象之间的关系则在场景文件中体现。模型文件和场景文件的描述性越强,可配置度就越高。

至于在模型文件和场景文件之间如何进行信息分配,以及是否将描述对象的模型文件进行分解,不同的引擎有自己不同的组织方式。比如OGRE便将描述对象骨骼动画的配置信息独立出来,只在模型(Mesh)文件中对其进行引用。

我给自己的引擎设计了一种单独的模型文件,由于处于初级阶段,所以该文件基本上是为了满足InputLayout的可配置性而设计的,贴一个例子(有删减):

 

<Mesh Topology = "TriangleList">

 

<Position Format = "Float3">

-1.0  1.0 -1.0

 1.0  1.0 -1.0

 1.0  1.0  1.0

-1.0  1.0  1.0

 

-1.0 -1.0 -1.0

 1.0 -1.0 -1.0

 1.0 -1.0  1.0

-1.0 -1.0  1.0

 

-1.0 -1.0  1.0

-1.0 -1.0 -1.0

-1.0  1.0 -1.0

-1.0  1.0  1.0

</Position>

 

<TexCoord Format = "Float2">

0.0 0.0

1.0 0.0

1.0 1.0

0.0 1.0

 

0.0 0.0

1.0 0.0

1.0 1.0

0.0 1.0

 

0.0 0.0

1.0 0.0

1.0 1.0

0.0 1.0

</TexCoord>

 

<Index Format = "Short16">

3 1 0

2 1 3

 

6 4 5

7 4 6

 

11 9 8

10 9 11

</Index>

 

</Mesh>

4.2 状态更新

状态更新主要根据对象的当前状态和环境计算出后续状态。此处的状态不包括对象的渲染状态,仅指对象的物理状态和心理状态。这些状态的变化规律有的是可配置的(比如心理状态的变化),有的则是不可配置的(比如对象的物理运动规律)。对于不可配置的状态变化通常硬编码在子系统中,比如物理子系统。对于其他希望配置的状态变化规律则可通过游戏脚本实现。比方说,用户可以通过脚本语言定制对象对外界的反应:遇到炮坑时是绕过去还是跳过去?是在正面阻击敌人还是在后面偷袭?遇到烟雾弹时是撤退还是拼命开枪?敌人扔掉枪拔出刀子冲过来时,我们是一枪解决他,还是同样拔出刀子?(真有这样的人,有韩片《大叔》为证)

现下比较流行的脚本语言有LuaPythonRubyPerl等,也有游戏引擎实现自己的脚本语言,个人根据情况可选择一种使用或自己实现。不少帖子推荐使用Lua,我也打算随波逐流,学习Lua

4.3 渲染

固定管线年代,渲染的可配置性主要通过材质文件实现,那时材质文件主要包括对象与光的交互特性信息。当代GPU引入了Shader,把渲染管线也设计成可配置的了。而恰恰因为此,大大增加了现代游戏引擎的开发难度,因为要实现Shader的通用性太难了。

游戏引擎通过Shader文件,实现对渲染管线的配置;通过特效文件,实现对渲染状态的配置。但我们不可能针对每一种渲染特性都定制一个Shader文件,因为会遇到组合爆炸问题。比方说,引擎需要实现3种光照模型,那么针对每种光照模型都必须写一个Shader;如果引擎还支持3种动画,那么针对每种动画都必须实现3种光照模型;再考虑贴图和雾等效果,Shader的数量会加剧增加。这样当用户需要新增自己的Shader时,不得不针对已有的每种Shader都实现一个版本。

解决上述问题主要有3种途径(我所知道的):一是实现一个超级巨无霸型的Shader,它包括引擎支持的所有功能,编译Shader时,通过预定义宏实现对Shader的裁剪,据说CryEnginey便有一个超大的Shader(没有考证);二是采用Shader tree技术,用户可通过设计界面对内置的Shader进行组合,并开发新的ShaderUnrealGameBryo等使用了该技术;三是采用动态Shader技术,根据配置信息动态实时生成需要的ShaderOGRE的新版本使用的就是该技术。

5 小结

游戏引擎的通用性是为了尽量满足现在或已知,可扩展性则是为了尽量满足未来或未知。当一个游戏引擎能兼顾现在和未来,能兼顾最大的用户群体,那么她必然会成为一个成功的引擎。达到这一目标的有效手段是让一切都可以配置,引擎的架构设计很大程度决定了其可配置的程度,实现可配置的有效途径是抽象出易变的内容并通过脚本对其进行定制。

6 致谢

本文以及正在设计开发的个人引擎得到OGREKlayGEOSGSmokeUnrealNebulaGameBryo等诸多引擎的启发,对这些优秀引擎的作者表示感谢和敬意!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值