B Color Smilies

全部评论22

  • 呆呆星人
    呆呆星人 2011-5-4 21:04:23
    游戏设计理论5:游戏引擎

      1、游戏引擎

      早想写一点游戏设计的文章与大家交流,一是经验的问题,二是公司正在紧张的游戏制作期,实 在抽不出多少时间,一直没有动手,今天忽然头脑发热,写了一段,以后准备陆续写一些游戏创意,策 划,制作,流程管理,和制作工具等方面的文章供大家参考。
       
      我们的游戏设计经验主要是冒险游戏和角色扮演游戏,但我们设计游戏工具时尽量适应其余题 材,不过是否可行未经检验。 写这篇文章的意图一是想为游戏界做点事,抛砖引玉吧,另外是公司正在寻找志同道合的战友, 我写一点文章交一交朋友,许多东西仅仅是我们的经验,不一定很好。参考而已吧
      
      游戏设计工具包括游戏编辑工具和游戏引擎两块;
      
      编辑工具:交互编辑游戏数据,生成游戏引擎所需的数据文件,包括以下几个功能块: 图像编辑,场景编辑,物品编辑,动画编辑,人物编辑,事件编辑等,具体介绍在以后的 文章介绍。
      
      先从游戏引擎说起。
      语言:VC5。0
      操作系统:WIN95
      图像引擎IRECT X 5。0
      支持游戏风格:各种类型和视角以及多层次的冒险游戏和角色扮演游戏


      整个游戏引擎包括以下功能块:

      资源管理:图像库CIMGLIB,声音库CSOUNDLIB,通过编辑工具形成的资源文件来定义,每种资源 包括定义管理和一些操作接口。图像库图"像包括多种格式(BMP,GIF。AVI,FLC等)以及他形成的内存 格式定义,子图定义(每一张图片包括许多小图,需定义它的小图位置,当然可以自动生成),游戏需 要的特殊定义,比如行走,身体性质,中心定位点,触发区,可以根据自己的要求扩充各种性质定义。 图像最好允许图像组合定义。声音库包括WAV和 MID的定义和再现。 资源由IMGLIB。DAT和 SOUNGIMG。DAT定义,调试版本中最好不要将资源打包,而是指向正常的 文件名,发行版本中再打包,这样修改和不同工作人员协调容易一些,否则最好有一个自己的资源 管理器。我们在调试版本中数据文件采用文本描述格式。许多数据可以手工编写而不需要专门的 编辑工具。
      
      资源管理对象还包括内存管理,比如设置时间阀释放长期不用的资源。

      声音管理:CSOUND,包括Creat(),Sound(char *fileName。。。),SetPos(),等,DirectSound有一 些函数,我们要做的是封装简化,减少对外的接口。

      窗口系统:接管标准窗口系统,一个完善的游戏引擎最好有一个自己的界面系统,至于简单还是 复杂根据自己情况具体分析,一个具备基本功能的界面系统1000行程序就可以对付下来,需要窗口 系统的原因是一般的图像引擎不支持标准窗口,二是可以便于移植到别的操作系统。在我们的游戏 引擎中,游戏只是窗口系统的一种特殊控件(CWINGMCTR),因此可以实现多窗口游戏等特殊要求。
  • 呆呆星人
    呆呆星人 2011-5-4 21:04:41
      CWINGMCTR是一种特殊控件,通过他来控制游戏。包括控制和显示。

      图像引擎:所使用的图像引擎的管理,我们使用的是DirectX,包括Creat(),CreatSurface(), OutToScr(),Bilt()等对外接口;他不是游戏的重点,我们尽量将图像引擎细节封装起来。

      图像管理:这是处理图像的中心,一般处理游戏显示喜欢以某种图像引擎为中心来设计,我觉得 最好设计自己的对象来封装别人的图像引擎,这样不会因某引擎而受限制,移植也比较容易,我们虽 然使用的DIRECT X ,但实际上对外的接口是一种CPICPAGE的界面,他不但包括DIRECT X 的surface, 也包括标准的位图,AVI界面,GIF动画界面,以及自定义的格式,他将各种类型的图像统一起来,对外 使用统一操作,比如DRAWTEXT,BILT,LINE等标准图形图像操作,以及扩充的ALPHA通道,透明度等操 作。为了减少内存的需求,特别是16M高彩,不要将全部图像使用DIRECT X的表面,对一些刷新不多的 图像,比如背景,可以使用标准256色位图,甚至一种GIF表面,需要时再解压,我们还使用一种单色位 图用来从背景中抠图,比如一所场景中一棵巨大的树,只要不是动画,我们可以用单色抠图的形式从 背景中扣除来作为另外一层,这样我们可以大大降低图像的内存需求。因此采用全部手绘(或3D场景), 而不是小图拼贴的场景成为可能。通过各种手段可以节约60%的内存需求。

      CPICPAGE可以通过TimeTrace()以及多线程来改写内容,比如AVI的改变。
      
      游戏控制:这部分包括显示和控制,由CGAME->CGAMEPAGE->CGMOBJ对象组成,CGAME是总控对象, 包括许多CGAMEPAGE游戏页,CGAMEPAGE是一个具有连续场景的游戏片断,有点类似于游戏的一关, CGAMEPAGE由一系列CGMOBJ组成,CGMOBJ是游戏的基本对象,由他继承出地图,物体,物品,人物,武器, 动画,触发器,多媒体按钮等特殊游戏对象,这是一个根据游戏要求不断丰富和改写的部分,对外的 接口是:SendDraw(),Draw(),TimeTrace(),AcceptMsg(),SendNetMsg(),AcceptNetMsg()等,他是通 过CWINGMCTR来调用,每种对象有许多控制参数,对象之间允许通讯,以及有自己的生长死亡发展的 控制,尽量做到对象与外界减少直接接口,通过消息实现交流。
     
      对象分为两类:景色对象和活动对象, 景色对象定义了组成场景的元素,包括背景和前景两层,可以是由整个图片组成或由RPG常用的 图片拼贴法的组成,它的特点只作为背景或前景,活动物是在他们的之间活动,一般定义后不做改变, 也不做控制,由于支持图像界面多格式,所以我们可以方便地使用AVI或 GIF动画作为背景来增加场 景的效果和真实性。景色还包括了行走性质定义,我们采用的是8x8为一单元,每个单元定义了一种 性质,比如平地,草地,障碍物等。
      
      活动对象是在背景和前景之间活动,他们之间有相互的位置关系,前后关系随着位置改变会不断 改变,因此他在所属的CGAMEPAGE中次序是动态的。对象的关系一般是由Y轴定义,由于要支持斜视角和 复杂的地形结构,光靠Y轴是不够的,我们引入了地基线的概念,通过在地基线之上还是之下来判断前 后关系,地基线的定义在图像定义中描述。活动对象有复杂的参数,可以接受外界消息,可以有自己的 各种反应。我们在引擎中使用了一种描述语言来描述他们的反应,比如对鼠标击打,人物经过等产生 参数改变,发声,对话等的回应。描述语言将作为专门的一章来介绍。 游戏显示过程是这样的, 在每次刷新期时窗口的游戏控件调用他所属的游戏页 CGAMEPAGE->SendDraw(); 游戏页将要显示的对象按前后次序送往窗口,同时注明此对象是否改变,窗口分析改写的区 域,调用每个对象的Draw()接口来刷新活动的区域,为了增加速度,并不是显示所有的区域,而是只改 写活动区域,因此我们设计了一个CCLIP的对象来管理刷新定义,它的原理是将表面分为16*16的单元, 最终显示时计算出优化后的多个剪切区域。整个窗口系统和每个游戏控件拥有自己的CCLIP对象。另 外一项增加速度的方案是游戏控件拥有一个比显示窗大两倍的显示页,这样场景滚动时只要将显示 位置改变即可 ,不用刷新所有区域。 游戏控制的过程是这样的:AcceptMsg()来接受各种消息调用脚本来改变自己参数和状态并影响 别的对象,另外每次时钟来时,调用每个对象的TimeTrace()来改变状态,比如动画改变,运动轨迹改变, 观察周围的对象做出反应等。
  • 呆呆星人
    呆呆星人 2011-5-4 21:04:59
      系统控制模块:对系统的参数做出反应。不同的题材控制不一样,比如即时战略等。只要改写这部 分以及扩充游戏对象,引擎便能支持不同的题材。至于人工智能,智能行走,只是对象的方法,比较简 单,只是需要时间。 游戏控制部分比较复杂,每一种游戏对象都有许多控制的细节,在这篇文章里不做具体描述,以后 再说吧。
      
      最后一个是网络模块:我们正在开发的是国内第一个图形化MUD游戏,网络是它的核心部分,介绍 网络的内容很多,需专门文章。我们使用的不是DirectPlay,使用的是WinSoct,考虑的是UNIX作为服务 器的需求。网络要解决的难点是安全,同步和数据压缩,这里要用到许多技巧。
      
      游戏是通过数据文件来定义:

      数据文件格式:数据文件包括资源定义文件和游戏定义,界面定义文件,文件的数据格式我们采用 的是文本形式,类似于WEB的文本,这样的好处一是版本升级容易处理,二是可以减少前期对编辑工具的 功能要求,因为我们可以用文字编辑器处理大部分数据,然后有时间再设计一个强大的工具比较现实, 当然,最终提供给用户的是处理后的数据文件。他中间有一个转换模块。

      游戏的运行流程描述(不是真正的过程,按DOS格式描述):
      CreatGameWindow();//初始化window窗口
      CreatDraw(hWnd); //初始化图像引擎
      CreatSound();//初始化声音引擎
      CreatAvi()//初始化AVI引擎
      CreatNet();//初始化网络引擎
      LoadGameData();//读取游戏定义数据,包括资源定义文件和游戏定义,界面定义
      While(1)
      {
      WINTraceMsg();//处理系统消息,比如鼠标,键盘等
      GameTimeTarce();//处理活动的游戏页的时间反应
      WinPaint();//刷新游戏显示
      OutToScr();
      }

      我们这里介绍的是单线程结构,许多部分可以用多线来加快游戏速度,但结构是一样的,就不多介绍了。
      游戏引擎的系统分析是游戏设计技术方面的成功关键,是最容易走弯路的部分,希望我们的文 章能给大家一点启发,由于今天的游戏趋向于多类型综合,设计引擎时一定不要拘泥于某一单项题材, 我们在策划这套引擎时要求他支持的游戏非常广,甚至支持多媒体设计,这套引擎只要扩充或改写参 数管理以及游戏对象,便能支持各种风格的2D类游戏。将来我们要做的是一套可以交互设计各种游戏 的开发平台,当然不是<<游戏工厂>>似的玩具。
  • 呆呆星人
    呆呆星人 2011-5-4 21:08:24
    游戏设计理论6:游戏的脚本语言

    同样有点问题,看上去内容不很重要,直接略过吧

    游戏设计理论7:RPG游戏的对话系统
      1、RPG游戏的对话系统
      我要和各位谈谈角色扮 演游戏中的最重要的一个部份~那就是交谈系统。若是各位 玩者接触的角色扮演游戏够多的话,应该会知道在角色扮演 游戏中有着单句式以及关键字两种完全不同的交谈系统,今 天我们就来看看这两个不同系统。
      
      要怎麽分辨单句式和关键字系统呢?我们可以简单的从在 游戏中玩者和游戏中的人物交谈时是如何获得讯息来看。在 一般国内的角色扮演游戏中,经常使用的就是单句式的交谈 系统,也就是那种玩者只要和角色对话,对方就会一股脑的 将所有的消息告诉你,而玩者也就可以获得所有的讯息,这 种就是属於单句式系统(如大宇的轩辕剑系列、仙剑奇侠传 等等)。就算是有的游戏用比较刁钻的方式,会要求玩者多 问几次才会出现真正需要的讯息(像精讯的乱世伏魔录), 也不过是这一种系统的延伸使用罢了。
      
      那麽关键字系统呢?我想最有名的应该就是创世纪系列, 在创世纪的前几代中,玩者需要自行输入各个关键字 ,来 询问各式各样的问题,随着玩者输入不同的关键字,就会有 不一样的答案。但是也因为这个原因,使得一 些英文程度 不好的玩者在接触那几代的游戏时相当的辛苦(反过来说, 也有一些玩者因为玩这个游戏而使得英文程度大增),也使 得许多的玩者不愿意接触创世纪。但是到了创世纪七代的时 候,由於采用了滑鼠来进行游戏,因此整个关键字系统就做了很大规模的改进了,玩者可以由画面选择要问的关键字, 因此不再会有找不到关键字来问的状况发生了。

      那麽我们来看看国内的几个采用关键字系统的角色扮演游 戏吧。在笔者的印象中,好像只有精讯的聊斋志异、大宇的 妖魔道、以及软体世界早期推出的一个侠影记而已。在这几 个游戏中,笔者觉得精讯聊斋志异的关键字系统是最成功的, 至於为什麽笔者会这麽认为,绝对不是因为笔者自己曾经叁 与过这个游戏的制作,而是从关键字系统的结构来看,这个 游戏才是真的使用到关键字系统的精髓。

      所谓的关键字系统,正如同它的名称一样,是一个以关键 字来串连游戏剧情的交谈系统,因此玩者必需要能够从某一 个游戏中人物的口中获得某个关键字後,再使用此一关键字 到另一名人物的口中发挥作用,这才能说是使用了关键字的 妙用。在聊斋志异这个游戏中,玩者要获得第一座塔~金形 塔的八字真言,就必需要先从一位书生的口中获得四字真言, 然後再到酒店里找一位酒女,用这前四个字的真言向她询问 後四个字的真言。而所 谓的关键字系统的巧妙,也就是当玩者若是没有获得前一 个关键字的时候,在後面的那个人口中是无法取得下一个关 键字的。在聊斋志异这个游戏中,若是玩者不能先由书生的 口中套出前四个字的真言,那麽在酒女的对话选单中是不会 出现那四个字供玩者选择,因此玩者就无法获得後四个字了。 也就是说,关键字系统是一个凭着玩者选项不同会有不同情 况的对话系统。

      那麽我们回过头来看看另外两个游戏的关键字系统吧,首先我们来看看大宇的妖魔道吧。在这个游戏中也有关键字系统,但是它的关键字系统所发挥的作用并不是在对 话上,而是当某些任务还没有解决时,这些关键字就会出现 在对话的选单中,让玩者可以选择它取得所需的资料,当这 一项任务或是事件解决了後,这个关键字就会从选项中消失。 这种作法并没有真正发挥「关键字」的作用,只是用它来提 供玩者多次获得相同的问题解答而已。

      接下来我们看看侠影记这个游戏,这可以说是关键字系统 使用的错误范例,因为在这个游戏中,提供玩者选择的关键 字并没有影响其他地方事件的作用,反而把原来简单的可以 在单句中表现的讯息切成一段一段的文字,让玩者需要将所 有的关键字选择完之後才可以知道全部的讯息。再加上一些 关键字所得到的讯息又都是没有用的讯息,使得这个游戏的 交谈系统完全没有表现出关键字系统应有的特色。

      现在我们就来探讨一下这两种系统在资料结构上究竟有什 麽不一样的地方。首先我们来看看单句式的资料结构 ,记 得在讨论剧情结构的那一篇文章中(第52期),笔者曾经 举过一个单线式剧情的范例吗?那种的结构就 是单句式交谈系统的标准结构。因为在单句式交谈系统中, 在玩者遭遇一名游戏中的角色时,角色会依据当时 游戏中的剧情发展情况,决定出要说那一句话,若是在剧 情没有改变的情况下,就会重复的使用这一句话。

      在这种系统中,当玩者控制的角色和游戏中的角色交谈时, 程式会依据这名角色所相对的几个旗标,判断出目前该显示 出什麽样的讯息。若是同时有几个旗标都成立的话,也只会 以最优先的旗标为准。在这种系统之下,讯息的结构相当的 单纯,程式也只要将每一句对话编上相对的代码,然後用判 断式就可以决定出要显示出那一句讯息。同样的,这种系统 的资料结构也比较单纯。

      接下来我们看看关键字系统的资料结构吧。在前面说明单 句式系统的时候,笔者曾经说过要以旗标来判断显示那一句 讯息,其实这个所谓旗标就是游戏中控制剧情目前发展到那 里的指标。同样的在关键字系统中这些旗标也存在着,只不 过除了它们之外还要再加上一组关键字的旗标,而这些关键 字的旗标就是这种交谈系统的重心 。
     
      在游戏中,当玩者从其中一位角色的口中获得一个关键字 之後,这个关键字的旗标就会打开,之後若是有遇到对这个 关键字有所了解的角色时,在交谈的选单中就会出现这个关键字。
     
      在这种系统中,交谈的时候,程式会先判断目前玩者已经 获得那些关键字,然後再看这一名交谈的对象对於那些关键 字会有反应,接下来在在选单中就会出现这些有反应的关键 字让玩者选择。在选择了某个关键字之後,相关的讯息就会 显示在画面上,同时若是有某个关键字会因为问了这一句话 之後打开,也会在讯息显示之後将 此一关键字的旗标打开。接下来我们看看下面这个例子:
  • 呆呆星人
    呆呆星人 2011-5-4 21:08:44
      在这个例子中,会有某甲以及某乙两个人,在游戏的过程 中玩者必需先从某甲的口中获得一句暗号,然後用这 个暗号向某乙询问,才可以获得所需的资料。因此在游戏中某甲的资料如下:

      某甲【无关键字】:喔,是你呀。我相信你一定是想要加入这个秘密的组织,不过若是没有组织的口令的话,是不可能加入的。

      ◎在这句讯息後关键字「口令」打开

      某甲【口令 】:你想要知道口令?好吧,那我就告诉你,我们的口令就是神出鬼没。

      ◎在这句讯息後关键字「神出鬼没」打开

      某甲【神出鬼没】:不对不对,这句口令不是对我说的,必需要找到我们组织的某乙,然後对他说出这个口令,就可以加入我们的组织了。

      因此在这个地方我们可以看到,在开始的时候玩者和某甲交谈时会先获得「口令」这个关键字,接下来这个关键字就会出现在交谈的选单中。而玩者选择口令这个关键字之後,就可以再获得进一步的资料以及「神出鬼没」这个关键字。之後再以神出鬼没这个关键字向某甲询问,就可以得知这个关键字的使用方式。接下来我们再看看某乙方面的资料:

      某乙【无关键字】:对不起,我什麽事都不知道,请你不要来打扰我好吗。

      某乙【神出鬼没】:你知道我们的口令,那麽你可以加入我们的组织了。我们的任务是要推翻目前的政府, 希望你也为这件事尽一份力。

      ◎成功的加入判乱组织了

      从这里我们可以看到,若是玩者在还没有获得神出鬼没 这个关键字的时候来找某乙交谈,那麽是无法从某乙的 口中获得任何的消息。但是当玩者从某甲的口中获得了「神出鬼没」这个关键字之後,在交谈的选单中就会出 现这个选项,选择後从某乙处就可以得到回应,也就可 以加入判乱组织了。这一种的交谈讯息结构,也就是关 键字系统真正能够发挥作用的地方。
      
      在设计关键字系统的时候有一件事要注意,那就是需要 适时的将某些没有作用的关键字关掉。就以上面的例子 来说,当玩者成功的加入了判乱组织之後,这一部份的 事件可以说是已经结束了,此时就可以将「口令」以及 「神出鬼没」这两个关键字关闭。不过若是这两个关键 字还有作用的话,就不可以这麽轻率的关闭它。
      
      说到这里也许有人会说其实这种剧情不需要用关键字系 统,就算是单纯的单句式结构就可以处理了。但是请各位 想想看,这里的剧情若是当玩者一和某甲交谈,某甲就将 所有的消息一股脑的都说了出来,那麽不是很没有 意思吗?除此之外,关键字系统在玩者被游戏卡 住的时候,还可以让玩者重复询问相同的关键字来获得讯息, 比起单句式结构那种不小心没有看到就会被卡住的情况来说, 是不是也显得比较人性化呢?
      
      前面所说的都是关键字系统的好处,但是为什麽目前的游 戏很少使用关键字系统呢?那都是因为要设计关键字系统的 话交谈的资料结构就绘变得比较复杂了。各位想想看,原本 在规划交谈的讯息资料时只要针对每个游戏 中的角色,配 合不同的剧情段落配上讯息就可以。但是在关键字系统中, 设计者必需要将关键字妥善的分配到各地的剧情对话中,才能使关键字系统真正的发挥作用, 因此在设计剧情的时候有比较多的困难。 除此之外,目前 国内的公司大多有将游戏推向国外的计划。由於中国字并不 是拼音系的文字,因此这些关键字 一但翻译成其它国家的 语言(像是韩文、英文等等)时,就会发生翻译出来的单字 变成好长一串的文字,若是 勉强的将一串文字删成适度的 长度时,又会有可能会文不对题的问题(这种情况就好像 魔法门三代中的问题被变成中文的问题那样奇怪),或是 因此要增加文字显示的区域。也因为这些原因,使得国内 的公司尽量的不去 碰这样的系统,以免自找麻烦。
      
      事实上关键字系统其实是个扩展性相当大的系统,就连游乐器的游戏中也曾经有采用过这样的系统。正因为它对於剧情的掌握比单句式的要强,以及它更结构化的 资料结构,它绝对是一个完美的交谈系统。
  • 呆呆星人
    呆呆星人 2011-5-4 21:10:32
    本帖最后由 呆呆星人 于 2011-5-4 21:10 编辑

    游戏设计理论8:完成游戏制作的关键

    又出错了……打包
  • 呆呆星人
    呆呆星人 2011-5-4 21:11:40
    游戏设计理论9:十分钟学会COM 技术

      十分钟学会 DirectX 中 的 COM 技术

      DirectX 是按照微软的COM(Component Object Model) 搭起来的。设计COM是希望它 能提供一个更安全,易升级,可移植的软件模块。COM用的面向对象的模式比一般的 C++更严格。例如,COM只能永远通过成员函数(member function)进行访问,并且不 能拥有公用数据成员(public data members)
      
      COM异常严格地对待对象(object)和接口(interface). 而且就不让你直接访问对象, COM竟然都不给对象起名字,让你老老实实通过接口来访问对象。在DirectDraw编程 中,我们讲:“访问对象”,其实都是在访问接口。叫惯了也就不改了。

      所有的COM接口都从 IUnknown 接口中派生出来。“I”打头是COM接口的标识(I for Interface). 所有的DirectDraw接口都以“I”打头,但莫明其妙的是在很多编程手 册中都把“I”略去。所以看书时要做到心中有爱。
      
      IUnknown 接口提供三个成员函数,其余所有的COM接口,都继承这三个函数。
      AddRef()
      Release()
      QueryInterface()

      AddRef() 和 Release() 支持COM的一个特色功能,名字叫“活着封装”(lifetime encapsulation). “活着封装”是一个协议,用来让对象在崩溃时(destruction)自 己负责自己的后事。

      “活着的时候”(lifetime) 每个对象内部有一个值用来跟踪记载自己用过的指针(pointer) ,或引用(reference)。当这个对象建立时,该值为1。然后随着对它的调用/被调用, 该值递增。反之递减。当本身崩溃时(destroy itself),该值为0。

      AddRef()就是用来加计数器的。你可能不用亲自调用它。你在用DirectDraw API 时, AddRef()就被自动调用了。

      Release()则是对着干。减值。你常常要亲自用到它,因为程序可能会异常退出AddRef的 作用域(scope)。如出错控制中。

      QueryInterface()用来问COM一个接口是否可用。如可用,则返回一个相应接口的指 针。

      问对象是否支持一个接口用QueryInterface, 那么怎么问?当然要知道接口的ID。 我们用GUID来表示,GUID = Globally Unique IDentifier. (全局单一证认)。GUID是 一个128bits的数。所有DirectX中接口的GUID值都可在DirectX的头文件中找到。
  • 呆呆星人
    呆呆星人 2011-5-4 21:12:00
      2、windows 游戏设计时的单任务与多任务处理
      
      单 任 务 处 理
      
      Windows 最 杰 出 的 功 能 之 一 是 能 够 同 时 运 行 多 个 程 序, 但 有 时 也 会 让 人 感 到 头 疼, 特 别 是 对 于 那 些 习 惯 于 完 全 控 制 计 算 机 甚 至 时 钟 频 率、 非 常 自 信 的 游 戏 程 序 员( 当 然, 我 们 的 确 在 乎 那 些 没 礼 貌 的、 在 退 出 时 不 恢 复 正 确 的 系 统 时 间 的 游 戏。 但 是 幸 好, 现 在 我 们 可 以 忘 掉 这 些 了)。
      ---- 在 多 任 务 环 境 下, 游 戏 程 序 员 需 要 注 意 三 个 大 的 负 效 应:

      当 游 戏 失 去 焦 点 而 进 入 后 台 后, 其 执 行 不 得 不 被 挂 起( 可 以 在 Moby Dick Windows 中 使 用“ 中 止 的” 变 量 观 察 它 是 如 何 工 作 的)。 如 果 是 一 个 实 时 游 戏, 程 序 员 当 然 希 望 它 被 悬 挂。 但 在 回 合 制 游 戏 中, 当 玩 家 去 做 其 它 事 情 时, 程 序 员 可 能 不 希 望 计 算 机 一 方 作 任 何 动 作, 但 希 望 后 台 的 人 工 智 能(AI) 运 算 依 旧 执 行。

      其 它 的 任 务 占 用 CPU 时 间, 结 果 造 成 我 们 不 能 一 直 控 制 游 戏 中 事 情 发 生 时 的 速 度。 我 们 将 在 后 面 讨 论 这 个 痛 苦 的 问 题。

      每 当 游 戏 回 到 前 台, 程 序 员 不 得 不 重 画 游 戏 窗 口。Windows 并 不 负 责 记 忆 它 所 覆 盖 或 隐 藏 的 窗 口 的 内 容; 它 所 能 做 的 最 多 是 通 知 一 个 窗 口 需 要 重 画 其 客 户 区 域。 这 在 有 关 Windows 的 文 章 中 都 有 论 述( 参 见 WM_PAINT 的 内 容), 我 们 在 这 里 就 不 讨 论 了。 事 实 上,Moby Dick Windows 并 不 恢 复 其 自 己 的 窗 口; 我 们 将 在 讲 到 DirectDraw 下 的 双 缓 冲 时 看 它 是 如 何 实 现 的。


      程 序 中 的 多 任 务

      ---- 尽 管 Moby Dick DOS 在 使 用 中 断 处 理 程 序 时 展 示 了 内 部 多 任 务( 或 者 说 多 线 程) 的 一 种 原 始 形 式, 但 是 该 程 序 仍 然 没 有 突 破 DOS 的 单 主 题 特 性, 即 在 一 个 时 间 只 做 一 件 事 情。 有 些 DOS 程 序 的 确 作 到 了 真 正 的 多 线 程, 但 是 那 需 要 非 常 巨 大 的 编 程 工 作。Windows 95 SDK 使 这 项 工 作 简 单 了 许 多, 把 线 程 放 进 每 一 个 游 戏 开 发 者 的“ 锦 囊” 之 中( 如 果 读 者 还 不 熟 悉 这 个 概 念, 那 么 简 单 说 明 一 下, 一 个 线 程 就 是 程 序 的 一 部 分, 它 执 行 时 独 立 于 其 它 的 部 分, 并 且 不 需 要 与 其 它 部 分 同 步。 线 程 不 是 由 中 断 来 驱 动 的; 它 们 只 是 在 每 一 次 Windows 给 它 们 CPU 时 间 时 继 续 其 执 行。)

      ---- 在 下 列 情 况 下, 可 能 要 考 虑 实 现 独 立 的 线 程:

      允 许 后 台 AI。 就 算 是 用 户 正 忙 于 来 回 移 动(moving pieces around)、 打 开 对 话 框 等 事 情, 计 算 机 也 能 够 考 虑 其 下 一 步。 处 理 这 类 线 程 非 常 方 便, 因 为 它 不 需 要 与 其 它 的 事 情 同 步。

      预 先 加 载 数 据。 例 如, 当 玩 家 正 努 力 向 下 一 级 奋 斗 时, 使 一 个 线 程 负 责 读 取 文 件 并 准 备 好 游 戏“ 世 界”。

      给 予 时 间 紧 迫(time-critical) 的 任 务 优 先 权。 我 们 将 在 后 面 会 到 这 个 主 题。

      游 戏 循 环

      ---- 游 戏 循 环 的 概 念 在 各 编 程 环 境 下 都 比 较 相 似。 第 一 步, 需 要 获 取 输 入: 可 以 是 轮 询 它, 等 待 它, 或 者 在 它“ 运 行” 时 通 过 中 断 或 一 个 消 息 队 列 拦 截 它。 第 二 步, 处 理 该 输 入, 并 且 把 它 变 成 一 个 在 游 戏 中 有 实 际 意 义 的 动 作, 如: 使 飞 机 倾 斜 飞 行 或 小 卒 向 前 走 一 步。 然 后, 把 结 果 显 示 出 来。 当 然, 在 这 个 主 题 中 也 要 求 精 雕 细 刻 和“ 变 奏 曲”, 包 括 计 算 AI 的 移 动、 把 控 制 权 从 一 个 玩 家 移 交 给 另 一 个 玩 家、 检 查 胜 负 等 等。

      ---- 然 而, 在 Windows 和 DOS 下 实 现 循 环 的 机 制 迥 然 不 同。 每 个 Windows 程 序 都 建 立 于 一 个 消 息 循 环 之 上。 尽 管 一 个 游 戏 循 环 可 以 建 立 在 消 息 循 环 之 上, 但 是 这 两 者 仍 有 本 质 的 差 别。
  • 呆呆星人
    呆呆星人 2011-5-4 21:12:21
      Moby Dick DOS 的 循 环

      ---- Moby Dick DOS 演 示 了 一 种 简 单 的 游 戏 循 环, 在 这 里 我 们 所 做 的 工 作 是: 检 查 是 否 有 什 么 东 西 要 移 动, 移 动 它, 显 示 结 果。

      while (!gamedone)

      //调用时间程序 -如果时间未到,则没有任何响应。

      AhabMoved = Move_Ahab();

      //仅当 Ahab没有移动时移动 Moby Dick。

      //否则它们可能擦肩而过却不能拦截。

      if (!AhabMoved) Move_Moby();

      //如果有任何一个移动,更新屏幕,

      //并检查是否有胜利和失败。

      if ((MobyX != OldMobyX) || (MobyY != OldMobyY)

      || (AhabMoved))
      {
      UpdateScreen();

      if ((MobyX == AhabX) && (MobyY == AhabY)


      && (painted[MobyX][MobyY]))
      {
      gamedone = 1;

      cprintf("\a");

      cprintf("You win!");

      }


      if (TimesUp <= 0) { cprintf("\a"); cprintf("Time's up!"); gamedone="1;" } if (raw_key="=" MAKE_ESC) { gamedone="1;" progdone="1;" } } //结束更新 } //结束游戏内部循环 (while !gamedone) Moby Dick Windows的循环 从表面看来,好像没有多大的差别: do { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message="=" WM_QUIT) break; //唯一的退出循环的出口。 TranslateMessage(&msg); DispatchMessage(&msg); } else { if ((MobyX !="OldMobyX)" || (MobyY !="OldMobyY)" || (AhabMoved)) { UpdateScreen(); if ((AhabX="=" MobyX) && (AhabY="=" MobyY) && (painted[AhabY][AhabX])) { Control="MessageBoxEx(hwnd," "You caught Moby! Play again?", "Call Me Ishmael", MB_ICONQUESTION | MB_YESNO, 0); if (Control="=" IDYES) InitializeGame(); else break; } } //如果有人移动了 } //如果屏幕已更新 } //结束循环 while (TRUE);

      ---- 前 面 已 经 提 到 过, 这 里 没 有 检 查 是 否 运 行 超 时, 请 忽 略 它, 笔 者 在 Windows 版 中 未 实 现 它 是 为 了 避 免 令 人 烦 恼 的 中 断。 中 断 并 退 出 无 限 循 环 的 机 制 有 一 点 儿 而 且 并 不 重 要。 我 们 把 精 力 集 中 在 消 息 循 环 本 身, 所 以 把 其 它 的 无 关 代 码 都 删 掉, 只 留 下 最 基 本 的 部 分:

      do

      {

      if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

      {

      if (msg.message == WM_QUIT) break;

      TranslateMessage(&msg);

      DispatchMessage(&msg);

      }

      else DoSomething();

      }

      while (TRUE)

      ---- 这 是 一 个 非 常 典 型 的 消 息 循 环。 唯 一 有 点 特 殊 的 地 方 就 是 它 使 用 的 是 PeekMessage 而 不 是 GetMessage。

      GetMessage 与 PeekMessage 的 比 较

      ---- 为 什 么 要 用 PeekMessage 呢 ? 原 因 很 简 单,GetMessage 等 待 一 个 消 息( 就 像 _getch), 而 PeekMessage 不 是 这 样( 就 像_kbhit)。 请 考 虑 下 面 的 循 环:

      while (GetMessage(&msg, NULL, 0, 0))

      {
      // 我 们 并 不 进 入 括 号 内 部, 直 到 有 一 个 消 息

      TranslateMessage(&msg);

      DispatchMessage(&msg);

      DoSomething()

      }

      // 当 GetMessage 返 回 NULL 时, 退 出 该 程 序

      return msg.wParam;

      ---- 在 这 里,DoSomething 不 会 完 成, 除 非 一 个 消 息-- 或 许 多 消 息-- 被 放 入 队 列 中 并 被 处 理。 如 果 DoSomething 恰 好 产 生 一 个 消 息, 例 如, 如 果 它 更 新 了 屏 幕 并 且 因 此 而 产 生 了 一 个 WM_PAINT 消 息, 那 么 好 了, 水 泵 注 水 后 将 开 始 启 动 了。 要 使 DoSomething 可 靠 地 完 成 其 工 作, 这 并 不 是 一 个 好 方 法, 它 使 代 码 有 点 混 淆, 但 它 工 作 的 还 不 错。
  • 呆呆星人
    呆呆星人 2011-5-4 21:12:40
      ---- 相 比 之 下,PeekMessage 则 无 论 是 否 有 消 息 在 等 待, 只 要 检 查 一 下 消 息 队 列 就 完 成 其 操 作(yields the floor)。 在 我 们 的 例 子 中, 我 们 实 际 上 是 使 用 PeekMessage 来 处 理 消 息 的( 通 过 分 发 它 所 找 到 的 每 一 个 消 息 并 使 用 PM_REMOVE 参 数 从 队 列 中 清 除 它)。 同 下 面 同 样 有 效 的 代 码 相 比, 它 要 更 加 直 接:

      if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))

      {

      if (!GetMessage(&msg, NULL, 0, 0)) break;

      TranslateMessage(&msg);

      DispatchMessage(&msg);

      }

      else DoSomething();

      ---- 在 这 里 有 非 常 重 要 的 一 点 要 说 明, 我 们 的 伪 代 码 DoSomething 是 独 立 于 消 息 的; 无 论 队 列 中 送 出 的 什 么 消 息, 甚 至 无 论 有 没 有 消 息 在 那 儿, 它 都 将 执 行。 在 Moby Dick 中, 我 们 将 屏 幕 更 新 和 胜 利 条 件 的 检 查 放 在 这 里, 因 为 在 这 里 检 查 一 个 或 多 个 消 息 被 响 应 后 是 否 需 要 更 新 屏 幕 或 是 否 达 到 胜 利 条 件 很 方 便。

      ---- 那 么, 消 息 循 环 就 是 游 戏 循 环 吗 ? 从 抽 象 的 角 度, 是 的, 因 为 它 是 大 的 齿 轮, 带 动 那 些 小 的 齿 轮。 但 是, 尽 管 把 一 些 函 数 调 用 放 在 此 处 可 能 比 较 方 便,Windows 编 程 规 则 却 要 求 任 何 响 应 一 个 消 息 的 动 作 都 应 该 放 在 消 息 响 应 程 序 中( 就 是 说, 放 在 窗 口 过 程 中)。 在 一 个 实 时 游 戏 中, 绝 大 多 数 的 动 作 发 生 在 一 个 或 多 个 WM_TIMER 消 息 响 应 程 序 中。 回 合 制 游 戏 则 常 常 在 输 入 消 息 的 响 应 函 数 中 做 大 量 的 工 作。

      ---- 事 实 上, 程 序 员 经 常 会 发 现 在 主 消 息 循 环 内, 除 了 标 准 的 翻 译 和 分 发 消 息 任 务 之 外 什 么 事 情 也 没 做。 如 果 这 样, 就 可 以 回 过 头 来 使 用 GetMessage, 因 为 除 了 响 应 消 息 之 外, 什 么 事 也 不 需 要 发 生。

      ---- 总 结 一 下, 实 现 循 环 有 两 个 关 键 点:

      Windows 消 息 循 环 与 游 戏 循 环 不 相 同。 游 戏 循 环 依 然 存 在( 至 少 在 概 念 上 如 此), 但 是 同 DOS 下 的 情 况 相 比, 它 的 部 件 在 代 码 中 更 加 分 散。

      如 果 想 要 在 消 息 循 环 内 执 行 任 何 独 立 于 时 钟 消 息 和 输 入 消 息 的 代 码, 请 使 用 PeekMessage 而 不 是 GetMessage。

你可能喜欢

游戏设计理论(ZT) 
联系
我们
快速回复 返回顶部 返回列表