3.5 方块游戏的设计
“我们再来举些例子,比如就拿手机里的俄罗斯方块游戏为例。要是让你开发这个小游戏,你如何考虑?”大鸟问道。
“我想想,首先它方块下落动画的原理是画四个小方块,擦掉,然后再在下一行画四个方块。不断地绘出和擦掉就形成了动画,所以应该要有画和擦方块的代码。然后左右键实现左移和右移,下键实现加速,上键实现旋转,这其实都应该是函数,当然左右移动需要考虑碰撞的问题,下移需要考虑堆积和消层的问题。”
“OK,你也说了不少了。如果就用WinForm的方式开发,你打算怎么开发呢?”
“那当然是先建立一个窗体Form,然后加一个用于游戏框的控件,比如Panel或者PictureBox,一个按钮Button来控制‘开始’,最后再放一个Timer控件用于分时动画的编程。写代码当然就是编写Timer_Tick事件来绘出和擦除方块,并做出堆积和消层的判断。再编写控件的键盘事件,按了左箭头则左移,右箭头则右移等等。对了,还需要用到些GDI+技术的方法来画方块和擦方块。”
“你能不能就这些代码划分一下类呢?”
“分类?这里好像关键在于各种事件代码如何写吧,这里有什么类可言呢?”
“看来你的面向过程开发已经根深蒂固了。你把所有的代码都写在了Form1.cs这个类里,你觉得这合理吗?”
“可能不合理,但我实在没想出怎么分离它。”
“打个比方,如果现在要你写的是手机版的俄罗斯方块程序,即Pocket PC或者Windows CE上运行的程序,它们可以安装.NET框架的精简版,运行C#语言编写的应用程序,但PC上的普通WinForm界面的程序不能使用。那你现在这个代码有什么可以复用的吗?”
“你都已经说了,不能使用,我当然就没法使用了。Copy过去,再针对代码做些改进吧。”
“但这当中,有些东西是始终没变的。”
“你是说,下落、旋转、碰撞判断、移动、堆积这些游戏逻辑吧?”
“说得没错,这些都是和游戏有关的逻辑,和界面如何表示没有什么关系,为什么要写在一个类里面呢?如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏[ASD]。事实上,你完全可以找出哪些是界面,哪些是游戏逻辑,然后进行分离。”
“但我还是不明白,如何分离开。”
“你仔细想想看,方块的可移动的游戏区域,可以设计为一个二维整型数组用来表示坐标,宽10,高20,比如‘int[,] arraySquare=new int[10,20];’,那么整个方块的移动其实就是数组的下标变化,比如原方块在arraySquare [3,5]上,则下移时变成arraySquare [3,6],如果下移同时还按了左键,则是arraySquare[2,6]。每个数组的值就是是否存在方块的标志,存在为1,不存在时缺省为0。这下你该明白,所谓的碰撞判断,其实就是什么?”
“我知道了,是否能左移,就是判断arraySquare [x,y]中的x–1是否小于0,否则就撞墙了。或者arraySquare [x–1,y]是否等于1,否则就说明左侧有堆积的方块。所谓堆积,不过是判断arraySquare [x,y+1]是否等于1的过程,如果是,则将自己arraySquare [x,y]的值改1。那么消层,其实就是arraySquare [x,y]中循环x由0到9,判断arraySquare [x,y]是否都等于1,是则此行数据清零,并将其上方的数组值遍历下移一位。”
“那你就应该明白了,所谓游戏逻辑,不过就是数组的每一项值变化的问题,下落、旋转、碰撞判断、移动、堆积这些都是在做数组具体项的值的变化。而界面表示逻辑,不过是根据数组的数据进行绘出和擦除,或者根据键盘命令调用数组的相应方法进行改变。因此,至少应该考虑将此程序分为两个类,一个是游戏逻辑的类,一个是WinForm窗体的类。当有一天要改变界面,或者换界面时,不过是窗体类的变化,和游戏逻辑无关,以此达到复用的目的。”
“这个听起来容易,真正要做起来还是有难度的哦!”
“当然,软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离[ASD]。其实要去判断是否应该分离出类来,也不难,那就是如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责[ASD],就应该考虑类的职责分离。”
“的确是这样,界面的变化是和游戏本身没有关系的,界面是容易变化的,而游戏逻辑是不太容易变化的,将它们分离开有利于界面的改动。”