序
游戏设计模式
五年级时,我和朋友们获准使用一间闲置的小教室,里面放着几台破旧不堪的TRS-80。 为了激励我们,一位老师找来了一份简单BASIC程序的打印稿,供我们摆弄。
电脑的磁带驱动器已经坏掉了,所以每当我们想要运行代码,就得小心地从头开始输入它们。 因此,我们更喜欢那些只有几行长的程序:
10 PRINT "BOBBY IS RADICAL!!!"
20 GOTO 10
即便如此,这个过程仍然危机四伏。我们根本不懂怎么编程,所以哪怕一个微小的语法错误对我们来说都难以逾越。 如果程序跑不起来——这经常发生——我们就得从头再来。
那一叠打印纸的最后是一个真正的怪物:一个占据了密密麻麻好几页的程序。 我们得花些时间才能鼓起勇气去试一试,但它实在太诱人——它的标题是“地道与巨魔”。 我们不知道它能做什么,但听起来像是个游戏,还有什么比自己编个电脑游戏更酷的吗?
我们从来没能让它运行起来,一年以后,我们离开了那间教室。 (很久以后,当我真的学会了点BASIC,我意识到那只是个桌面游戏角色生成器,而不是游戏。) 但骰子已经掷出——从那时起,我就立志成为一名游戏程序员。
十几岁时,我家买了一台Macintosh,上面有QuickBASIC,后来还有THINK C。 几乎每个暑假我都在用它拼凑游戏。 自学的过程缓慢而痛苦。 我能轻松地让一些东西跑起来——也许是一个地图界面或一个小谜题——但随着程序越来越大,它变得越来越难。
起初,挑战只是让程序跑起来。后来,挑战变成了想明白如何写出规模超出我大脑容量的程序。 我不再只阅读关于“如何用C++编程”的书籍,而开始尝试找那些讲如何组织程序的书。
快进几年,一位朋友递给我一本书:《设计模式:可复用面向对象软件的基础》。 终于!这正是我从青年时期就在寻找的书。 我一口气从头读到尾。虽然我仍然挣扎于自己的程序中,但看到别人也在挣扎并提出了解决方案是一种解脱。 我感觉自己终于有了几件工具可用,而不是只能赤手空拳。
2001年,我得到了梦想中的工作:EA(Electronic Arts)的软件工程师。 我等不及要看看真正的游戏,还有专业人士是如何组织一切的。 像《麦登橄榄球》(Madden Football)这样的大型游戏使用了什么样的架构?不同的系统是如何交互的?一套代码库是如何在多个平台上运行的?
翻开源代码是一次令人谦卑又惊喜的经历。图形、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开创的事业:记录并分享他们所见到的软件模式。而这事业在本书墨迹干透之后仍将继续。
你是这个过程的核心部分。当你发展出自己的模式,并改良(或者反驳!)本书中的模式时,你就在为软件社区做贡献。 如果你有任何建议、更正或其他反馈,欢迎联系我!