代码改变世界

混合语言的游戏开发系统架构

2010-02-23 01:27  Milo Yip  阅读(17827)  评论(25编辑  收藏

用什么程式语言来做软件是一个大问题,思考了一个周末,现时想做一个混合语言的游戏开发系统架构。暂时只考虑三种程式语言: C++、C# 及Lua。以下首先分析这三种语言的特性,之后再提出一个系统架构科案。

三种语言的比较

C++

C++是一个strongly typed、static、multi-paradigm (procedural, object-oriented, meta-programming) 的语言。基本上是游戏引擎的 de facto 语言,其实没有什么第二选择。

优点

  • 高移植性: 所有游戏平台都提供C++ 工具(除了一些嵌入式系统,如只提供C 或Java)
  • 高效率: C++ 是通用高级语言中最高效的,无论是时间和空间上。

缺点

  • 程序库不足: C++ 的标准程序库是「简而清」的,其实这是优点也是缺点。因为C++ 本身可以在不同的应用层面及系统上,所以标准库不可能加入如平台相关的GUI、thread/process等程式库,或应用相关的image processing、encryption等功能。就是这样,C++才会产生五花八门的第三方程式库。经验告诉我,要选择、整合和维护第三方程式库是不容易的。有时候不同的程式库会在有兼容的问题,也会做成不协调(命名、记忆体管理等等)、或是功能上不能完全满足需求。解决方法只有两个──直接由程式库的源代码修改并嵌入系统里、或放弃使用程式库自己重做。
  • 高难度: 使用C++ 的难度实在太高。其中一个原因是C++ 的高自由度,你可以用不同的组合方式实现一个功能。另外是C++ 接近低阶,很多细节要程式员非常小心去处理,例如资源管理、pointer等等。要编写及维护高质量的C++ 程式是非常困难,看看刚毕业的新人加入团队时要花多少时间及培训就可见一二了。
  • 编译慢: C++ 源至C,采用#include "header" 这种传统文字方式去引用其他功能。就算采用了pre-compiled header 或distributed build (如使用IncrediBuild 软件),一个大规模的系统都需要很长的编释时间。一个Edit-compile-run Cycle 所需要的时间成为开发速度的一个巨大因素。如有一些代码需要Tweaking,例如一些视觉效果及游戏内容,用C++ 去做的话实在会浪费太多时间在编释上。

C#

C# 是为.Net 而设计、 strongly typed、static、object-oriented 的语言。 C# 的语法参考了C++的语法,但作了许多改善。

优点

  • 高阶的语言功能: C#有许多高阶的功能如Garbage Collection、Reflection、Attribute、Serialization等等。在C++ 要做到同样的功能是可以的,但没有语言本身的支持写出来的代码很不美观、亦难以维护。
  • .Net 的程式库: .Net 的程式库可谓包罗万有,由低阶的OS 功能,到公用的Xml / Regular Expression,到应用层面的GUI / Web / Database 都有很好的支持。 API 的设计也做得很好(相对于Windows 上C/C++ 的超大量新旧API)。
  • 编译快: 从C# 编释至Microsoft Intermediate Language (MSIL) 是很快的。相对于C++ 的#include "header" 方式,.Net 语言采用引用.Net assembly 的meta data来编释,节省很多时间(包括C++ 编释时读取及生成档案的大量I/O 时间)。
  • 中难度: 相对写C++ ,写C# 的人可能会长寿一点(^_^)。 C#代码较简洁美观、不用分header/implementation 档(.h/.cpp)、不用那么着紧资源管理(虽然还是应该注意的)、不用调试release版本时看assembly...

缺点

  • 跨平台问题: Microsoft 暂时没有提供跨平台的.Net,这是可以理解的。但理论上,.Net是应可以和Java 一样做到跨平台。开源的Mono项目就是提供跨平台的.Net 方案,包括C#编绎器、Common Language Runtime (CLR) 和.Net Framework Class Library等。早前看过一些Mono 的资料,Mono 很成熟,也支持embedding (可以用C# 做Script Engine),但暂时不考虑这个方案,详情看后文。
  • 与其他程式库连接问题: 虽然.Net Framework Class Library 已经有大量标准的程式库可供使用。但游戏软件必须使用一些特别的程式库或API,例如DirectX、OpenGL 等。 C#提供P/Invoke及COM Interop机制去使用这些程式库,但有一定的限制。

Lua

Lua 是一个dynamic、weakly typed 的脚本语言。 Lua 被应用到许多商业游戏当中,最为人熟悉的例子如Far cry/Crysis 用Lua 来做gameplay、World of Warcraft 用Lua 做使用者接口。这些例子中Lua 版本的API 也开放给玩家来做MOD 或Add-on。

优点

  • 轻量: Lua Runtime 加上它的standard library 只是100KB~200KB 左右。放在记忆体受限的系统也没有问题(如可携式游戏console)。
  • 执行快: 一般性而言, Lua 的速度比许多其他脚本语言快。
  • 低难度: 由于是loosely typed,编写脚本比较简单及容易。
  • 动态: Lua 可以用字串生成代码的执行,也可以在执行期为物加入attribute 及method。这是Lua 与C++/C# 设计上的一个重大分别,但其实这是一个特性,很难说是优点或缺点。

缺点

  • 语法: 拥有自成一格的语法,和C/C++/Java/C# 系列的语言很不同。未接触过Lua 的编程人员要花一点时间学习。
  • Object-oriented: Lua 语言是没有制定OO 的支持。但它的设计令使用者可以自行制作一套OO 的系统,并加入一些syntactic sugar 帮助编写OO 的代码。
  • Unicode: 和C/C++ 一样,Lua 没有特别支持Unicode (可能是因为完整的Unicode 支持可能需要很多代码,使Lua 变大)。要修改Lua 的编释器代码,或使用Lua 的Mod 如Lua Plus 

分析

没有一个程式语言适合做所有的任务。我把以上的资料编成一个简单的表:

一个游戏通常会由不同的人员制作,编程人员大概可以分为做Technology、Toolset、Gameplay等领域。Technology 指做游戏引擎核心部份,或客制化第三方的游戏引擎。Toolset 包括面向不同使用者的软件工具,从Content pipeline (如汇入汇出档案)、Asset Management、Level Editor及其他编辑工具等。而Gameplay 是指游戏内容中的行为部份,可以分为游戏的核心行为(如人物控制、战斗系统),及为个别人物及关卡编写的行为(如NPC对话、AI、任务、场境中的trigger等等)。 

基本上Technology 的部份需要高效、跨平台、和低阶API连接。基本上只可以选择 C/C++。 

Toolset 的部份需要许多GUI 的部份,也要因应使用者(美工、关卡设计、音效设计等)的要求迅速改变或加强功能。另外Toolset 要处理不同种类的档案,现时常见会使用XML 作为中介的档案(如COLLADA)。以上三种语言其实都可以使用来做toolset。用C++ 的话可以选择一个GUI API 如Win32、MFC、WTL、wxWindows、Qt 等等、或使用自己开发的GUI API。但是用C++ 开发GUI 的时候,编程困难程度比较高,编释时间亦长。这两点可能不符合需要经常改变需求的Toolset。如果使用脚本语言来做GUI 的话,就需要一个好的程序库连接及工具。许多时候这些脚本用的程序库和原来的GUI 程序库会有版本后滞的问题。所以,我暂时认为用C# 做大部份工具是比较好的选择。 .Net 提供的GUI (Windows Form) 及XML、Regular Expression 等功能也支持得很好。 

而 Gameplay 的部份是经常要改动的。除了纯粹改动数值外,还要在行为的处理上改动。对于个别人物和关卡的编程可能由关卡设计师负责,他们的编程能力可能相对较低。脚本语言比较适合。有一些情况也可以用视觉化的脚本来表达。首先尝试用 Lua 吧。

设计科案

以下两张图是现时对于混合语言的游戏开发系统架构的设计。

 

游戏执行期

 

游戏开发期

 

 

两张图的橙色部份都是由Swig 生成的模组。 这个设计是基于以上的分析,把Technology、Toolset和Gameplay的部份用各自最适合的语言来实现。 为了跨平台的需求,在执行期的版本是没有C#的部份,只使用Lua 的轻量脚本Runtime。 C++ Engine 设定为执行期和开发期的共通部份,所以把需要用C++ 的Toolset 部份抽取了出来。 而在游戏开发期中,希望可以在工具里直接运行及调试游戏,所以会同时有三个语言的执行环境。

混合语言的优点

  • 各取所长: 透个组合各个语言的优点,可以增强开发的效率及最终的游戏质量。
  • 适合不同的开发者: 做Technology、Toolset 和Gameplay 的开发人员在技能方面有所不同。不同语言各自照顾他们的需要。
  • 低藕合性(Coupling): 不同语言的中间有一个独立的接口,使藕合性降低。例如同样的Gameplay代码可以在不同版本的引擎运行。

混合语言的缺点

  • 更多知识: Technology 的编程人员应该需要学习这三种语言,及相关的连接事宜。
  • 多个接口: 需要建立及维护多个接口。希望 Swig 可以减轻这个问题。
  • 调试困难: 调试时夸越一个语言可能会增加调试的难度。

其他设计科案

在搜集资料的时候,看见可以把Mono 的CLR 嵌入程式中,就好像把C# 用来做脚本语言。连接的实现方式类似于P/Invoke。用Mono 作为Gameplay 的脚本引擎是很吸引的。在效能上,由于采用Just-in-Time (JIT) 把MSIL 翻译至native code,执行速度一定会比直译式的脚本快。 C#在语法上跟C++/Java相似,许多人会懂得使用。此外,Mono 利用CLR 的特性,还可以支持其他语言。 这个方案的其中一个问题是它不太Light-weight。或许可以删掉大部份的程序库及JIT来改善。另一个想法是,透过另一种语言的结合,可以引证系统的可延展性。基于可延展性,在将来也可以考虑加入用Mono来做Scripting。所以暂时采用 Lua。

后记

原来只是想记录一些想法,却发现写这篇日记花了多个小时。希望除了作为一个记录,也可以引发大家一起讨论,那么也可能是值得的。


我后来使用这架构开发 Mil Engine,但在开发中,我放弃使用.Net 的 XML 库,而采用C++的 rapidxml 开源库。之后会介绍陆续介绍 Mil 和把它开源。

其实一般游戏引擎都会使用脚本语言,本文旨在分析及设计。而使用.Net来做ToolSet并非主流商用引擎的选择,但在Mil 中也证实是一个很好的选择。

本文原来是繁体中文,在2008-02-22发表于http://miloyip.seezone.net/?p=7,本文經過修正。