文章来源:J2ME开发网
前些时日希望找一份俄罗斯方块的游戏学习一下, 发现网上大部分链接都是陈万飞 兄的大作, 你可以在http://blog.csdn.net/legendinfo/archive/2004/11/29/197720.aspx 访问获取具体信息. 唯一遗憾的是程序写成的时候已经较长, 而作为一个开源项目的延展, 似乎并没有后续版本的跟上. 学习之余, 本人也做了一定的修改, 目前的版本主要是将游戏从MIDP1.0 改为用 MIDP2.0 实现, 使用GameCanvas替代了Canvas, 舍弃了经典的paint() / repaint(), 而重新定义了画布方法, 以调用双缓冲特性的flushGraphics(). 另外, 修正了诸如 “竖条方块在左墙壁转向时可能出现的数组外溢”等小bug. 您可以在 http://www.hyweb.net/BrowseFiles.aspx?Folder=Public 下的我的项目(My Project / RussiaGame)中下载到执行程序以及源代码.
陈兄文中关于数据组成结构论述的已经很清楚了,我这里就具体的程序结构做一个简单的描述, 希望能够为有需要的朋友提供帮助. 同时希望大家就这个开源项目提出自己的真知灼见!
首先, 列出本程序涉及的4个文件外部可见的类结构:
1. DiamondMIDlet:
虽然结构比较简单,程序依然采用了单态结构,DiamondMIDlet除了初始化主类DiamondCanvas, 将当前表示层display传递过去, 没有再担负其它工作.
2. DiamondCanvas:
DiamondCanvas继承自GameCanvas,是实现游戏重画,封装逻辑运算的关键类.除了DiamondMIDlet,表示游戏地图的DiamondMap和表示小砖块的DiamondBlock也都在DiamondCanvas中注册, 由其统一调度,产生的重绘效果.
另一方面, DiamondCanvas中还包含了对游戏开始, 暂停, 结束的各种控制. 同时还注册了命令监听器, 用于响应玩家的操作控制.
下面简略的分析一下DiamondCanvas程序结构.
由上图可见, DiamondCanvas包含了清理屏幕Clear(), 初始化游戏画布, 游戏状态以及砖块边长大小的初始化方法init(), 在构造函数中, 以上两个首先被执行, 同时注册两个CommandAction事件, 用于响应用户的击键事件, 从而判断当前游戏状态. 构造函数的最后, 将整个程序作为一个新线程启动, 在恒真的情况下, 每个50毫秒就检测一次用户按键事件keyPressedState(), 再重画当前画布paintCanvas(Graphics graphics).
KeyPressedState()用getKeyStates()主动捕获按键状态, 是为了更好的响应用户按”下移”键的程序可玩度; 另一方面, 为了避免下降的砖块变化过于灵活, 对其它键状态的响应, 则继承了Canvas原有的keyPressed()方法, 单击一次, 执行一次.
PaintCanvas(Graphics graphics)中, 程序根据不同的游戏状态重画画布, 一开始所有的重画都执行在传入参数graphics上面,在完成所有操作后,在利用双缓冲重画方法flushGraphics()一次性画到屏幕上, 以避免色块bug.
3. DiamondMap
DiamondMap是游戏地图类, 包含了地图逻辑二维数组, 游戏分数统计等内部成员变量. 该类直接控制着地图逻辑变化和图层变化的维护和更新, 检查游戏图层能否消去, 当然, 还有游戏分数的控制.
下面简略的分析一下DiamondMap程序结构.
DiamondMap的构造函数完成初始化地图逻辑数组mapdata和当前行是否为空的任务.
初始化方法init()将清空逻辑地图, 在设置逻辑上两侧墙和地面数据。GetData与setData提供存取地图指定点逻辑值的方法。paint(Graphics g)画出两侧墙和地面砖块图像,check(Graphics g, int inputRow)检查在指定行是否能够消行,如果可以, 先改变当前行的地图数据, 再重画砖块。
repaintMap(Graphics g)在有消去行为后调用,从容器底开始,冒泡重画容器地图。使用drawBlock(Graphics g, int y)方法,以行为单位,根据每格mapdata[]数据中的信息画出他们的图形。对于需要删除的行,只是使用deleteRow(Graphics g, int y)简单的把该行置黑。
当然,最后还需要提供在画布上重画得分的方法paintScore(Graphics g)。
4. DiamondBlock
DiamondBlock是响应各种图形重画的核心类。与DiamondCanvas和DiamondMap均可直接通信。
在diamondBlock中以成员变量的形式存储了7个预定义的方块逻辑数组,颜色选配方案以及相对坐标端点。DiamondBlock定义画出单个小方砖,小方块,以及对某个图形方块的位置,碰撞检测,移动检测,转动检测,获取坐标值,更新坐标值等操作。
下面简略的分析一下DiamondBlock程序结构.
首先DiamondBlock定义了颜色数组
BRICK_COLORS,由于一种游戏方块对应一种颜色,
BRICK_COLORS的索引也就唯一标识了某一个游戏方块。而二维数组blockpatternX系列则定义7种下坠物方块,包含每个方块可能的4种转换位置。
DiamondBlock的构造函数中首先引用了DiamondMap的一个实例,接着随即生成了下一个游戏方块。在init()中,DiamondBlock初始化了当前下坠物和下一个下坠物,并根据当前下坠方块的初始化位置,判断游戏是否结束。
方块是否能够移动,旋转都需要被DiamondBlock中类似于checkXXX()的方法判断,如果判断成功则通过move(int)移动,通过down()下移或者rotBlock()变换状态。
当方块已经不能继续下移的时候,fixBlock()可以更新当前逻辑地图数据。paint(Graphics)则是通过首先清理原先背景,再调用drawBlock(Graphics g)来更新图层。
总之,DiamondBlock提供粒子级别的操作方法,将底层的数组更新和画布重绘与上层的游戏逻辑隔离开。
以上几个程序文件大部分成员变量和方法,都附有较详细的注释说明,你可以在具体代码中得到更为清晰的解释。
仍需考虑和改进的问题:
程序里面运用了相当一部分 static类型成员变量,而此类变量往往需要一直占用内存,直到本身类被消除。而如果把这部分变量改为由实例产生,则内存会随着实例的增加而增加,虽然可以在使用结束以后把它置为null,可是什么时候回收将是垃圾回收器(GC)的事,就我性能测试的感觉而言,这是一件非常不稳定的事。
当然,以上问题已经超出了本程序的考虑范围,本程序的性能还是很稳定的,笔者只是对手中另外一款正在处理的程序产生了以上考虑,如果你有什么好的解决方案,请一定告诉我,可以直接回复或者以合适的方式通知我。