前些时日希望找一份俄罗斯方块的游戏学习一下,发现网上大部分链接都是陈万飞兄的大作. 唯一遗憾的是程序写成的时候已经较长 , 而作为一个开源项目的延展 , 似乎并没有后续版本的跟上 . 学习之余 , 本人也做了一定的修改 , 目前的版本主要是将游戏从 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) 的事,就我性能测试的感觉而言,这是一件非常不稳定的事。 当然,以上问题已经超出了本程序的考虑范围,本程序的性能还是很稳定的,笔者只是对手中另外一款正在处理的程序产生了以上考虑,
|