随着游戏的日益盛行,×××游戏一直以来很受玩家喜爱,比如:极品飞车,侠盗飞车等。很多人可能会想如果我设计一款×××游戏该如何做?首先遇到的问题是如何架构×××游戏框架?带着疑问我们开始今天的探讨。我在这里结合自己多年的游戏经验以及利用业余时间制做的×××游戏,跟大家分享一下架构设计×××游戏逐步揭开它神秘的面纱。并不是说做×××游戏一定要按照本文的架构进行设计,只是提供一种设计架构的方法以及思维方式,达到抛砖引玉的效果。
设计模式
本文架构×××游戏框架主要运用三元组MVC(Model/View/Controller)模型/视图/控制器进行框架的模块划分。设计中主要运用State状态模式, Singleton单件模式, Faade外观模式, Builder构造者模式。Observer观察者模式。非常容易掌握而且上手比较快。可以在很短的时间内架构好自己的×××游戏框架,然后根据自己的意愿加上逻辑代码即可成为一款×××游戏。
首先介绍一下MVC,MVC包括三类对象,模型Model是应用对象,视图View是它在屏幕上的表示,控制器Controller定义玩家界面对玩家输入的相应方式。而我们用在游戏框架里面的MVC模式如下。
模型Model:游戏逻辑(具体内容根据作者自己写)。
视图View:功能引擎(类似OGRE 3D图形引擎和ODE物理库等以及UE3引擎等)。
控制器Controller:游戏框架(也就是本文重点介绍的部分)。
本框架使用MVC模式时是把自己定位为一个Controller控制器层,负责协调并调度功能引擎和游戏之间的结构和流程,同时丰富了一下Model模型层,在框架中加入了游戏逻辑。在架构游戏框架之前首先介绍一下本文中要用到的设计模式:
State状态模式解决问题:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。本框架用State模式是为了在程序运行时不同状态之间的动态切换,消除了if-else和case-switch的复杂性。同时更易于扩展。
Singleton单件模式解决的问题:保证一个类仅有一个实例,并提供一个访问它的全局访问点。该模式主要用在一些全局的管理器中。
Faade外观模式解决的问题:为一系列系统的相关接口提供一个一致的界面,让玩家通过一个Faade模式定义的高层接口统一处理,隐藏相关细节。这个接口使一个子系统可以更加容易使用。该模式是用来提供统一的接口并封装调用流程。在类中提供包括插件系统、计时器系统、消息系统,以及游戏工厂等工具的调用过程。玩家只需要调用配置,载入,释放3个简单的操作来控制整体操作。
Builder构造者模式解决的问题:将一个复杂对象的创建与其表示相互分离,使得同样的创建过程可以使用不同的表示。
在载入游戏逻辑配置信息时,该框架提供了多种载入这些信息的途径,如从XML文件,INI文件中载入,通过程序参数载入并在代码中通过相应字符串载入。设计者通过采用Builder模式来处理这些问题,并为以后的扩展带来好处。
Observer观察者模式解决的问题:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时所有依赖于它的对象都得到通知并被自动更新。
本文就以作者开发的×××Demo为例说说如何架构×××游戏框架以及丰富游戏逻辑。在这里只是把Demo中关键的地方用设计模式说明一下。因为设计模式的运用都是相互结合的,不是孤立的运用一种设计模式进行设计,它们是交叉进行的,本文在这里就不进行细化了。
框架的搭建
首先我们要考虑的事情,是如何架构游戏框架:
第一、要明确在游戏中如何设计实体
第二、如何确定设计问题所在
第三、如何应用模式来搞定你的设计说明书
设计实体
需要标识游戏中的所有对象。因此你要想象一下玩家将如何使用这个系统,我们假设玩家将用以下序列来操作游戏:
第一、打开游戏界面
第二、选择车辆
第三、选择天气
第四、选择场景
第五、开始游戏
为了使系统扩展起来方便需要至少如下对象:
×××( Car),玩家驾驶的×××,场景(Scene),×××在场景中驾驶,天空(Sky),太阳光晕,天气的变化。游戏中需要加入一些逻辑对象,比如:游戏(Game),制定了×××比赛名次,制定在不同场景,不同天气的情况下的比赛,以及×××AI等。
下面用类图的方式说明一下:游戏框架(GameEngine)表示拥有若干比赛(Game);比赛(Game)可以在一个场景(Scene)中拥有若干辆×××Car以及不同的游戏界面(GUI),场景中不同的天气(Sky)效果。
设计问题
现在我们要根据上述对象如何组织,如何创建,如何在设计说明书中确切地阐述当他们彼此影响时的行为。
下面我们就之前讨论的对象进行设计问题分析。
界面(GUI),当我们打开游戏的时候首先看到的是游戏的界面,游戏需要不同的界面。以及各界面之间的交互转化,增加视觉效果。
场景(Scene),每一个场景中需要有天空盒,太阳光晕,以及障碍物,有不同的表示。
×××(Car),游戏中的AI×××以及玩家操作的×××,可以选择×××的车型以及颜色的变化。
天空(Sky),场景中天气的变化,可以选择不同时段的比赛环境,比如傍晚,上午,雨天等天气效果。
确定需要用到的模式
第一、解决于GUI界面之间切换的设计问题
首先看一下运行游戏时进入游戏界面以及不同界面之间的切换,是在运行时动态进行的。使人很容易想到状态模式(State Pattern)。也就是我们经常用到的有限状态机。运用State状态模式,可以动态的处理不同UI之间的切换,从一种状态变换到另一种状态,同时可以进行不同场景的切换。UML类图设计如下所示:
State类是作为抽象类使用的,MenuState,ChooseCarState,ChooseTrackState,GameState继承于State模式。这样它们之间可以进行相互的状态转化。GUISystem界面管理系统类,负责GUI界面的加载,事件和管理操作。StateSystem是定义玩家感兴趣的接口,玩家可以通过该类实现游戏的不同状态转换。在这里需要把以上各个类设计成Singleton单件模式作为全局唯一的管理器。
运行时的界面如下图所示:
为了使游戏能方便进行扩展,该Demo可以支持多种文本格式。比如TXT格式,INI格式,XML格式,作者可以根据自己的需要进行扩展。本文选择运用Builder构造者模式解决这个问题,游戏读取文本配置文件的UML类图如下所示:
该游戏框架可以读入INI配置文件和XML配置文件,通过TexReader类可以对不同的配置文件进行操作以满足程序需要。INIBuilder类和XMLBuilde类都继承于TexBuilder类。INI配置文件主要是用来设置×××文件名字和×××的一些参数,同时也可以用来设置场景文件,玩家可以自行修改无需重新编译。而XML是通过建模软件导出的一种模型格式,包括×××模型和场景模型。
通过以上设计整个×××游戏框架就出来了,下面开始继续丰富×××游戏。
由于系统中需要用到很多子系统,比如日志管理器,计时器(调度器)管理器、OIS输入管理器,音频管理器及插件管理器。对于它们的使用我们在这里进行了封装使玩家操作起来会非常的方便,换句话说就需要对玩家提供一个统一的接口,减少了操作的繁杂性。这自然使我们想起用外观Faade模式解决此类问题。UML类图设计如下所示:
通过以上的设计实现的游戏场景效果图如下所示:
接下来为了丰富游戏逻辑我们在游戏中加入了AI算法,这样玩家可以与电脑中自动分配的×××进行比赛,最后列出各个×××的排名及时间。这就带来一个疑问AI车是如何知道玩家车子的位置,玩家的位置变化会引起AI×××怎样的变化。换句话说,当一个对象的状态发生改变时所有依赖于它的对象都得到通知并被自动更新。这让我们自然想到的是Observer观察者模式,UML类图如下所示:
类图说明一下,当调用Car的 SetCarPosition函数设置一个新的位置时,它马上调用类 Car 中定义的 Notify 函数。Notify 函数迭代观察者序列,并调用它们的 Update 函数。当 Update函数被调用,AI观察者就可以通过调用 PlayerCar 类的 GetCarPosition 函数来得到玩家的新的状态位置。在这里我把AI类作为观察者使用。实现后的效果如下图所示:
至此我们基本的设计已完成,通过逐步以分解的方式说明设计×××游戏时用到的主要设计模式,从而可以实现我们的×××游戏框架。
我在这里进一步完整了一下×××Demo实现的主要功能:可以在同一台电脑上进行分屏显示不同玩家的状态,模拟了急速行驶的×××刹车或者漂移时的粒子效果和出现车痕效果,以及在不同环境下×××的不同状态比如在夜色里面可以显示车灯的效果,天空上飘动的云层可以根据时间的变化进行颜色变化以及随风飘动,时而组合时而分开。在场景中也加入了太阳光晕效果,实现了画中画效果,可以通过不同的视角观察×××。为了增加趣味性在游戏中加入了AI算法,这样玩家操作×××时不会由于不熟练而时自己在后面很寂寞,AI×××可以根据玩家×××的速度位置适当的调整自身速度,使其始终围绕在×××周围,当然在将到达终点前的时候会全力加速,最后显示×××的排名次序。本文中做的×××游戏不是产品,不可能做到功能面面俱到。不过个人认为只要架构设计好了实现其他功能只是时间问题。
以下是运行游戏时截取的几幅图像: