序
游戏设计模式
在五年级时,我和我的朋友被准许使用一间存放有几台非常破旧的TRS-80s的房间。 为了鼓舞我们,一位老师给我们找了一些简单的BASIC程序打印文档。
电脑的磁带驱动器已经坏掉了,所以每当我们想要运行代码,就得小心地从头开始输入它们。 因此,我们更喜欢那些只有几行长的程序:
10 PRINT "BOBBY IS RADICAL!!!" 20 GOTO 10
哪怕这样,过程也充满了困难。我们不知道如何编程,所以小小的语法错误对我们来说也是天险。 如果程序没有工作,我们就得从头再来一遍——这经常发生。
文档的最后几页是个真正的怪物:一个占据了几页篇幅的程序。 我们得花些时间才能鼓起勇气去试一试,但它实在太诱人——它的标题是“地道与巨魔”。 我们不知道它能做什么,但听起来像是个游戏,还有什么比自己编个电脑游戏更酷的吗?
我们从来没能让它运行起来,一年以后,我们离开了那间教室。 (很久以后,当我真的学会了点BASIC,我意识到那只是个桌面游戏角色生成器,而不是游戏。) 但是命运的车轮已经开始转动——自那时起,我就想要成为一名游戏程序员。
青少年时,我家有了一台能运行QuickBASIC的Macintosh,之后THINK C也能在其上运行。 几乎整个暑假我都在用它编游戏。 自学缓慢而痛苦。 我能轻松地编写并运行某些部分——地图或者小谜题——但随着程序代码量的增长,这越来越难。
起初,挑战之处仅仅在于让程序成功运行。然后,是搞明白怎样写出内容超出我大脑容量的代码。 我不再只阅读关于“如何用C++编程”的书籍,而开始尝试找那些讲如何组织程序的书。
几年过后,一位朋友给我一本书:《设计模式:可复用面向对象软件的基础》。 终于!这正是我从青年时期就在寻找的书。 我一口气从头读到尾。虽然我仍然挣扎于自己的程序中,但看到别人也在挣扎并提出了解决方案是一种解脱。 我意识到手无寸铁的我终于有件像样的工具了。
在2001年,我获得了梦想中的工作:EA的软件工程师。 我等不及要看看真正的游戏,还有专业人士是如何组织一切的。 像实况足球这样的大型游戏使用了什么样的架构?不同的系统是如何交互的?一套代码库是如何在多个平台上运行的?
分析理解源代码是种震颤的体验。图形,AI,动画,视觉效果皆有杰出代码。 有专家知道如何榨干CPU的最后一个循环并好好使用。 那些我都不知道是否可行的事情,这些人在午饭前就能完成。
但是这些杰出代码依赖的架构通常是事后设计。 他们太注重功能而忽视了架构。耦合充斥在模块间。 新功能被塞到任何能塞进去的地方。 在梦想幻灭的我看来,这和其他程序员没什么不同, 如果他们阅读过《设计模式》,最多也就用用单例。
当然,没那么糟。我曾幻想游戏程序员坐在白板包围的象牙塔里,为架构冷静地讨论上几周。 而实际情况是,我看到的代码是努力应对紧张截止期限的人赶工完成的。 他们已经竭尽全力,而且就像我慢慢意识到的那样,他们全力以赴的结果通常很好。 我花在游戏代码上的时间越多,我越能发现藏在表面下的天才之处。
不幸的是,“藏”是普遍现象。 宝石埋在代码中,但人们从未意识到它们的存在。 我看到同事重复寻找解决方案,而需要的示例代码就埋在他们所用的代码库中。
这个问题正是这本书要解决的。 我挖出了游戏代码库中能找到的设计模式,打磨然后在这里展示它们,这样可以节约时间用在发明新事物上,而非重新发明它们。
书店里已有的书籍
书店里已经有很多游戏编程书籍了。为什么要再写一本呢?
我看到的很多编程书籍可以归为这两类:
-
特定领域的书籍。 这些关于细分领域的书籍带你深入理解游戏开发的某一特定层面。 它们会教授你3D图形,实时渲染,物理模拟,人工智能,或者音频播放。 那些很多程序员穷其一生研究的细分领域。
-
完整引擎的书籍。 另一个方向,还有书籍试图包含游戏引擎的各个部分。 它们倾向于构建特定种类游戏的完整引擎,通常是3D FPS游戏。
这两种书我都喜欢,但我认为它们并未覆盖全部空间。 特定领域的书籍很少告诉你这些代码如何与游戏的其他部分打交道。 你擅长物理或者渲染,但是你知道怎么将两者优雅地组合吗?
第二类书包含这些,但是我发现完整引擎的书籍通常过于整体,过于专注某类游戏了。 特别是,随着手游和休闲游戏的兴起,我们正处于众多游戏类型欣欣向荣的时刻。 我们不再只是复制Quake了。如果你的游戏与该类游戏不同,那些介绍单一引擎的书就不那么有用了。
相反,我在这里做的更à la carte 。 每一章都是独立的、可应用到代码上的思路。 这样,你可以用你认为最好的方式组合这些思路,用到你的游戏上去。
和设计模式的关联
任何名字中有“模式”的编程书 都与Erich Gamma,Richard Helm,Ralph Johnson,和John Vlissides(通常被称为GoF)合著的经典书籍: 《设计模式:可复用面向对象软件要素》相关。
称这本书为“游戏编程模式”,我不是暗示GoF的模式不适用于游戏编程。 相反:本书的重返设计模式一节包含了《设计模式》中的很多模式, 但强调了这些模式在游戏编程中的特定使用。
同样地,我认为本书也适用于非游戏软件。 我可以依样画瓢称本书为《更多设计模式》,但是我认为举游戏编程为例子更为契合。 你真的想要另一本介绍员工记录和银行账户的书吗?
也就是说,虽然这里介绍的模式在其他软件上也很有用,但它们更合适于处理游戏中常见的工程挑战:
-
时间和顺序通常是游戏架构的核心部分。事物必须在正确的时间按正确的顺序发生。
-
高度压缩的开发周期,大量程序员需要能快速构建和迭代一系列不同的行为,同时保证不烦扰他人,也不污染代码库。
-
在定义所有的行为后,游戏开始互动。怪物攻击英雄,药物相互混合,炸弹炸飞敌人或者友军。 实现这些互动不能把代码库搞成一团乱麻。
-
最后,游戏中性能很重要。 游戏开发者处于一场榨干平台性能的竞赛中。 节约CPU循环的技巧区分了A级百万销量游戏和掉帧差评游戏。
如何阅读这本书
《游戏设计模式》分为三大块。 第一部分介绍并划分本书的框架。包含你现在阅读的这章和下一章。
第二部分,重访设计模式,复习了GoF书籍里的很多模式。 在每一章中,我给出我对这个模式的看法,以及我认为它和游戏编程有什么关系。
最后一部分是这本书最肥美的部分。 它展示了十三种我发现有用的模式。它们被分为四类: 序列模式, 行为模式, 解耦模式,和优化模式。
每种模式都使用固定的格式表述,这样你可以将这本书当成引用,快速找到你需要的:
-
意图 部分提供这个模式想要解决什么问题的简短介绍。 将它放在首位,这样你可以快速翻阅,找到你现在需要的模式。
-
动机 部分描述了模式处理的问题示例。 不同于具体的算法,模式通常不针对某个特定问题。 不用示例教授模式,就像不用面团教授烘烤。动机部分提供了面团,而下部分会教你烘烤。
-
模式 部分将模式从示例中剥离出来。 如果你想要一段对模式的教科书式简短介绍,那就是这部分了。 如果你已经熟悉了这种模式,想要确保你没有拉下什么,这部分也是很好的提示。
-
到目前为止,模式只是用一两个示例解释。但是如何知道模式对你的问题有没有用呢? 何时使用 部分提供了这个模式在何时使用何时不用的指导。 记住 部分指出了使用模式的结果和风险。
-
如果你像我一样需要具体的例子来真正地理解某物,那么示例代码部分能让你称心如意。 它描述模式的一步步具体实现,来展现模式是如何工作的。
-
模式与算法不同的是它们是开放的。 每次你使用模式,可以用不同的方式实现。 下一部分设计决策,讨论这些方式,告诉你应用模式时可供考虑的不同选项。
-
作为结尾,这里有参见部分展示了这一模式与其他模式的关联,以及那些使用它的真实代码。
关于示例代码
这本书的示例代码使用C++写就,但这并不意味着这些模式只在C++中有用,或C++比其他语言更适合使用这些模式。 这些模式适用于几乎每种编程语言,虽然有的模式假设编程语言有对象和类。
我选择C++有几个原因。首先,这是在游戏制作中最流行的语言,是业界的通用语。 通常,C++基于的C语法也是Java,C#,JavaScript和其他很多语言的基础。 哪怕你不懂C++,你也只需一点点努力就能理解这里的示例代码。
这本书的目标不是教会你C++。 示例代码尽可能地简单,不一定符合好的C++风格或规范。 示例代码展示的是意图,而不是代码。
特别地,代码没用“现代的”——C++11或者更新的——标准。 没有使用标准库,很少使用模板。 它们是“糟糕”C++代码,但我希望保持这样,这样那些使用C,Objective-C,Java和其他语言的人更容易理解它们。
为了避免花费时间在你已经看过或者是与模式无关的代码上,示例中省略了部分代码。 如果是那样,示例代码中的省略号表明这里隐藏了一些代码。
假设有个函数,做了些工作然后返回值。 而用它作示例的模式只关心返回的值,而不是完成了什么工作。那样的话,示例代码长得像这样:
bool update() { // 做点工作…… return isDone(); }
接下来呢
设计模式在软件开发过程中不断地改变和扩展。 这本书继续了GoF记录分享设计模式的旅程,而这旅程也不会终于本书。
你是这段旅程的关键部分。改良(或者否决)了这本书中的模式,你就是为软件开发社区做贡献。 如果你有任何建议,更正,或者任何反馈,保持联络!