当前位置: 首页 > 新闻 > 信息荟萃
编号:5935
Game Programming Patterns 游戏编程模式英文.pdf
http://www.100md.com 2020年11月24日
第1页
第9页
第15页
第47页
第128页

    参见附件(7335KB,231页)。

     Game Programming Patterns 游戏编程模式

    游戏开发一直是热门的领域,掌握良好的游戏编程模式是开发人员的应备技能。本书细致地讲解了游戏开发需要用到的各种编程模式,并提供了丰富的示例。小编今天为大家准备了的资料,有需要的就快来吧

    内容简介

    游戏开发一直是热门的领域,掌握良好的游戏编程模式是开发人员的应备技能。本书细致地讲解了游戏开发需要用到的各种编程模式,并提供了丰富的示例。

    全书共分20章,通过三大部分内容全面介绍了与游戏编程模式相关的各类知识点。首部分介绍了基础知识和框架;第二部分深入探索设计模式,并介绍了模式与游戏开发之间的关联;第三部分介绍了13种有效的游戏设计模式。

    本书提供了丰富的代码示例,通过理论和代码示例相结合的方式帮助读者更好地学习。无论是游戏领域的设计人员、开发人员,还是想要进入游戏开发领域的学生和普通程序员,都可以阅读本书。

    相关内容部分预览

    作者简介

    Robert Nystrom是一位拥有2 0 年以上职业编程经验的开发者,而他在其中大概一半的时间从事游戏开发。在艺电(El e c t r o n i c Arts)的8年时间里,他曾参与劲爆美式足球(Madden)系列这样庞大的项目,也曾投身于亨利·海茨沃斯大冒险(Henry Hatsworth in the Puzzling Adventure)这样稍小规模的游戏开发之中。

    他所开发的游戏遍及PC、GameCube、PS2、XBox、X360以及DS平台。但他引以为傲的,是为开发者们提供了开发工具和共享库。他热衷于寻求易用的、漂亮的代码来延伸和增强开发者们的创造力。Robert与他的妻子和两个女儿定居于西雅图,在那里你很有可能会见到他正在为朋友们下厨,或者在为他们上啤酒。

    目录

    第1篇 概述

    第1章 架构,性能和游戏 3

    1.1 什么是软件架构 3

    1.1.1 什么是好的软件架构 3

    1.1.2 你如何做出改变 4

    1.1.3 我们如何从解耦中受益 5

    1.2 有什么代价 5

    1.3 性能和速度 6

    1.4 坏代码中的好代码 7

    1.5 寻求平衡 8

    1.6 简单性 9

    1.7 准备出发 9

    第2篇 再探设计模式

    第2章 命令模式 13

    2.1 配置输入 14

    2.2 关于角色的说明 16

    2.3 撤销和重做 18

    2.4 类风格化还是函数风格化 21

    2.5 参考 22

    第3章 享元模式 23

    3.1 森林之树 23

    3.2 一千个实例 25

    3.3 享元模式 26

    3.4 扎根之地 26

    3.5 性能表现如何 30

    3.6 参考 31

    第4章 观察者模式 33

    4 ......

    1. Introduction

    2. 介绍

    i. 架构,性能和游戏

    3. 再探设计模式

    i. 命令模式

    ii. 享元模式

    iii . 观察者模式

    iv. 原型模式

    v. 单例模式

    vi. 状态模式

    4. 序列模式

    i. 双缓冲

    ii. 游戏循环

    iii . 更新方法

    5. 行为模式

    i. 字节码

    ii. 子类沙盒

    iii . 对象类型

    6. 解耦模式

    i. 组件

    ii. 事件队列

    iii . 服务定位器

    7. 优化模式

    i. 数据局部性

    ii. 脏标记

    iii . 对象池

    iv. 空间划分

    Gitbook地址

    你是否还在为代码整体规划而苦苦挣扎?

    是否发现随着代码库的增长却不容易做出些改动?

    是否感觉到你的游戏就是一个纷乱交杂的巨大的毛球?

    又或者不知如何将设计模式应用到游戏?

    听说过“缓存一致性”和“对象池”,但却不知道如何使用它们来提升你的游戏的性能?

    你们的救星来啦!我撰写了这本书来解答这些问题。这是我在游戏中所使用的模式总结,这些模式能让我们的代码更整洁,更清晰易懂,以及运行更快!

    当我开始编写游戏时,我希望我有一本这样的书。现在,我希望你能够有一本。

    开始阅读

    我叫BobNystrom。当我在EA工作的时候,我便开始写这本书了。在EA工作的8年时间里,我

    见过很多优美的代码,也见过很多着实可怕的代码。我希望我能够将我从这些优美的代码设计中

    学到的东西,在这里写下来,并教给大家如何写出这样好的代码来。

    如果你想要联系我,你可以在网站写email给我,或者直接在twitter上@munificentbob都可

    以。

    在线写作书籍的一大好处就是方便修改。如果你发现了错误或者有什么建议,不要犹豫,给我报

    告bug或者发送一个pullrequest。

    介绍

    架构,性能和游戏

    再探设计模式

    命令模式

    享元模式

    观察者模式

    原型模式

    单例模式

    状态模式

    序列模式

    Game-Programming-Patterns-CN

    游戏编程模式中文

    嘿,游戏开发伙伴们!

    免费在线阅读

    我是谁?

    反馈

    目录双缓冲

    游戏循环

    更新方法

    行为模式

    字节码

    子类沙盒

    对象类型

    解耦模式

    组件

    事件队列

    服务定位器

    优化模式

    数据局部性

    脏标记

    对象池

    空间划分

    ==========================

    欢迎朋友们阅读并斧正,提交Issue或者sendpullrequest:)。

    能有所收获,便是我们翻译中文的意义所在。

    翻译:(目前已经都领取完毕)

    在Issue列表中查看尚未领取的章节

    然后在Issue1中留言回复,我会更新Issue列表状态。

    Fork项目后开始翻译,提交PR。

    校正:由于能力所及,总有些地方翻译欠妥,所以校正会一直进行中,朋友们看到有翻译错误,可以在Issue列表中新

    建issue提出来,更鼓励欢迎直接发送pullrequest。

    术语参考

    注:所有贡献人的名字都会在此列出,欢迎大家踊跃参与翻译、校正。

    子龙山人

    kislyl

    Henry-T

    lazyqiang

    zhizhen

    Gwill

    Tsiannian

    coneo

    Gizmosir

    jptiancai

    ChildhoodAndy

    翻译

    如何参与?

    参考资料

    贡献人列表(排名不分先后)在五年级的时候,我和我的小伙伴们被获准使用一个容放着一些非常破旧的TRS-80s(译者注:见维基百科TRS-80s)的

    废弃教室。为了激励我们,一个老师找到了一些简单的BASIC程序的打印输出让我们鼓捣玩耍。

    电脑上的音频盒式磁带驱动器当时是坏掉的,所以每次我们想要运行一些代码的时候,我们不得不仔细的从头开始键入。这

    使得我们更喜欢那些只有几行代码的程序:

    10PRINTBOBBYISRADICAL!!!

    20GOTO10

    注解

    如果计算机打印足够多的次数,或许它会神奇的变成现实哦。(译者注:这里指的是计算机反复打印第10行代码的语

    句BOBBYISRADICAL!!!,作者开玩笑的说会变成现实。)

    即便如此,整个过程还是比较艰辛。我们不懂得如何去编程,所以一个小的语法错误便让我们感到很费解。程序出毛病是家

    常便饭,而那是我们只能重头再来。

    在有了这些小程序的经验积累之后,我们遇到了个大BOSS:一个代码密密麻麻占去好几页纸的程序。我们光是鼓起勇气决定

    去尝试它就花了不少时间,光是它的标题“隧道与巨人”(TunnelsandTrolls)就令人捉摸不透。这听起来像是个游戏,而还

    有什么事比亲手编写一款电脑游戏更酷?

    我们从没让它实际运行起来过。一年后,我们搬出了那个教室。(后来随着我更多地接触BASIC,才意识到它只是一个为桌

    面游戏使用的角色生成器,而并非整个游戏。)但木已成舟,从那之后,我立志要成为一个游戏开发者。

    在我十几岁时,我的家人搞了一台装有QuickBASIC的Macintosh,之后又装了THINKC。我几乎整个暑假都在那上面倒腾

    游戏。自学是缓慢而痛苦的。我希望能让程序轻松地跑一些功能-一张地图或者是个小的猜谜游戏-但是随着程序的扩大,这

    越来越难了。

    注解

    我的许多夏天都是在路易斯安那州南部的沼泽中捕蛇和乌龟来渡过的。如果户外不是这样酷热,很有可能,这将是一

    本爬虫学的书,而不是编程书。

    起初,我的挑战在于让程序跑起来。后来,我开始思考如何让程序做一些超越我脑袋所想的工作。除了阅读一些关于“如何用

    C++编程”的书籍,我开始试图寻找一些关于如何组织程序的书籍。

    又过了几年,一个朋友给了我一本书:《设计模式:可复用面向对象软件的基础》。终于来了!这就是我从青少年开始便一

    直寻找的那本书!我们刚碰面,我就把书从头到尾读了一遍。我之前仍然在为我自己写的程序挣扎犯愁,但是看到别人也如此

    挣扎并提出了解决方案,我便解脱了。我感觉我终于有了一些武器用来挥舞而不再是赤手空拳了。

    注解

    这是我们第一次见面,5分钟自我介绍之后,我坐在他的沙发上,并在接下来的几个小时里,我聚精会神地阅读而完全

    忽视了他。我感觉那时候自己的社交能力还是稍有那么一丁点提升的。

    在2001年,我得到了我梦想中的工作:EA(ElectronicArts)的软件工程师。我迫不及待的想看下真正的游戏,以及工程师是

    如何组织它们的。像MaddenFootball这样的大型游戏到底是一个什么样的架构?他们是怎么让一套代码库在不同平台上运

    行的?

    破解开放的源码是一个震撼人心和令人惊奇的体验。图形、人工智能、动画和视觉效果等各个方面的代码都十分出众。我们

    公司有人懂得如何榨取CPU的每一个周期并得以善用。甚至一些我认为不可能的东西,这些家伙一个早上就能搞定。

    但这种优秀代码的结构往往是事后想出来的。他们太专注于功能以至于忽视了组织架构。模块之间耦合很严重。只要是有作

    序言为的新功能都会被扔进代码库里。我的幻想破灭了,在我看来,恐怕很多程序员,从没翻过设计模式,不曾了解单例模式。

    当然,实际并非想象的那么糟。我曾设想游戏程序员们坐在放满白板的象牙塔中,从头到尾的几周时间里都在想当然地讨论

    代码架构细节。实际上,我所见到的代码是出自那些被上司催着进度的人之手。他们竭尽了全力,而且,我逐渐意识到,他

    们竭尽全力的结果往往是很棒的。我越深入这些代码,便越是这么觉得。

    不幸的是,“隐藏”一词恰恰反映了这样的情况:宝藏埋在代码深处,而许多人只从地表踏过。我看到同事在努力重塑更好的解

    决方案时,他们所寻求的办法正藏在他们脚下的代码库之中。

    这样的问题正是这本书所关注的。我挖掘并打磨出自己在游戏中所发现的最好的设计模式,在此一一呈现给大家,以便我们

    将时间节省下来发现新大陆,而不是重新造轮子。

    目前市面已经有数十多本游戏编程的书籍。为什么还要再写一本?

    我见过的大多数游戏编程书籍无非下列两类:

    关于特定领域的书籍。这些针对性较强的书籍为你的游戏开发打开一个独特而深刻的视角。他们会教你3D图形,实时渲

    染,物理模拟,人工智能,或音频处理。这些是多数游戏程序员在自己的职业生涯中所专注的领域。

    关于整个游戏引擎的书籍。相反,这些书尝试涵盖整个游戏引擎的各部分。它们被组织起来形成一个完整的引擎以适用

    于一些特定类型的游戏,通常是3D第一人称射击游戏。

    我喜欢这两类书,但我觉得它们仍留有一些空白。特定领域的书很少会写你的代码块如何与游戏的其他部分交互。你可能擅

    长物理和渲染(译者注:这里指的是在某个领域特别擅长的人),是你知道如何优雅的将它们拼合起来?

    第二类书籍写到了这些,但我通常发现这类书都太单一,太泛泛而谈。特别是随着移动和休闲游戏的兴起,我们正处在充斥

    着大量不同类型游戏的时代。我们不再只是克隆Quake(译者注:雷神之锤,第一个真3D实时演算的FPS游戏)了。当你的

    游戏不适合这个模型时,这类阐述单个引擎的书籍就不再合适了。

    相反,这里我想要做的,更倾向于“引导”。在这本书中,每个章节都是一个独立的思想,而你可以将它应用到你的代码里。

    藉此,你可以混用它们以令其在你制作的游戏中发挥最好的效果。

    注解

    这种“向导”风格的另外一个例子,就是广受大家喜爱的《游戏编程精粹》系列。

    任何名字中带有“模式”的编程书籍都和经典图书《设计模式:可复用面向对象软件的基础》脱不了干系。这本书由Erich

    Gamma,RichardHelm,RalphJohnson和JohnVlissides完成。(俗称“GangofFour”--四人组)

    注解

    设计模式一书本身也源自前人的灵感。使用模式语言来描述开放式解决问题的想法来自《APatternLanguage》,由

    ChristopherAlexander(以及SarahIshikawa,MurraySilverstein)完成。

    他们的书是关于架构(就像真正的建筑结构有着建筑和围墙之类的东西),但他们希望他人会使用相同的结构来描述

    在其他领域的解决方案。设计模式(DesignPatterns)是GangofFour在软件领域的一个尝试。

    本书以“游戏编程模式”命名,并不是说GangofFour的书不适用于游戏。恰恰相反,在再探设计模式一节中覆盖众多来自

    GoF著作的设计模式,同时强调了在游戏开发中的运用。

    从另一面说,我觉得这本书也适用于非游戏软件。我也可以把这本书命名为MoreDesignPatterns,但我认为游戏制作例子

    更吸引人。难道你真的想要阅读的一本关于员工记录和银行账户例子的书么?

    市面上的书籍

    设计模式相关话虽这么说,这里介绍的模式在其他软件中是有用的,我觉得他们是特别适合于软件开发,就像在游戏中经常遇到的挑战一

    样:

    时间和序列化往往是一个游戏的架构的核心部分。事情必须依照正确的顺序和正确的时间发生。

    开发周期被高度压缩,程序员们需要能够快速构建和迭代一组丰富且相异的行为,同时不牵涉到他人或者弄乱代码库。

    所有这些行为被定义后,游戏便开始互动。怪物撕咬英雄,药水混合在一起,炸弹炸到敌人和朋友...诸如此类。这些交

    互必须很好地进行下去,同时代码库需要保持干净不能纷乱无章得像个交织错乱的毛线球。

    最后,性能是游戏的关键。游戏开发者们总在不断比赛着看谁能够充分利用平台的性能。游戏周期处理的不同可能意味

    着产品是成为数以百万计销售的A级游戏,或者满是掉帧且贴满愤怒评论的废铁。

    游戏编程模式分为三大部分。第一部分是序言和书的概括。这正是你现在阅读的章节以及下一章节。

    第二部分,再探设计模式,回顾了GangofFour中的一些设计模式。在每个章节中,我会提及自己对该模式的认识,及将该

    模式运用到游戏中的方法。

    最后部分是这本书的重头戏。这部分代表了我认为十分有用的13种设计模式。它们分为四类:序列模式,行为模式,解耦模

    式,优化模式。

    这些模式使用一致的文本组织结构来讲述,以便你将该书作为参考并能快速找到你所需要的内容:

    目的部分简述了此模式旨在解决的问题。这样你很容易根据自己遇到的问题来快速确定该用哪个模式。

    动机部分描述了一个示例、该示例存在问题、我们将要对之采用设计模式。不同于具体的算法,模式通常是无形的,除

    非针对一些特定的问题。学设计模式离不开示例正如学烘培离不开面团一样。这个部分提供面团,之后的部分将会教你

    如何烘培。

    模式部分会提炼出前面示例中的模式本质。如果你想了解该模式的书面描述,就是这部分了。如果你已经熟悉了,这部

    分也是一个很好的复习,确保你没有忘记该模式。

    到目前为止,该模式只是就一个单一的例子来解释的。但你怎么该模式是否适用于你的问题呢?使用情境针对模式使用

    的情境以及何时避免使用它提供一些指引。使用须知部分会指出使用该模式时面临的后果和风险。

    如果像我一样,需要借助具体的实例才能真正的理解,那么示例正满足你的需要。示例会一步步实现模式,所以你可以

    清楚的看到模式是如何工作的。

    模式和单一的算法不同,因为模式是开放式的。每次使用模式的时候,你实现的方式有可能会不同。接下来设计决策部

    分,会探讨这个问题,并告诉你在应用模式时要考虑的不同因素。

    结束部分,参考部分会告诉你该模式和其他模式的关联并指出使用该模式的一些真实世界中的开源代码。

    这本书中的示例代码用C++编写,但是这并不意味着这些模式仅在该语言中有用或者说C++比其他语言要好。几乎所有的

    语言都适用,虽然有些模式确实倾向于面向对象语言。

    我选择C++有几个原因。首先,它是商业游戏中最流行的语言,是该行业的通用语言。另外,C++基于C的语法也是

    Java,C,JavaScript和许多其他语言的基础。即使你不懂C++,也没有关系,你可以很轻松的明白示例代码的含义。

    这本书的目的,不是教你学习C++。示例会尽可能保持简单,但是这并不代表良好的C++编码风格或使用就是这样。阅读

    代码时要理解代码所传达的思想,而不是代码本身的表达。

    特别一提的是,示例代码没有采用“现代”C++--C++11或更高版本风格。它没使用标准库并很少使用模板。这是“糟糕”的

    如何阅读本书

    关于示例代码C++代码,但我仍希望保留下来,这样会对那些从C,Objective-C,Java和其他语言转来的人们更加的友好。

    为了避免浪费页面空间,你已经看到了,和模式不相关的代码,有时会在例子中省略,通常用省略号来表示省去的代码。

    举个例子,有一个函数,它会处理一些工作,并且会返回一个值。被解释的模式是只关心返回值,不关心处理的工作。在这

    种情况下,示例代码看起来像这样:

    boolupdate

    {

    Dowork...

    returnisDone;

    }

    模式是软件开发中一个不断变化和扩展的部分。这本书从GangofFour文献开始,并分享他们了解的软件设计模式,当书页

    发布之后过程仍然会继续。

    你是这个过程的核心部分。只要你开发了你自己的模式并细化(或者反驳!)这本书中提到的模式,你就是在为软件社区贡

    献力量。如果你关于书中内容有任何建议,修正或者其他反馈,请与我联系。

    ===============================目录

    下一节

    下一步Beforeweplungeheadfirstintoapileofpatterns,IthoughtitmighthelptogiveyousomecontextabouthowIthinkabout

    softwarearchitectureandhowitappliestogames.Itmayhelpyouunderstandtherestofthisbookbetter.Ifnothingelse,whenyougetdraggedintoanargumentabouthowterrible(orawesome)designpatternsandsoftwarearchitectureare,it

    willgiveyousomeammotouse.

    note:NotethatIdidn’tpresumewhichsideyou’retakinginthatfight.Likeanyarmsdealer,Ihavewaresforsaleto

    allcombatants.

    Ifyoureadthisbookcovertocover,youwon’tcomeawayknowingthelinearalgebrabehind3Dgraphicsorthecalculus

    behindgamephysics.Itwon’tshowyouhowtoalpha-betapruneyourAI’ssearchtreeorsimulatearoom’sreverberationin

    youraudioplayback.

    note:Wow,thisparagraphwouldmakeaterribleadforthebook.

    Instead,thisbookisaboutthecodebetweenallofthat.It’slessaboutwritingcodethanitisaboutorganizingit.Every

    programhassomeorganization,evenifit’sjust“jamthewholethingintomainandseewhathappens”,soIthinkit’smore

    interestingtotalkaboutwhatmakesforgoodorganization.Howdowetellagoodarchitecturefromabadone?

    I’vebeenmullingoverthisquestionforaboutfiveyears.Ofcourse,likeyou,Ihaveanintuitionaboutgooddesign.We’ve

    allsufferedthroughcodebasessobad,thebestyoucouldhopetodoforthemistakethemoutbackandputthemoutof

    theirmisery.

    note:Let’sadmitit,mostofusareresponsibleforafewofthose.

    Aluckyfewhavehadtheoppositeexperience,achancetoworkwithbeautifullydesignedcode.Thekindofcodebasethat

    feelslikeaperfectlyappointedluxuryhotelfestoonedwithconciergeswaitingeagerlyonyoureverywhim.What’sthe

    differencebetweenthetwo?

    Forme,gooddesignmeansthatwhenImakeachange,it’sasiftheentireprogramwascraftedinanticipationofit.Ican

    solveataskwithjustafewchoicefunctioncallsthatslotinperfectly,leavingnottheslightestrippleontheplacidsurfaceof

    thecode.

    Thatsoundspretty,butit’snotexactlyactionable.“Justwriteyourcodesothatchangesdon’tdisturbitsplacidsurface.”

    Right.

    Letmebreakthatdownabit.Thefirstkeypieceisthatarchitectureisaboutchange.Someonehastobemodifyingthe

    codebase.Ifnooneistouchingthecode?—?whetherbecauseit’sperfectandcompleteorsowretchednoonewillsullytheir

    texteditorwithit?—?itsdesignisirrelevant.Themeasureofadesignishoweasilyitaccommodateschanges.Withno

    changes,it’sarunnerwhoneverleavesthestartingline.

    Beforeyoucanchangethecodetoaddanewfeature,tofixabug,orforwhateverreasoncausedyoutofireupyoureditor,youhavetounderstandwhattheexistingcodeisdoing.Youdon’thavetoknowthewholeprogram,ofcourse,butyou

    needtoloadalloftherelevantpiecesofitintoyourprimatebrain.

    note:It’sweirdtothinkthatthisisliterallyanOCRprocess.

    Architecture,Performance,andGames

    WhatisSoftwareArchitecture?

    Whatisgoodsoftwarearchitecture?

    Howdoyoumakeachange?Wetendtoglossoverthisstep,butit’softenthemosttime-consumingpartofprogramming.Ifyouthinkpagingsomedata

    fromdiskintoRAMisslow,trypagingitintoasimiancerebrumoverapairofopticalnerves.

    Onceyou’vegotalltherightcontextintoyourwetware,youthinkforabitandfigureoutyoursolution.Therecanbealotof

    backandforthhere,butoftenthisisrelativelystraightforward.Onceyouunderstandtheproblemandthepartsofthecode

    ittouches,theactualcodingissometimestrivial.

    Youbeatyourmeatyfingersonthekeyboardforawhileuntiltherightcoloredlightsblinkonscreenandyou’redone,right?

    Notjustyet!Beforeyouwritetestsandsenditoffforcodereview,youoftenhavesomecleanuptodo.

    note:DidIsay“tests”?Oh,yes,Idid.It’shardtowriteunittestsforsomegamecode,butalargefractionofthe

    codebaseisperfectlytestable.

    Iwon’tgetonasoapboxhere,butI’llaskyoutoconsiderdoingmoreautomatedtestingifyouaren’talready.Don’t

    youhavebetterthingstodothanmanuallyvalidatestuffoverandoveragain?

    Youjammedabitmorecodeintoyourgame,butyoudon’twantthenextpersontocomealongtotripoverthewrinklesyou

    leftthroughoutthesource.Unlessthechangeisminor,there’susuallyabitofreorganizationtodotomakeyournewcode

    integrateseamlesslywiththerestoftheprogram.Ifyoudoitright,thenextpersontocomealongwon’tbeabletotellwhen

    anylineofcodewaswritten.

    Inshort,theflowchartforprogrammingissomethinglike:

    note:ThefactthatthereisnoescapefromthatloopisalittlealarmingnowthatIthinkaboutit.

    Whileitisn’tobvious,Ithinkmuchofsoftwarearchitectureisaboutthatlearningphase.Loadingcodeintoneuronsisso

    painfullyslowthatitpaystofindstrategiestoreducethevolumeofit.Thisbookhasanentiresectionondecoupling

    patterns,andalargechunkofDesignPatternsisaboutthesameidea.

    Youcandefine“decoupling”abunchofways,butIthinkiftwopiecesofcodearecoupled,itmeansyoucan’tunderstand

    onewithoutunderstandingtheother.Ifyoude-couplethem,youcanreasonabouteithersideindependently.That’sgreat

    becauseifonlyoneofthosepiecesisrelevanttoyourproblem,youjustneedtoloaditintoyourmonkeybrainandnotthe

    otherhalftoo.

    Tome,thisisakeygoalofsoftwarearchitecture:minimizetheamountofknowledgeyouneedtohavein-craniumbefore

    Howcandecouplinghelp?youcanmakeprogress.

    Thelaterstagescomeintoplaytoo,ofcourse.Anotherdefinitionofdecouplingisthatachangetoonepieceofcode

    doesn’tnecessitateachangetoanother.Weobviouslyneedtochangesomething,butthelesscouplingwehave,theless

    thatchangeripplesthroughouttherestofthegame.

    Thissoundsgreat,right?Decoupleeverythingandyou’llbeabletocodelikethewind.Eachchangewillmeantouching

    onlyoneortwoselectmethods,andyoucandanceacrossthesurfaceofthecodebaseleavingnaryashadow.

    Thisfeelingisexactlywhypeoplegetexcitedaboutabstraction,modularity,designpatterns,andsoftwarearchitecture.A

    well-architectedprogramreallyisajoyfulexperiencetoworkin,andeveryonelovesbeingmoreproductive.Good

    architecturemakesahugedifferenceinproductivity.It’shardtooverstatehowprofoundaneffectitcanhave.

    But,likeallthingsinlife,itdoesn’tcomefree.Goodarchitecturetakesrealeffortanddiscipline.Everytimeyoumakea

    changeorimplementafeature,youhavetoworkhardtointegrateitgracefullyintotherestoftheprogram.Youhaveto

    takegreatcaretobothorganizethecodewellandkeepitorganizedthroughoutthethousandsoflittlechangesthatmake

    upadevelopmentcycle.

    note:Thesecondhalfofthis?—?maintainingyourdesign?—?deservesspecialattention.I’veseenmanyprogramsstart

    outbeautifullyandthendieadeathofathousandcutsasprogrammersadd“justonetinylittlehack”overandover

    again.

    Likegardening,it’snotenoughtoputinnewplants.Youmustalsoweedandprune.

    Youhavetothinkaboutwhichpartsoftheprogramshouldbedecoupledandintroduceabstractionsatthosepoints.

    Likewise,youhavetodeterminewhereextensibilityshouldbeengineeredinsofuturechangesareeasiertomake.

    Peoplegetreallyexcitedaboutthis.Theyenvisionfuturedevelopers(orjusttheirfutureself)steppingintothecodebase

    andfindingitopen-ended,powerful,andjustbeckoningtobeextended.TheyimagineTheOneGameEngineToRule

    ThemAll.

    Butthisiswhereitstartstogettricky.Wheneveryouaddalayerofabstractionoraplacewhereextensibilityissupported,you’respeculatingthatyouwillneedthatflexibilitylater.You’readdingcodeandcomplexitytoyourgamethattakestimeto

    develop,debug,andmaintain.

    Thateffortpaysoffifyouguessrightandenduptouchingthatcodelater.Butpredictingthefutureishard,andwhenthat

    modularitydoesn’tendupbeinghelpful,itquicklybecomesactivelyharmful.Afterall,itismorecodeyouhavetodealwith.

    note:Somefolkscoinedtheterm“YAGNI”?—?Youaren’tgonnaneedit?—?asamantratousetofightthisurgeto

    speculateaboutwhatyourfutureselfmaywant.

    Whenpeoplegetoverzealousaboutthis,yougetacodebasewhosearchitecturehasspiraledoutofcontrol.You’vegot

    interfacesandabstractionseverywhere.Plug-insystems,abstractbaseclasses,virtualmethodsgalore,andallsortsof

    extensionpoints.

    Ittakesyouforevertotracethroughallofthatscaffoldingtofindsomerealcodethatdoessomething.Whenyouneedto

    makeachange,sure,there’sprobablyaninterfacetheretohelp,butgoodluckfindingit.Intheory,allofthisdecoupling

    meansyouhavelesscodetounderstandbeforeyoucanextendit,butthelayersofabstractionthemselvesendupfilling

    yourmentalscratchdisk.

    Codebaseslikethisarewhatturnpeopleagainstsoftwarearchitecture,anddesignpatternsinparticular.It’seasytogetso

    wrappedupinthecodeitselfthatyoulosesightofthefactthatyou’retryingtoshipagame.Thesirensongofextensibility

    sucksincountlessdeveloperswhospendyearsworkingonan“engine”withouteverfiguringoutwhatit’sanenginefor.

    AtWhatCost?There’sanothercritiqueofsoftwarearchitectureandabstractionthatyouhearsometimes,especiallyingamedevelopment:

    thatithurtsyourgame’sperformance.Manypatternsthatmakeyourcodemoreflexiblerelyonvirtualdispatch,interfaces,pointers,messages,andothermechanismsthatallhaveatleastsomeruntimecost.

    note:Oneinterestingcounter-exampleistemplatesinC++.Templatemetaprogrammingcansometimesgiveyouthe

    abstractionofinterfaceswithoutanypenaltyatruntime.

    There’saspectrumofflexibilityhere.Whenyouwritecodetocallaconcretemethodinsomeclass,you’refixingthat

    classatauthortime?—?you’vehard-codedwhichclassyoucallinto.Whenyougothroughavirtualmethodor

    interface,theclassthatgetscalledisn’tknownuntilruntime.That’smuchmoreflexiblebutimpliessomeruntime

    overhead.

    Templatemetaprogrammingissomewherebetweenthetwo.There,youmakethedecisionofwhichclasstocallat

    compiletimewhenthetemplateisinstantiated.

    There’sareasonforthis.Alotofsoftwarearchitectureisaboutmakingyourprogrammoreflexible.It’saboutmakingittake

    lessefforttochangeit.Thatmeansencodingfewerassumptionsintheprogram.Youuseinterfacessothatyourcode

    workswithanyclassthatimplementsitinsteadofjusttheonethatdoestoday.Youuseobserversandmessagingtolettwo

    partsofthegametalktoeachothersothattomorrow,itcaneasilybethreeorfour.

    Butperformanceisallaboutassumptions.Thepracticeofoptimizationthrivesonconcretelimitations.Canwesafely

    assumewe’llneverhavemorethan256enemies?Great,wecanpackanIDintoasinglebyte.Willweonlycallamethod

    ononeconcretetypehere?Good,wecanstaticallydispatchorinlineit.Arealloftheentitiesgoingtobethesameclass?

    Great,wecanmakeanicecontiguousarrayofthem.

    Thisdoesn’tmeanflexibilityisbad,though!Itletsuschangeourgamequickly,anddevelopmentspeedisabsolutelyvital

    forgettingtoafunexperience.Noone,notevenWillWright,cancomeupwithabalancedgamedesignonpaper.It

    demandsiterationandexperimentation.

    Thefasteryoucantryoutideasandseehowtheyfeel,themoreyoucantryandthemorelikelyyouaretofindsomething

    great.Evenafteryou’vefoundtherightmechanics,youneedplentyoftimefortuning.Atinyimbalancecanwreckthefun

    ofagame.

    There’snoeasyanswerhere.Makingyourprogrammoreflexiblesoyoucanprototypefasterwillhavesomeperformance

    cost.Likewise,optimizingyourcodewillmakeitlessflexible.

    Myexperience,though,isthatit’seasiertomakeafungamefastthanitistomakeafastgamefun.Onecompromiseisto

    keepthecodeflexibleuntilthedesignsettlesdownandthentearoutsomeoftheabstractionlatertoimproveyour

    performance.

    Thatbringsmetothenextpointwhichisthatthere’satimeandplacefordifferentstylesofcoding.Muchofthisbookis

    aboutmakingmaintainable,cleancode,somyallegianceisprettyclearlytodoingthingsthe“right”way,butthere’svaluein

    slapdashcodetoo.

    Writingwell-architectedcodetakescarefulthought,andthattranslatestotime.Moreso,maintainingagoodarchitecture

    overthelifeofaprojecttakesalotofeffort.Youhavetotreatyourcodebaselikeagoodcamperdoestheircampsite:

    alwaystrytoleaveitalittlebetterthanyoufoundit.

    Thisisgoodwhenyou’regoingtobelivinginandworkingonthatcodeforalongtime.But,likeImentionedearlier,game

    designrequiresalotofexperimentationandexploration.Especiallyearlyon,it’scommontowritecodethatyouknowyou’ll

    throwaway.

    PerformanceandSpeed

    TheGoodinBadCodeIfyoujustwanttofindoutifsomegameplayideaplaysrightatall,architectingitbeautifullymeansburningmoretime

    beforeyouactuallygetitonscreenandgetsomefeedback.Ifitendsupnotworking,thattimespentmakingthecode

    elegantgoestowastewhenyoudeleteit.

    Prototyping?—?slappingtogethercodethat’sjustbarelyfunctionalenoughtoansweradesignquestion?—?isaperfectly

    legitimateprogrammingpractice.Thereisaverylargecaveat,though.Ifyouwritethrowawaycode,youmustensureyou’re

    abletothrowitaway.I’veseenbadmanagersplaythisgametimeandtimeagain:

    Boss:“Hey,we’vegotthisideathatwewanttotryout.Justaprototype,sodon’tfeelyouneedtodoitright.How

    quicklycanyouslapsomethingtogether?”

    Dev:“Well,ifIcutlotsofcorners,don’ttestit,don’tdocumentit,andithastonsofbugs,Icangiveyousometemp

    codeinafewdays.”

    Boss:“Great!”

    Afewdayspass…

    Boss:“Hey,thatprototypeisgreat.Canyoujustspendafewhourscleaningitupabitnowandwe’llcallitthereal

    thing?”

    Youneedtomakesurethepeopleusingthethrowawaycodeunderstandthateventhoughitkindoflookslikeitworks,it

    cannotbemaintainedandmustberewritten.Ifthere’sachanceyou’llenduphavingtokeepitaround,youmayhaveto

    defensivelywriteitwell.

    note:Onetricktoensuringyourprototypecodeisn’tobligedtobecomerealcodeistowriteitinalanguagedifferent

    fromtheoneyourgameuses.Thatway,youhavetorewriteitbeforeitcanendupinyouractualgame.

    Wehaveafewforcesinplay:

    1. Wewantnicearchitecturesothecodeiseasiertounderstandoverthelifetimeoftheproject.

    2. Wewantfastruntimeperformance.

    3. Wewanttogettoday’sfeaturesdonequickly.

    note:Ithinkit’sinterestingthattheseareallaboutsomekindofspeed:ourlong-termdevelopmentspeed,the

    game’sexecutionspeed,andourshort-termdevelopmentspeed.

    Thesegoalsareatleastpartiallyinopposition.Goodarchitectureimprovesproductivityoverthelongterm,butmaintaining

    itmeanseverychangerequiresalittlemoreefforttokeepthingsclean.

    Theimplementationthat’squickesttowriteisrarelythequickesttorun.Instead,optimizationtakessignificantengineering

    time.Onceit’sdone,ittendstocalcifythecodebase:highlyoptimizedcodeisinflexibleandverydifficulttochange.

    There’salwayspressuretogettoday’sworkdonetodayandworryabouteverythingelsetomorrow.Butifwecramin

    featuresasquicklyaswecan,ourcodebasewillbecomeamessofhacks,bugs,andinconsistenciesthatsapsourfuture

    productivity.

    There’snosimpleanswerhere,justtrade-offs.FromtheemailIget,thisdisheartensalotofpeople.Especiallyfornovices

    whojustwanttomakeagame,it’sintimidatingtohear,“Thereisnorightanswer,justdifferentflavorsofwrong.”

    But,tome,thisisexciting!Lookatanyfieldthatpeoplededicatecareerstomastering,andinthecenteryouwillalways

    findasetofintertwinedconstraints.Afterall,iftherewasaneasyanswer,everyonewouldjustdothat.Afieldyoucan

    masterinaweekisultimatelyboring.Youdon’thearofsomeone’sdistinguishedcareerinditchdigging.

    note:Maybeyoudo;Ididn’tresearchthatanalogy.ForallIknow,therecouldbeavidditchdigginghobbyists,ditch

    StrikingaBalancediggingconventions,andawholesubculturearoundit.WhoamItojudge?

    Tome,thishasmuchincommonwithgamesthemselves.Agamelikechesscanneverbemasteredbecauseallofthe

    piecesaresoperfectlybalancedagainstoneanother.Thismeansyoucanspendyourlifeexploringthevastspaceof

    viablestrategies.Apoorlydesignedgamecollapsestotheonewinningtacticplayedoverandoveruntilyougetboredand

    quit.

    Lately,Ifeellikeifthereisanymethodthateasestheseconstraints,it’ssimplicity.Inmycodetoday,Itryveryhardtowrite

    thecleanest,mostdirectsolutiontotheproblem.Thekindofcodewhereafteryoureadit,youunderstandexactlywhatit

    doesandcan’timagineanyotherpossiblesolution.

    Iaimtogetthedatastructuresandalgorithmsright(inaboutthatorder)andthengofromthere.IfindifIcankeepthings

    simple,there’slesscodeoverall.Thatmeanslesscodetoloadintomyheadinordertochangeit.

    Itoftenrunsfastbecausethere’ssimplynotasmuchoverheadandnotmuchcodetoexecute.(Thiscertainlyisn’talways

    thecasethough.Youcanpackalotofloopingandrecursioninatinyamountofcode.)

    However,notethatI’mnotsayingsimplecodetakeslesstimetowrite.You’dthinkitwouldsinceyouendupwithlesstotal

    code,butagoodsolutionisn’tanaccretionofcode,it’sadistillationofit.

    note:BlaisePascalfamouslyendedaletterwith,“Iwouldhavewrittenashorterletter,butIdidnothavethetime.”

    AnotherchoicequotecomesfromAntoinedeSaint-Exupery:“Perfectionisachieved,notwhenthereisnothingmore

    toadd,butwhenthereisnothinglefttotakeaway.”

    Closertohome,I’llnotethateverytimeIreviseachapterinthisbook,itgetsshorter.Somechaptersaretightened

    by20%bythetimethey’redone.

    We’rerarelypresentedwithanelegantproblem.Instead,it’sapileofusecases.YouwanttheXtodoYwhenZ,butW

    whenA,andsoon.Inotherwords,alonglistofdifferentexamplebehaviors.

    Thesolutionthattakestheleastmentaleffortistojustcodeupthoseusecasesoneatatime.Ifyoulookatnovice

    programmers,that’swhattheyoftendo:theychurnoutreamsofconditionallogicforeachcasethatpoppedintotheirhead.

    Butthere’snothingelegantinthat,andcodeinthatstyletendstofalloverwhenpresentedwithinputevenslightlydifferent

    thantheexamplesthecoderconsidered.Whenwethinkofelegantsolutions,whatweoftenhaveinmindisageneralone:

    asmallbitoflogicthatstillcorrectlycoversalargespaceofusecases.

    Findingthatisabitlikepatternmatchingorsolvingapuzzle.Ittakesefforttoseethroughthescatteringofexampleuse

    casestofindthehiddenorderunderlyingthemall.It’sagreatfeelingwhenyoupullitoff.

    Almosteveryoneskipstheintroductorychapters,soIcongratulateyouonmakingitthisfar.Idon’thavemuchinreturnfor

    yourpatience,butI’llofferupafewbitsofadvicethatIhopemaybeusefultoyou:

    Abstractionanddecouplingmakeevolvingyourprogramfasterandeasier,butdon’twastetimedoingthemunless

    you’reconfidentthecodeinquestionneedsthatflexibility.

    Thinkaboutanddesignforperformancethroughoutyourdevelopmentcycle,butputoffthelow-level,nitty-gritty

    optimizationsthatlockassumptionsintoyourcodeuntilaslateaspossible.

    note:Trustme,twomonthsbeforeshippingisnotwhenyouwanttostartworryingaboutthatnagginglittle“game

    onlyrunsat1FPS”problem.

    Simplicity

    GetOnWithIt,AlreadyMovequicklytoexploreyourgame’sdesignspace,butdon’tgosofastthatyouleaveamessbehindyou.You’llhave

    tolivewithit,afterall.

    Ifyouaregoingtoditchcode,don’twastetimemakingitpretty.Rockstarstrashhotelroomsbecausetheyknow

    they’regoingtocheckoutthenextday.

    But,mostofall,ifyouwanttomakesomethingfun,havefunmakingit.《设计模式:可复用面向对象软件的基础》一书已经出版了将近20年。不过通过阅读本章,你将有机会重温和理解设计模

    式。软件行业发展迅速,这本书确实有些古老了。这本书经久不衰说明比起许多框架和方法来说,设计模式更加永恒。

    然而我认为设计模式到了今天仍然重要,我们从过去几十年中学习到了许多东西。在这个章节中,我们将重温四人帮(Gang

    ofFour)的几个原作设计模式。针对每一种模式,我都希望能写出一些有用、有趣的东西。

    我认为有些模式被过度使用(单例模式),而另一些却又被冷落(命令模式)。书中还涉及到两个,我想探讨他们与游戏的

    相关性(享元模式和观察者模式)。最后,有时候我只是觉得了解设计模式在更大点的编程领域的表现是蛮有趣的(原型模式

    和状态模式)。

    命令模式

    享元模式

    观察者模式

    原型模式

    单例模式

    状态模式

    ===============================上一节

    目录

    下一节

    再探设计模式

    模式命令模式是我最喜爱的模式之一。在我写过的许多大型游戏或者其他程序中,都有用到它。正确的使用它,会让你的代码变

    得更加优雅。对于这个模式,GangofFour有着一个预见性的深奥说明:

    将一个请求(request)封装成一个对象,从而让你使用不同的请求,请求队列或请求日志来参数化客户端,同时支持请

    求操作的撤销与恢复。

    我想你也和我一样觉得这句话晦涩难明。首先,它分割了自己试图建立的物象。在软件世界之外,一词往往多义。“客户

    (client)”就是一个人的意思-一个你与它做生意的人。据我查证,人类(humanbeings)是不可“参数化”的。(译者注:作者

    在这里的意思是GangofFour的说明因为太具概括性,涵盖了软件开发之外的一些定义,使得句子很难理解。)

    然后,句子的剩余部分就像你可能使用的模式的一串列表一样。不是特别明朗,除非你的用例恰巧在列表中。我对命令模式

    的精炼(pithy)概括如下:

    命令就是一个对象化(实例化)的方法调用。(Acommandisareifiedmethodcall.)

    当然,“精炼”(pithy)通常意味着“令人费解的简洁”,所以我可能改得不是很好。让我解释一下:你可能没听过“Reify”一词,意即“具象化”(makereal)。另一个术语reifying的意思是使一些事物成为“第一类”(first-class)。(译者注:你可能在其他书

    籍中见到说是“第一类值”的类似说法)

    注解

    “Reify”出自拉丁文“res”,意思为“thing”,加上英语后缀“-fy”,所以就成为了“thingify”,坦白说,这是个很有趣的单词。

    这两个术语都意味着,将某个概念(concept)转化为一块数据(data),即一个对象,你可以认为是传入函数的变量等等。所

    以说命令模式是一个“对象化的方法调用”,我的意思就是封装在一个对象的一个方法调用。

    你可能对“回调“(callback),”第一类函数“(first-classfunction),”函数指针“(functionpointer),”闭

    包“(closure),。”局部函数“(partiallyappliedfunction)更耳熟,至于耳熟哪个就取决于你所使用的语言,而它们都具共性。

    TheGangofFour之后这样阐述:

    命令就是回调的面向对象化。(Commandsareanobject-orientedreplacementforcallbacks.)

    这个比他们对模式的概括要好多了。

    注解

    一些语言的反射系统(译者注:如.NET)可以让你在运行时使用类型处理。你可以得到一个对象,它代表着某些其他对象

    的类,你也可以玩玩看类型可以处理哪些问题。换句话说,反射是一个具体化的类型系统。

    但是这些都比较抽象和模糊。正如我所推崇的那样,我喜欢用一些具体点的东西来开头。为弥补这点,现在开始我将举例,它

    们都非常适合命令模式。

    每个游戏都有一处代码块用来读取用户原始输入-按钮点击,键盘事件,鼠标点击,或者其他等等。它记录每次的输入,并将

    命令模式

    目的

    动机

    输入配置之转换为游戏中一个有意义的动作(action):

    注解

    专业级贴士:请勿常按B键。

    下面是个简单的实现:

    voidInputHandler::handleInput

    {

    if(isPressed(BUTTON_X))jump;

    elseif(isPressed(BUTTON_Y))fireGun;

    elseif(isPressed(BUTTON_A))swapWeapon;

    elseif(isPressed(BUTTON_B))lurchIneffectively;

    }

    这个函数通常会通过游戏循环被每帧调用,我想你能理解这段代码在干些什么。如果我们将用户的输入硬关联到游戏的动作

    (gameactions),上面的代码是有效的,但是许多游戏允许用户配置他们的按钮与动作的映射。

    为了支持自定义配置,我们需要把那些对jump和fireGun的直接调用转换为我们可以换出(swapout)的东西。”换

    出“(swappingout)听起来很像分配变量,所以我们需要个对象来代表一个游戏动作。这就用到了命令模式。

    我们定义了一个基类用来代表一个可激活的游戏命令:

    classCommand

    {

    public:

    virtual~Command{}

    virtualvoidexecute=0;

    };

    注解

    当你的接口仅有一个无返回值的方法时,很有可能就会用到命令模式。

    然后我们为每个不同的游戏动作创建一个子类:

    classJumpCommand:publicCommand

    {

    public:

    virtualvoidexecute{jump;}

    };

    classFireCommand:publicCommand

    {

    public:

    virtualvoidexecute{fireGun;}

    };

    Yougettheidea...在我们的输入处理中,我们为每个按钮存储一个指针指向他们。

    classInputHandler

    {

    public:

    voidhandleInput;

    Methodstobindcommands...

    private:

    CommandbuttonX_;

    CommandbuttonY_;

    CommandbuttonA_;

    CommandbuttonB_;

    };

    现在输入处理成了下面这样:

    voidInputHandler::handleInput

    {

    if(isPressed(BUTTON_X))buttonX_->execute;

    elseif(isPressed(BUTTON_Y))buttonY_->execute;

    elseif(isPressed(BUTTON_A))buttonA_->execute;

    elseif(isPressed(BUTTON_B))buttonB_->execute;

    }

    注解

    注意到我们这里没有检查命令是否为null没?这里假设了每个按钮有某个命令与之对应关联。

    如果你想要支持不处理任何事情的按钮,而不用明确检查是否为null,我们可以定义一个命令类,这个命令类中

    的execute方法不做任何事情。然后,我们将按钮处理器(buttonhandler)指向一个空对象(nullobject)代替指向

    null。这个模式叫空对象(NullObject)。

    以前每个输入都会直接调用一个函数,现在则会有一个间接调用层。

    简而言之,这就是命令模式。如果你已经看到了它的优点,不妨看完本章的剩余部分。

    我们刚才定义的命令类在上个例子中是有效的,但他们很受限。问题在于,他们假设存在jump,fireGun等这样的能找

    到玩家的头像,使得玩家像木偶一样进行动作处理的顶级函数。

    模式

    关于角色的说明这种假设耦合限制了这些命令的的效用。JumpCommand类唯一能做的事情就是使得player进行跳跃。让我们放宽限制。

    我们传进去一个我们想要控制的对象而不是用命令对象自身来调用函数:

    classCommand

    {

    public:

    virtual~Command{}

    virtualvoidexecute(GameActoractor)=0;

    };

    这里,GameActor是我们用来表示游戏世界中的角色的”游戏对象“类。我们将它传入execute中,以便子类化的命令可以

    针对我们选择的角色进行调用,就像这样:

    classJumpCommand:publicCommand

    {

    public:

    virtualvoidexecute(GameActoractor)

    {

    actor.jump;

    }

    };

    现在,我们可以使用这个类来让游戏中的任何角色进行来回跳动。在输入处理和记录命令以及调用正确的对象之间,我们缺

    少了一部分。首先,我们改变下handleInput,像下面这样返回一个命令(commands):

    CommandInputHandler::handleInput

    {

    if(isPressed(BUTTON_X))returnbuttonX_;

    if(isPressed(BUTTON_Y))returnbuttonY_;

    if(isPressed(BUTTON_A))returnbuttonA_;

    if(isPressed(BUTTON_B))returnbuttonB_;

    Nothingpressed,sodonothing.

    returnNULL;

    }

    它不能直接执行命令,因为它并不知道该传入那个角色对象。命令是一个具体化的调用,这里正是我们可以利用的地方-我们

    可以延迟调用。

    然后,我们需要一些代码来保存命令并且执行对玩家角色的调用。像下面这样:

    Commandcommand=inputHandler.handleInput;

    if(command)

    {

    command->execute(actor);

    }

    假设actor是玩家角色的一个引用,这将会基于用户的输入来驱动角色,所以我们可以赋予角色与前例一致的行为。在命令

    和角色之间加入的间接层使得我们可以让玩家控制游戏中的任何角色,只需通过改变命令执行时传入的角色对象即可。

    在实践中,这并不是一个常见的功能,但是有一种情况却经常见到。迄今为止,我们只考虑了玩家驱动角色(player-driven

    character),但是对于游戏世界中的其他角色呢?他们由游戏的AI来驱动。我们可以使用相同的命令模式来作为AI引擎和角

    色的接口;AI代码部分提供命令(Command)对象用来执行。(译者注:command->execute(AI对象);)

    AI选择命令,角色执行命令,它们之间的解耦给了我们很大的灵活性。我们可以为不同的角色使用不同的AI模块。或者我们

    可以为不同种类的行为混合AI。你想要一个更加具有侵略性的敌人?只需要插入一段更具侵略性的AI代码来为它生成命令。

    事实上,我们甚至可以将AI使用到玩家的角色身上,这对于像游戏需要自动运行的demo模式是很有用的。

    通过将控制角色的命令作为第一类对象,我们便去掉了直接的函数调用这样的紧耦合。相反的,把它想象成一个队列或者一

    个命令流(queueorstreamofcommands):注解

    关于队列更多信息,见事件队列

    注解

    为什么我感觉有必要通过图片来解释“流(stream)”呢?为什么它看起来就像一个管道(tube)一样?

    一些代码(输入处理(theinputhandler)或者AI)生成命令并将它们放置于命令流中,一些代码(发送者(the

    dispatcher)或者角色自身(actor))执行命令并且调用它们。通过中间的队列,我们解耦了一端的生产者和另一端的消费

    者。

    注解

    如果我们把这些命令序列化,我们便可以通过互联网发送数据流。我们可以把玩家的输入,通过网络发送到另外一台

    机器上,然后进行回放。这是多人网络游戏很重要的一块。

    最后这个例子(译者注:作者指的是撤销和重做)是命令模的成名应用了。如果一个命令对象可以do一些事情,那么应该

    可以很轻松的undo(撤销)它们。撤销这个行为经常在一些策略游戏中见到,在游戏中如果你不喜欢的话可以回滚一些步

    骤。在创建游戏时这是一个很常见的工具。如果你想让你的游戏设计师们讨厌你,最可靠的办法就是在关卡编辑器中不要提

    供撤销命令,让他们不能撤销不小心犯的错误。

    注解

    这里可能是我的经验之谈。

    如果没有命令模式,实现撤销是很困难的。有了它,小菜一碟啊。我们假定一个情景,我们在制作一款单人回合制的游戏,我们想让我们的玩家能够撤销一些行动以便他们能够更多的专注于策略而不是猜测。

    我们已经可以很方便的使用命令模式来抽象输入处理,所以每次对角色的移动要封装起来。例如,像下面这样来移动一个单

    位:

    classMoveUnitCommand:publicCommand

    {

    public:

    MoveUnitCommand(Unitunit,intx,inty)

    :unit_(unit),x_(x),y_(y)

    {}

    virtualvoidexecute

    {

    unit_->moveTo(x_,y_);

    }

    private:

    Unitunit_;

    intx_,y_;

    };

    注意到这个和我们上一个命令不太相同。在上个例子中,我们想要抽象出命令,执行命令时可以针对不同的角色。在这个例

    撤销和重做(UndoandRedo)子中,我们特别希望将命令绑定到移动的单位上。这个命令的实例不是一般性质的”移动某些物体“这样适用于很多情境下的

    的操作,在游戏的回合次序中,它是一个特定具体的移动。

    这凸显了命令模式在实现时的一个变化。在某些情况下,像我们的第一对的例子,一个命令代表了一个可重用的对象,表示

    athingthatcanbedone(一件可完成的事情)。我们前面的输入处理程序仅针对单一的命令对象,并要求在对应按钮被按

    下的时候其execute方法被调用。

    这里,这些命令更加具体。他们表示athingthatcanbedoneataspecificpointintime(一件可在特定时间点完成的事

    情)。这意味着每次玩家选择移动,输入处理程序代码都会创建一个命令实例。像下面这样:

    CommandhandleInput

    {

    Gettheselectedunit...

    Unitunit=getSelectedUnit;

    if(isPressed(BUTTON_UP)){

    Movetheunitupone.

    intdestY=unit->y-1;

    returnnewMoveUnitCommand(unit,unit->x,destY);

    }

    if(isPressed(BUTTON_DOWN)){

    Movetheunitdownone.

    intdestY=unit->y+1;

    returnnewMoveUnitCommand(unit,unit->x,destY);

    }

    Othermoves...

    returnNULL;

    }

    一次性命令的特质很快能为我们所用。为了撤销命令,我们定义了一个操作,每个命令类都需要来实现它:

    classCommand

    {

    public:

    virtual~Command{}

    virtualvoidexecute=0;

    virtualvoidundo=0;

    };

    注解

    当然了,在没有垃圾回收的语言如C++中,这意味着执行命令的代码也要负责释放它们申请的内存。

    undo方法会反转由对应的execute方法改变的游戏状态。下面我们针对上一个移动命令加入了撤销支持:

    classMoveUnitCommand:publicCommand

    {

    public:

    MoveUnitCommand(Unitunit,intx,inty)

    :unit_(unit),xBefore_(0),yBefore_(0),x_(x),y_(y)

    {}

    virtualvoidexecute

    {

    Remembertheunit'spositionbeforethemove

    sowecanrestoreit.

    xBefore_=unit_->x;

    yBefore_=unit_->y;

    unit_->moveTo(x_,y_);

    }virtualvoidundo

    {

    unit_->moveTo(xBefore_,yBefore_);

    }

    private:

    Unitunit_;

    intxBefore_,yBefore_;

    intx_,y_;

    };

    注意到我们在类中添加了一些状态。当单位移动时,它会忘记它刚才在哪。如果我们要撤销移动,我们得记录单位的上一次

    位置,正是xBefore_和yBefore_变量的功能。

    注解

    这看起来挺像备忘录模式的,但是我发现备忘录模式用在这里并不能有效的工作。因为命令试图去修改一个对象状态

    的一小部分,而为对象的其他数据创建快照是浪费内存。只手动存储被修改的部分相对来说就节省很多内存了。

    持久化数据结构是另一个选择。通过它们,每次对一个对象进行修改都会返回一个新的对象,保留原对象不变。通过

    这样明智的实现,这些新对象与原对象共享数据,所以比拷贝整个对象的代价要小的多。

    使用持久化数据结构,每个命令存储着命令执行前对象的一个引用,所以撤销意味着切换到原来老的对象。

    为了让玩家能够撤销一次移动,我们保留了他们执行的上一个命令。当他们敲击Control-Z时,我们便会调用undo方法。

    (如果他们已经撤销了,那么会变为”重做“,我们会再次执行那个命令。)

    支持多次撤销并不难。这次我们不再保存最后一个命令,取而代之的是,我们保存了一个命令列表和”current“(当前)命令

    的一个引用。当玩家执行了一个命令,我们将这个命令添加到列表中,并将”current“指向它。

    当玩家选择”撤销“时,我们撤销当前的命令并且将当前的指针移回去。当他们选择”重做“,我们将指针前移然后执行命令。如

    果他们在撤销之后选择了一个新的命令,列表中位于当前命令之后的所有命令被舍弃掉。

    我第一次在一个关卡编辑器中实现了这一点,顿时自我感觉良好。我很惊讶它是如此的简单而且高效。我们需要指定规则来

    确保每个数据的更改都经由一个命令实现,但只要定了规则,剩下的就容易得多。

    注解

    重做在游戏中并不常见,但回放(re-play)却不是。一个很老实的实现方法就是记录每一帧的游戏状态以便能够回

    放,但是这样会使用大量的内存。

    相反,许多游戏会记录每一帧每个实体所执行的一系列命令。为了回放游戏,引擎只需要运行正常游戏的模拟,执行

    预先录制的命令。

    设计决策

    类风格化还是函数风格化?此前,我说命令(commands)和第一类函数或者闭包相似,但是这里我举的每个例子都用了类定义。如果你熟悉函数式编

    程,你可能想知道如何用函数式风格实现命令模式。

    我用这种方式写例子是因为C++对于第一类函数的支持非常有限。函数指针无须过多阐述,仿函数(译者注:关于仿函数可

    以看百科的介绍)看起来比较怪异,还需要定义一个类,C++11中的闭包使用起来比较棘手因为要手动管理内存。

    这并不是说在其他语言中你不应该使用函数来实现命令模式。如果你使用的语言中有闭包的实现,无论怎样,使用它们!在

    某些方面(Insomeways),命令模式对于没有闭包的语言来说是模拟闭包的一种方式。

    注解

    我说在某些方面(Insomeways),是因为即使在有闭包的语言中为命令构建实际的类或结构仍然是有用的。如果你的

    命令有多个操作(如可撤销命令),映射到一个单一函数是比较尴尬的。

    定义一个实际的附带字段的类也有助于读者很容易分辨该命令中包含哪些数据。闭包自动包装一些状态是比较简洁,但它们太过于自动化了以至于很难分辨出它们实际上持有的状态。

    举个例子,如果我们在用JavaScript编写游戏,我们可以像下面这样创建一个单位移动命令:

    functionmakeMoveUnitCommand(unit,x,y){

    Thisfunctionhereisthecommandobject:

    returnfunction{

    unit.moveTo(x,y);

    }

    }

    我们也可以通过闭包来添加对撤销的支持:

    functionmakeMoveUnitCommand(unit,x,y){

    varxBefore,yBefore;

    return{

    execute:function{

    xBefore=unit.x;

    yBefore=unit.y;

    unit.moveTo(x,y);

    },undo:function{

    unit.moveTo(xBefore,yBefore);

    }

    };

    }

    如果你熟悉函数式风格,上面这么做你会感到很自然。如果不熟悉,我希望这个章节能够帮助你了解一些。对于我来说,命

    令模式的作用能够真正的显示函数式编程在解决许多问题时是多么的高效。

    你可能最终会有很多不同的命令类。为了更容易地实现这些类,定义一个具体的基类,里面有着一些方便的高层次的方

    法,这样派生的命令可以将它们组合来定义自身的行为,这么做通常是有帮助的。它会将命令的主要方法execute变

    成子类沙盒。

    在我们的例子中,我们明确地选择了那些角色会执行一个命令。在某些情况下,尤其是在对象模型是分层的情况下,它

    可能没这么直观。一个对象可以响应一个命令,或者它可以决定于关闭一些从属对象。如果你这样做,你需要了解下责

    任链(ChainofResponsibility)。

    一些命令如第一个例子中的JumpCommand是一些纯行为的代码块,无需过多阐述。在类似情况下,拥有不止一个这样命

    令类的实例会浪费内存,因为所有的实例是等价的。享元模式就是解决这个问题的。

    注解

    你可以用单例模式实现它,但我奉劝你别这么做。

    参考===============================上一节

    目录

    下一节云雾散去,露出一片巨大而又古老、绵延的深林。数不清的远古铁杉,高出你而形成一个绿色的大教堂。叶子像彩色玻璃窗一

    样,将阳光打碎成一束束金黄色的雾气。巨大的树干之间,你能在远出制作出巨大的深林。对于我们游戏开发者来说,这是

    一种超凡脱俗的梦境设置,这样的场景我们经常通过一个模式来实现,它的名字可能并不常见,那就是享元模式。

    我能用简短的语句描述绵延不绝的深林,然而在一个虚拟现实游戏中去实现它却又是另一回事。当你看到各种形状不一的树

    填满整个屏幕时,在图形程序员眼里看到的,却是每隔60分之一秒就必须渲染进GPU的数以百万计的多边形。

    我们谈到成千上万的树,每一颗的几何结构又都包含了成千上万的多边形。即便你有足够的内存来描述这片深林,到了需要

    渲染的时候,这些数据还必须像搭乘巴士一样从CPU传输到GPU。每一颗树都有一堆与之相关联的数据:

    交织成网状的定义了树的主干、树枝以及树叶的多边形。

    树皮和树叶的贴图。

    它在深林中的位置以及朝向。

    大量的参数像大小、颜色等,这样每棵树看起来都不一样。

    如果你将它在代码中表述出来,那么你将得到下面的东西:

    classTree

    {

    private:

    Meshmesh_;

    Texturebark_;

    Textureleaves_;

    Vectorposition_;

    doubleheight_;

    doublethickness_;

    ColorbarkTint_;

    ColorleafTint_;

    };

    数据很多,并且网格和贴图数据还挺大。整个深林的物体在一帧中被扔进去GPU就太多了,所幸的是,有一个很古老的窍门

    来处理这个。

    关键的是,即便深林中有成千上万的树,它们大部分看起来相似。它们大部分都用同样的网格和贴图。这就意味着远近许多

    物体间都有着许多共性。

    我们通过把物体分块能很明显地模拟这一切,首先,我们取出所有树共有的数据放到一个单独的类:

    享元模式

    构成深林的树classTreeModel

    {

    private:

    Meshmesh_;

    Texturebark_;

    Textureleaves_;

    };

    游戏只需要一个这样的类,因为没有道理在内存中存有成千上万个同样的网格和贴图。因此,每一个树的实例,都用一个指

    向共享的TreeModel这个类的引用。这就意味着,在Tree这个类里有这么一个指针:

    classTree

    {

    private:

    TreeModelmodel_;

    Vectorposition_;

    doubleheight_;

    doublethickness_;

    ColorbarkTint_;

    ColorleafTint_;

    };

    你可以这样形象地描述:

    这样能很好的在主内存中存储物体,不过这却对渲染没太大帮助。在树林搬到屏幕上之前,它必须自己搬运到GPU,我们必

    须用显卡能理解的方式来表达资源共享。

    一千个实例^titleObserver^sectionDesignPatternsRevisited

    Youcan'tthrowarockatacomputerwithouthittinganapplicationbuiltusingtheModel-View-Controllerarchitecture,and

    underlyingthatistheObserverpattern.ObserverissopervasivethatJavaputitinitscorelibrary(java.util.Observer)and

    Cbakeditrightintothelanguage(theeventkeyword).

    Likesomanythingsinsoftware,MVCwasinventedbySmalltalkersintheseventies.Lispersprobablyclaimtheycameup

    withitinthesixtiesbutdidn'tbotherwritingitdown.

    ObserverisoneofthemostwidelyusedandwidelyknownoftheoriginalGangofFourpatterns,butthegame

    developmentworldcanbestrangelycloisteredattimes,somaybethisisallnewstoyou.Incaseyouhaven'tlefttheabbey

    inawhile,letmewalkyouthroughamotivatingexample.

    Saywe'readdinganachievementssystemtoourgame.Itwillfeaturedozensofdifferentbadgesplayerscanearnfor

    completingspecificmilestoneslikeKill100MonkeyDemons,FalloffaBridge,orCompleteaLevelWieldingOnlya

    DeadWeasel.

    Achievement:WeaselWielder

    IswearIhadnodoublemeaninginmindwhenIdrewthis.

    Thisistrickytoimplementcleanlysincewehavesuchawiderangeofachievementsthatareunlockedbyallsortsof

    differentbehaviors.Ifwearen'tcareful,tendrilsofourachievementsystemwilltwinetheirwaythrougheverydarkcornerof

    ourcodebase.Sure,FalloffaBridgeissomehowtiedtothephysicsengine,butdowereallywanttoseeacallto

    unlockFallOffBridgerightinthemiddleofthelinearalgebrainourcollisionresolutionalgorithm?

    Thisisarhetoricalquestion.Noself-respectingphysicsprogrammerwouldeverletussullytheirbeautifulmathematicswith

    somethingaspedestrianasgameplay.

    Whatwe'dlike,asalways,istohaveallthecodeconcernedwithonefacetofthegamenicelylumpedinoneplace.The

    challengeisthatachievementsaretriggeredbyabunchofdifferentaspectsofgameplay.Howcanthatworkwithout

    couplingtheachievementcodetoallofthem?

    That'swhattheobserverpatternisfor.Itletsonepieceofcodeannouncethatsomethinginterestinghappenedwithout

    actuallycaringwhoreceivesthenotification.

    Forexample,we'vegotsomephysicscodethathandlesgravityandtrackswhichbodiesarerelaxingonniceflatsurfaces

    andwhichareplummetingtowardsuredemise.ToimplementtheFalloffaBridgebadge,wecouldjustjamthe

    achievementcoderightinthere,butthat'samess.Instead,wecanjustdo:

    ^codephysics-update

    Allitdoesissay,Uh,Idon'tknowifanyonecares,butthisthingjustfell.Dowiththatasyouwill.

    Observer

    AchievementUnlockedThephysicsenginedoeshavetodecidewhatnotificationstosend,soitisn'tentirelydecoupled.Butinarchitecture,we're

    mostoftentryingtomakesystemsbetter,notperfect.

    Theachievementsystemregistersitselfsothatwheneverthephysicscodesendsanotification,theachievementsystem

    receivesit.Itcanthenchecktoseeifthefallingbodyisourless-than-gracefulhero,andifhisperchpriortothisnew,unpleasantencounterwithclassicalmechanicswasabridge.Ifso,itunlockstheproperachievementwithassociated

    fireworksandfanfare,anditdoesallofthiswithnoinvolvementfromthephysicscode.

    Infact,wecanchangethesetofachievementsortearouttheentireachievementsystemwithouttouchingalineofthe

    physicsengine.Itwillstillsendoutitsnotifications,oblivioustothefactthatnothingisreceivingthemanymore.

    Ofcourse,ifwepermanentlyremoveachievementsandnothingelseeverlistenstothephysicsengine'snotifications,we

    mayaswellremovethenotificationcodetoo.Butduringthegame'sevolution,it'snicetohavethisflexibility.

    Ifyoudon'talreadyknowhowtoimplementthepattern,youcouldprobablyguessfromthepreviousdescription,butto

    keepthingseasyonyou,I'llwalkthroughitquickly.

    We'llstartwiththenosyclassthatwantstoknowwhenanotherobjectdoessomethinginteresting.Theseinquisitiveobjects

    aredefinedbythisinterface:

    ^codeobserver

    TheparameterstoonNotifyareuptoyou.That'swhythisistheObserverpatternandnottheObserverready-made

    codeyoucanpasteintoyourgame.Typicalparametersaretheobjectthatsentthenotificationandagenericdata

    parameteryoustuffotherdetailsinto.Ifyou'recodinginalanguagewithgenericsortemplates,you'llprobablyusethem

    here,butit'salsofinetotailorthemtoyourspecificusecase.Here,I'mjusthardcodingittotakeagameentityandan

    enumthatdescribeswhathappened.

    Anyconcreteclassthatimplementsthisbecomesanobserver.Inourexample,that'stheachievementsystem,sowe'd

    havesomethinglikeso:

    ^codeachievement-observer

    Thenotificationmethodisinvokedbytheobjectbeingobserved.InGangofFourparlance,thatobjectiscalledthe

    subject.Ithastwojobs.First,itholdsthelistofobserversthatarewaitingoh-so-patientlyforamissivefromit:

    ^codesubject-list

    Inrealcode,youwoulduseadynamically-sizedcollectioninsteadofadumbarray.I'mstickingwiththebasicsherefor

    peoplecomingfromotherlanguageswhodon'tknowC++'sstandardlibrary.

    TheimportantbitisthatthesubjectexposesapublicAPIformodifyingthatlist:

    ^codesubject-register

    Thatallowsoutsidecodetocontrolwhoreceivesnotifications.Thesubjectcommunicateswiththeobservers,butitisn't

    coupledtothem.Inourexample,nolineofphysicscodewillmentionachievements.Yet,itcanstilltalktotheachievements

    system.That'sthecleverpartaboutthispattern.

    It'salsoimportantthatthesubjecthasalistofobserversinsteadofasingleone.Itmakessurethatobserversaren't

    implicitlycoupledtoeachother.Forexample,saytheaudioenginealsoobservesthefalleventsothatitcanplayan

    appropriatesound.Ifthesubjectonlysupportedoneobserver,whentheaudioengineregistereditself,thatwouldun-

    registertheachievementssystem.

    HowitWorks

    Theobserver

    ThesubjectThatmeansthosetwosystemswouldinterferewitheachother--andinaparticularlynastyway,sincethesecondwould

    disablethefirst.Supportingalistofobserversensuresthateachobserveristreatedindependentlyfromtheothers.Asfar

    astheyknow,eachistheonlythingintheworldwitheyesonthesubject.

    Theotherjobofthesubjectissendingnotifications:

    ^codesubject-notify

    Notethatthiscodeassumesobserversdon'tmodifythelistintheironNotifymethods.Amorerobustimplementation

    wouldeitherpreventorgracefullyhandleconcurrentmodificationlikethat.

    Now,wejustneedtohookallofthisintothephysicsenginesothatitcansendnotificationsandtheachievementsystem

    canwireitselfuptoreceivethem.We'llstayclosetotheoriginalDesignPatternsrecipeandinheritSubject:

    ^codephysics-inherit

    ThisletsusmakenotifyinSubjectprotected.Thatwaythederivedphysicsengineclasscancallittosend

    notifications,butcodeoutsideofitcannot.Meanwhile,addObserverandremoveObserverarepublic,soanythingthat

    cangettothephysicssystemcanobserveit.

    Inrealcode,Iwouldavoidusinginheritancehere.Instead,I'dmakePhysicshaveaninstanceofSubject.Insteadof

    observingthephysicsengineitself,thesubjectwouldbeaseparatefallingeventobject.Observerscouldregister

    themselvesusingsomethinglike:^codephysics-eventTome,thisisthedifferencebetweenobserversystemsand

    eventsystems.Withtheformer,youobservethethingthatdidsomethinginteresting.Withthelatter,youobservean

    objectthatrepresentstheinterestingthingthathappened.

    Now,whenthephysicsenginedoessomethingnoteworthy,itcallsnotifylikeinthemotivatingexamplebefore.That

    walkstheobserverlistandgivesthemalltheheadsup.

    Prettysimple,right?Justoneclassthatmaintainsalistofpointerstoinstancesofsomeinterface.It'shardtobelievethat

    somethingsostraightforwardisthecommunicationbackboneofcountlessprogramsandappframeworks.

    ButtheObserverpatternisn'twithoutitsdetractors.WhenI'veaskedothergameprogrammerswhattheythinkaboutthis

    pattern,theybringupafewcomplaints.Let'sseewhatwecandotoaddressthem,ifanything.

    Ihearthisalot,oftenfromprogrammerswhodon'tactuallyknowthedetailsofthepattern.Theyhaveadefaultassumption

    thatanythingthatsmellslikeadesignpatternmustinvolvepilesofclassesandindirectionandothercreativewaysof

    squanderingCPUcycles.

    TheObserverpatterngetsaparticularlybadrapherebecauseit'sbeenknowntohangaroundwithsomeshadycharacters

    namedevents,messages,andevendatabinding.Someofthosesystemscanbeslow(oftendeliberately,andfor

    goodreason).Theyinvolvethingslikequeuingordoingdynamicallocationforeachnotification.

    ThisiswhyIthinkdocumentingpatternsisimportant.Whenwegetfuzzyaboutterminology,welosetheabilityto

    communicateclearlyandsuccinctly.Yousay,Observer,andsomeonehearsEventsorMessagingbecauseeitherno

    onebotheredtowritedownthedifferenceortheydidn'thappentoreadit.That'swhatI'mtryingtodowiththisbook.To

    covermybases,I'vegotachapteroneventsandmessagestoo:EventQueue.

    But,nowthatyou'veseenhowthepatternisactuallyimplemented,youknowthatisn'tthecase.Sendinganotificationis

    simplywalkingalistandcallingsomevirtualmethods.Granted,it'sabitslowerthanastaticallydispatchedcall,butthat

    costisnegligibleinallbutthemostperformance-criticalcode.

    Ifindthispatternfitsbestoutsideofhotcodepathsanyway,soyoucanusuallyaffordthedynamicdispatch.Asidefrom

    Observablephysics

    It'sTooSlowthat,there'svirtuallynooverhead.Wearen'tallocatingobjectsformessages.There'snoqueueing.It'sjustanindirection

    overasynchronousmethodcall.

    Infact,youhavetobecarefulbecausetheObserverpatternissynchronous.Thesubjectinvokesitsobserversdirectly,whichmeansitdoesn'tresumeitsownworkuntilalloftheobservershavereturnedfromtheirnotificationmethods.Aslow

    observercanblockasubject.

    Thissoundsscary,butinpractice,it'snottheendoftheworld.It'sjustsomethingyouhavetobeawareof.UIprogrammers--who'vebeendoingevent-basedprogramminglikethisforages--haveatime-wornmottoforthis:stayofftheUIthread.

    Ifyou'rerespondingtoaneventsynchronously,youneedtofinishandreturncontrolasquicklyaspossiblesothattheUI

    doesn'tlockup.Whenyouhaveslowworktodo,pushitontoanotherthreadoraworkqueue.

    Youdohavetobecarefulmixingobserverswiththreadingandexplicitlocks,though.Ifanobservertriestograbalockthat

    thesubjecthas,youcandeadlockthegame.Inahighlythreadedengine,youmaybebetteroffwithasynchronous

    communicationusinganEventQueue.

    Wholetribesoftheprogrammerclan--includingmanygamedevelopers--havemovedontogarbagecollectedlanguages,anddynamicallocationisn'ttheboogiemanthatitusedtobe.Butforperformance-criticalsoftwarelikegames,memory

    allocationstillmatters,eveninmanagedlanguages.Dynamicallocationtakestime,asdoesreclaimingmemory,evenifit

    happensautomatically.

    Manygamedevelopersarelessworriedaboutallocationandmoreworriedaboutfragmentation.Whenyourgameneedsto

    runcontinuouslyfordayswithoutcrashinginordertogetcertified,anincreasinglyfragmentedheapcanpreventyoufrom

    shipping.TheObjectPoolchaptergoesintomoredetailaboutthisandacommontechniqueforavoidingit.

    Intheexamplecodebefore,IusedafixedarraybecauseI'mtryingtokeepthingsdeadsimple.Inrealimplementations,the

    observerlistisalmostalwaysadynamicallyallocatedcollectionthatgrowsandshrinksasobserversareaddedand

    removed.Thatmemorychurnspookssomepeople.

    Ofcourse,thefirstthingtonoticeisthatitonlyallocatesmemorywhenobserversarebeingwiredup.Sendinganotification

    requiresnomemoryallocationwhatsoever--it'sjustamethodcall.Ifyouhookupyourobserversatthestartofthegame

    anddon'tmesswiththemmuch,theamountofallocationisminimal.

    Ifit'sstillaproblem,though,I'llwalkthroughawaytoimplementaddingandremovingobserverswithoutanydynamic

    allocationatall.

    Inthecodewe'veseensofar,SubjectownsalistofpointerstoeachObserverwatchingit.TheObserverclassitselfhas

    noreferencetothislist.It'sjustapurevirtualinterface.Interfacesarepreferredoverconcrete,statefulclasses,sothat's

    generallyagoodthing.

    ButifwearewillingtoputabitofstateinObserver,wecansolveourallocationproblembythreadingthesubject'slist

    throughtheobserversthemselves.Insteadofthesubjecthavingaseparatecollectionofpointers,theobserverobjects

    becomenodesinalinkedlist:

    Toimplementthis,firstwe'llgetridofthearrayinSubjectandreplaceitwithapointertotheheadofthelistofobservers:

    ^codelinked-subject

    Thenwe'llextendObserverwithapointertothenextobserverinthelist:

    It'stoofast?

    ItDoesTooMuchDynamicAllocation

    Linkedobservers^codelinked-observer

    We'realsomakingSubjectafriendclasshere.ThesubjectownstheAPIforaddingandremovingobservers,butthelistit

    willbemanagingisnowinsidetheObserverclassitself.Thesimplestwaytogiveittheabilitytopokeatthatlistisby

    makingitafriend.

    Registeringanewobserverisjustwiringitintothelist.We'lltaketheeasyoptionandinsertitatthefront:

    ^codelinked-add

    Theotheroptionistoaddittotheendofthelinkedlist.Doingthataddsabitmorecomplexity.Subjecthastoeitherwalk

    thelisttofindtheendorkeepaseparatetail_pointerthatalwayspointstothelastnode.

    Addingittothefrontofthelistissimpler,butdoeshaveonesideeffect.Whenwewalkthelisttosendanotificationto

    everyobserver,themostrecentlyregisteredobservergetsnotifiedfirst.SoifyouregisterobserversA,B,andC,inthat

    order,theywillreceivenotificationsinC,B,Aorder.

    Intheory,thisdoesn'tmatteronewayortheother.It'satenetofgoodobserverdisciplinethattwoobserversobservingthe

    samesubjectshouldhavenoorderingdependenciesrelativetoeachother.Iftheorderingdoesmatter,itmeansthosetwo

    observershavesomesubtlecouplingthatcouldendupbitingyou.

    Let'sgetremovalworking:

    ^codelinked-remove

    Removinganodefromalinkedlistusuallyrequiresabitofuglyspecialcasehandlingforremovingtheveryfirstnode,like

    youseehere.There'samoreelegantsolutionusingapointertoapointer.Ididn'tdothatherebecauseitconfusesatleast

    halfthepeopleIshowitto.It'saworthwhileexerciseforyoutodo,though:Ithelpsyoureallythinkintermsofpointers.

    Becausewehaveasinglylinkedlist,wehavetowalkittofindtheobserverwe'reremoving.We'dhavetodothesame

    thingifwewereusingaregulararrayforthatmatter.Ifweuseadoublylinkedlist,whereeachobserverhasapointerto

    boththeobserverafteritandbeforeit,wecanremoveanobserverinconstanttime.Ifthiswererealcode,I'ddothat.

    Theonlythinglefttodoissendanotification.That'sassimpleaswalkingthelist:

    ^codelinked-notify

    Here,wewalktheentirelistandnotifyeverysingleobserverinit.Thisensuresthatalloftheobserversgetequalpriority

    andareindependentofeachother.Wecouldtweakthissuchthatwhenanobserverisnotified,itcanreturnaflag

    indicatingwhetherthesubjectshouldkeepwalkingthelistorstop.Ifyoudothat,you'reprettyclosetohavingtheChainof

    Responsibilitypattern.

    Nottoobad,right?Asubjectcanhaveasmanyobserversasitwants,withoutasinglewhiffofdynamicmemory.

    Registeringandunregisteringisasfastasitwaswithasimplearray.Wehavesacrificedonesmallfeature,though.

    Sinceweareusingtheobserverobjectitselfasalistnode,thatimpliesitcanonlybepartofonesubject'sobserverlist.In

    otherwords,anobservercanonlyobserveasinglesubjectatatime.Inamoretraditionalimplementationwhereeach

    subjecthasitsownindependentlist,anobservercanbeinmorethanoneofthemsimultaneously.

    Youmaybeabletolivewiththatlimitation.Ifinditmorecommonforasubjecttohavemultipleobserversthanviceversa.If

    itisaproblemforyou,thereisanothermorecomplexsolutionyoucanusethatstilldoesn'trequiredynamicallocation.It's

    toolongtocramintothischapter,butI'llsketchitoutandletyoufillintheblanks...

    Likebefore,eachsubjectwillhavealinkedlistofobservers.However,thoselistnodeswon'tbetheobserverobjects

    themselves.Instead,they'llbeseparatelittlelistnodeobjectsthatcontainapointertotheobserverandthenapointerto

    thenextnodeinthelist.

    ApooloflistnodesSincemultiplenodescanallpointtothesameobserver,thatmeansanobservercanbeinmorethanonesubject'slistat

    thesametime.We'rebacktobeingabletoobservemultiplesubjectssimultaneously.

    Linkedlistscomeintwoflavors.Intheoneyoulearnedinschool,youhaveanodeobjectthatcontainsthedata.Inour

    previouslinkedobserverexample,thatwasflippedaround:thedata(inthiscasetheobserver)containedthenode(i.e.the

    next_pointer).Thelatterstyleiscalledanintrusivelinkedlistbecauseusinganobjectinalistintrudesintothedefinition

    ofthatobjectitself.Thatmakesintrusivelistslessflexiblebut,aswe'veseen,alsomoreefficient.They'repopularinplaces

    liketheLinuxkernelwherethattrade-offmakessense.

    Thewayyouavoiddynamicallocationissimple:sinceallofthosenodesarethesamesizeandtype,youpre-allocatean

    ObjectPoolofthem.Thatgivesyouafixed-sizepileoflistnodestoworkwith,andyoucanuseandreusethemasyou

    needwithouthavingtohitanactualmemoryallocator.

    Ithinkwe'vebanishedthethreeboogiemenusedtoscarepeopleoffthispattern.Aswe'veseen,it'ssimple,fast,andcan

    bemadetoplaynicewithmemorymanagement.Butdoesthatmeanyoushoulduseobserversallthetime?

    Now,that'sadifferentquestion.Likealldesignpatterns,theObserverpatternisn'tacure-all.Evenwhenimplemented

    correctlyandefficiently,itmaynotbetherightsolution.Thereasondesignpatternsgetabadrapisbecausepeopleapply

    goodpatternstothewrongproblemandendupmakingthingsworse.

    Twochallengesremain,onetechnicalandoneatsomethingmorelikethemaintainabilitylevel.We'lldothetechnicalone

    firstbecausethosearealwayseasiest.

    Thesamplecodewewalkedthroughissolid,butitside-stepsanimportantissue:whathappenswhenyoudeleteasubject

    oranobserver?Ifyoucarelesslycalldeleteonsomeobserver,asubjectmaystillhaveapointertoit.That'snowa

    danglingpointerintodeallocatedmemory.Whenthatsubjecttriestosendanotification,well...let'sjustsayyou'renotgoing

    tohaveagoodtime.

    Nottopointfingers,butI'llnotethatDesignPatternsdoesn'tmentionthisissueatall.

    Destroyingthesubjectiseasiersinceinmostimplementations,theobserverdoesn'thaveanyreferencestoit.Buteven

    then,sendingthesubject'sbitstothememorymanager'srecyclebinmaycausesomeproblems.Thoseobserversmaystill

    beexpectingtoreceivenotificationsinthefuture,andtheydon'tknowthatthatwillneverhappennow.Theyaren't

    observersatall,really,theyjustthinktheyare.

    Youcandealwiththisinacoupleofdifferentways.ThesimplestistodowhatIdidandjustpuntonit.It'sanobserver'sjob

    tounregisteritselffromanysubjectswhenitgetsdeleted.Moreoftenthannot,theobserverdoesknowwhichsubjectsit's

    observing,soit'susuallyjustamatterofaddingaremoveObservercalltoitsdestructor.

    Asisoftenthecase,thehardpartisn'tdoingit,it'srememberingtodoit.

    Ifyoudon'twanttoleaveobservershangingwhenasubjectgivesuptheghost,that'seasytofix.Justhavethesubject

    sendonefinaldyingbreathnotificationrightbeforeitgetsdestroyed.Thatway,anyobservercanreceivethatandtake

    whateveractionitthinksisappropriate.

    Mourn,sendflowers,composeelegy,etc.

    People--eventhoseofuswho'vespentenoughtimeinthecompanyofmachinestohavesomeoftheirprecisenaturerub

    offonus--arereliablyterribleatbeingreliable.That'swhyweinventedcomputers:theydon'tmakethemistakesweso

    oftendo.

    Asaferansweristomakeobserversautomaticallyunregisterthemselvesfromeverysubjectwhentheygetdestroyed.If

    youimplementthelogicforthatonceinyourbaseobserverclass,everyoneusingitdoesn'thavetoremembertodoit

    themselves.Thisdoesaddsomecomplexity,though.Itmeanseachobserverwillneedalistofthesubjectsit'sobserving.

    Youendupwithpointersgoinginbothdirections.

    RemainingProblems

    DestroyingsubjectsandobserversAllyoucoolkidswithyourhipmodernlanguageswithgarbagecollectorsarefeelingprettysmugrightnow.Thinkyoudon't

    havetoworryaboutthisbecauseyouneverexplicitlydeleteanything?Thinkagain!

    Imaginethis:you'vegotsomeUIscreenthatshowsabunchofstatsabouttheplayer'scharacterliketheirhealthandstuff.

    Whentheplayerbringsupthescreen,youinstantiateanewobjectforit.Whentheycloseit,youjustforgetaboutthe

    objectandlettheGCcleanitup.

    Everytimethecharactertakesapunchtotheface(orelsewhere,Isuppose),itsendsanotification.TheUIscreen

    observesthatandupdatesthelittlehealthbar.Great.Nowwhathappenswhentheplayerdismissesthescreen,butyou

    don'tunregistertheobserver?

    TheUIisn'tvisibleanymore,butitwon'tgetgarbagecollectedsincethecharacter'sobserverliststillhasareferencetoit.

    Everytimethescreenisloaded,weaddanewinstanceofittothatincreasinglylonglist.

    Theentiretimetheplayerisplayingthegame,runningaround,andgettinginfights,thecharacterissendingnotifications

    thatgetreceivedbyallofthosescreens.Theyaren'tonscreen,buttheyreceivenotificationsandwasteCPUcycles

    updatinginvisibleUIelements.Iftheydootherthingslikeplaysounds,you'llgetnoticeablywrongbehavior.

    Thisissuchacommonissueinnotificationsystemsthatithasaname:thelapsedlistenerproblem.Sincesubjectsretain

    referencestotheirlisteners,youcanendupwithzombieUIobjectslingeringinmemory.Thelessonhereistobe

    disciplinedaboutunregistration.

    Anevensurersignofitssignificance:ithasaWikipediaarticle.

    Theother,deeperissuewiththeObserverpatternisadirectconsequenceofitsintendedpurpose.Weuseitbecauseit

    helpsusloosenthecouplingbetweentwopiecesofcode.Itletsasubjectindirectlycommunicatewithsomeobserver

    withoutbeingstaticallyboundtoit.

    Thisisarealwinwhenyou'retryingtoreasonaboutthesubject'sbehavior,andanyhangers-onwouldbeanannoying

    distraction.Ifyou'repokingatthephysicsengine,youreallydon'twantyoureditor--oryourmind--clutteredupwitha

    bunchofstuffaboutachievements.

    Ontheotherhand,ifyourprogramisn'tworkingandthebugspanssomechainofobservers,reasoningaboutthat

    communicationflowismuchmoredifficult.Withanexplicitcoupling,it'saseasyaslookingupthemethodbeingcalled.

    Thisischild'splayforyouraverageIDEsincethecouplingisstatic.

    Butifthatcouplinghappensthroughanobserverlist,theonlywaytotellwhowillgetnotifiedisbyseeingwhichobservers

    happentobeinthatlistatruntime.Insteadofbeingabletostaticallyreasonaboutthecommunicationstructureofthe

    program,youhavetoreasonaboutitsimperative,dynamicbehavior.

    Myguidelineforhowtocopewiththisisprettysimple.Ifyouoftenneedtothinkaboutbothsidesofsomecommunicationin

    ordertounderstandapartoftheprogram,don'tusetheObserverpatterntoexpressthatlinkage.Prefersomethingmore

    explicit.

    Whenyou'rehackingonsomebigprogram,youtendtohavelumpsofitthatyouworkonalltogether.Wehavelotsof

    terminologyforthislikeseparationofconcernsandcoherenceandcohesionandmodularity,butitboilsdowntothis

    stuffgoestogetheranddoesn'tgowiththisotherstuff.

    Theobserverpatternisagreatwaytoletthosemostlyunrelatedlumpstalktoeachotherwithoutthemmergingintoone

    biglump.It'slessusefulwithinasinglelumpofcodededicatedtoonefeatureoraspect.

    That'swhyitfitsourexamplewell:achievementsandphysicsarealmostentirelyunrelateddomains,likelyimplementedby

    differentpeople.Wewantthebareminimumofcommunicationbetweenthemsothatworkingoneitheronedoesn'trequire

    muchknowledgeoftheother.

    Don'tworry,I'vegotaGC

    What'sgoingon?DesignPatternscameoutinthe90s.Backthen,object-orientedprogrammingwasthehotparadigm.Everyprogrammeron

    EarthwantedtoLearnOOPin30Days,andmiddlemanagerspaidthembasedonthenumberofclassestheycreated.

    Engineersjudgedtheirmettlebythedepthoftheirinheritancehierarchies.

    Thatsameyear,AceofBasehadnotonebutthreehitsingles,sothatmaytellyousomethingaboutourtasteand

    discernmentbackthen.

    TheObserverpatterngotpopularduringthatzeitgeist,soit'snosurprisethatit'sclass-heavy.Butmainstreamcodersnow

    aremorecomfortablewithfunctionalprogramming.Havingtoimplementanentireinterfacejusttoreceiveanotification

    doesn'tfittoday'saesthetic.

    Itfeelsheavyweightandrigid.Itisheavyweightandrigid.Forexample,youcan'thaveasingleclassthatusesdifferent

    notificationmethodsfordifferentsubjects.

    Thisiswhythesubjectusuallypassesitselftotheobserver.SinceanobserveronlyhasasingleonNotifymethod,ifit's

    observingmultiplesubjects,itneedstobeabletotellw ......

您现在查看是摘要介绍页, 详见PDF附件(7335KB,231页)