记不得从哪个网站上看到的了(貌似gamedev.net),说Tetris(传说中的俄罗斯方块)应该是每个游戏初学者的首款作品。无论如何,它确实包含了很多游戏的基本元素,做一个优秀的Tetris也并不容易——当然,我写的这个就比较容易~
这个Tetris的算法并不算最好的,它只是我目前对游戏结构的理解的一种表现,并且引入了一些软件工程的理念——当然,仓促写作它的根本原因是2006年的情人节作为礼物送给我的女友。
源码:http://blog.matrix.org.cn/resources/fantasydog/Tetris.zip
打包好的可发布文件:http://blog.matrix.org.cn/resources/fantasydog/deployed.zip
1.
平台
这个游戏是建立在j2me平台上的。当然,由于软件工程的理念的原因,大多数核心的类于平台无关,因此易于移植(当然只是易于,并没有人真正会去移植它吧)。
2.
Engine
Engine和SplashScreen都来自Jason Lam的书J2ME &
Gaming:http://www.jasonlam604.com/books.php#j2megaming
这是一本很好的入门书籍,当然,他提供的例子都是相当精炼有效的。
我把Engine做在了Canvas里:GameCanvas<|---Engine<|---TetrisCanvas。
这样做违反了SRP(Single Responsibility
Principle [for a class]),不过这样做可以减少不少的复杂度,继承的关系也很大程度的分离了Canvas和Engine的Responsibility,还是可以一用的(在这样简单的游戏里)。
3.
结构
之前写过几个游戏,发现MVC模式似乎并不适合游戏,特别是2d的游戏,因为游戏中大多数元素的内在结构与行为与它的View息息相关——特别是碰撞测试之类。前面提到的那本书中所使用的模式也并不明朗,似乎并不是什么成型的模式,但比较适用于游戏:游戏的各个表现元素维护自己的状态和View,Engine只负责安排什么时候画它,要画的时候把画笔给它自己画好了。于是有了下面三个主要的部件:
a.
Canvas(Engine):Engine的作用是Engine类提供的,Canvas主要负责设备信息的初始化(屏幕尺寸,根据游戏的要求计算基本的pixel——一个方块占多少个像素,等),各部件的运作顺序,一些信息的显示(当前分数等)。
b.
Pattern:不是模式,而是图画的意思。对Tetris的元素进行分层,由远到近可分为背景、现有的图案(方块残渣?)和正在下落的方块。鉴于现有的图案在方块下落时(游戏的大部分时间里)是和背景一样不会动的,就把背景和现有的图案做在了一起,成为Pattern。
²
在绘图的时候,Pattern里做一个mutable的image,Pattern的内容发生变化了就更新一次这个image,每次做gameRender的时候直接把这个image画上去就ok了。如前所述,游戏大部分时间里是活动的方块在运动,而不是Pattern在变化,因此用个image可以提高不少效率。
²
闪烁效果:由Canvas控制。调用Pattern的paint函数中有个boolean变量告诉Pattern是否要闪烁。如果要,就对满行做闪烁绘制,否则画image。(怎么闪烁?每帧画不同的颜色嘛)
c.
Brick:下落的方块,没得说,活动的元素,玩家操作的对象,主角,描绘在最表层。
4.
算法
如前所述,这个程序的算法并不最好,但一定是相当节省内存的算法了。^_^
a.
Pattern:如何做Pattern的数据结构呢?对于一个二维空间的画布,最基础的方法是做个二维数组,每个元素表示相应的位置上有没有方块。那用什么类型?boolean就足够了。
Boolean? 嘿嘿嘿…….
现在我要10*20的数组,每行10个,共二十行,那么,我就用一个20个的int数组来表示——不是么?一个int有32位,足够表示32个boolean的了。如此一来,要检测在i行的j位置上有没有方块,只要做:(lines[i]&1<<j )!=0就可以了——计算机做位操作可是绝对快的。
在i行的j位置上加入一个方块也就成了 line[i]|= 1<<j;
还有一个受益的是对满行的检测,当然也就不用再遍历这个行的每个位置——8个1的二进制数转化为十进制是多少?line[i]==FULL搞定
还有一个潜在的好处,如果第3行消失,第4行掉下,正好能落入第2行怎么办呢?似乎没有Tetris做这个,我也没有。不过要做也很方便:检测就用一句(line[i]&line[i+1])==0,落入的结果就是 line[i]|=line[j]——还是那句话,位操作是很快的。
最后,这个算法是我自己想出来的,以前有没有人做?不知道(不过应该有)。所以如果这里没提您的名字,见谅,因为我确实不知道
b.
Brick:砖块,有7种造型,不包括其变化,均可以在一个4*4的区域里表示。承上算法,4*4=16<32,因此一个方块的形态可以用一个int来表示。
至于方块的行为嘛,几乎都一样,不同的无外乎不同的形态。所以,方块所有的算法都可以放在Brick中,不同的造型就构造相应的子类,在子类中定义其形态和变形的形态。具体可参见源码——这里除了应用了软件工程的理念,就没什么别的技术含量了。
当然做检测的算法很简单了(4个方块都检测一下,以确定该Brick是否可以移动或变形),可以改进,不过对于一个20帧的游戏来说,这点计算量实在算不了什么。
5.
其他
MIDP相关内容参见其它书籍或API
源码中几乎没什么注释(基本不太需要多少注释),看官受累
本程序没什么技术价值或者商业价值,不过若是整体使用或转载请考虑附上我的名字(fantasydog),谢谢