俄罗斯方块Tetris是一款很经典的益智游戏,之前就做了一款桌面版的java俄罗斯方块,这次就尝试着写了一款适用于Android平台的俄罗斯方块。
整个程序设计十分简洁,只是运用了两个类而已,最终做出的效果图如下所示:
首先,要考虑的自然是游戏应该如何布局的问题了。我的想法是将手机屏幕分为上下两部分,上边用来显示游戏者的名称、所得分数以及下一个方块,称为“文字区域”,下边自然就是游戏区域了。
如图所示:
布局文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:tools = "http://schemas.android.com/tools" android:id = "@+id/linear" android:layout_width = "match_parent" android:layout_height = "match_parent" android:background = "@drawable/three" android:orientation = "vertical" android:padding = "25px" > < TextView android:id = "@+id/text1" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:background = "@drawable/hh" /> < TextView android:id = "@+id/text2" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:height = "30px" /> < FrameLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" > </ FrameLayout > </ LinearLayout > |
为了让游戏能够更好地适配Android众多大小不一的屏幕,需要对布局进行动态规划。在xml文件中,android:padding=”25px”,text1代表的是上方的文字区域,txet1的背景是一张半透明的图片,在运行程序时会根据手机屏幕大小动态规划其高度。text2是文字区域以及游戏区域之间的间距,我将它的高度定为固定值“30px”。而游戏区域的高度亦是会动态规划的,自定义的view将会添加在FrameLayout当中。
自定义的view组件代码如下,用来绘制并显示所有的方块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public class Brick extends View { // 要绘制的方块的坐标集 private boolean [][] map; //保存每个坐标点在屏幕中的坐标 private Point[][] Points; private int PADDING = 3 ; //方块的宽度 private float BRICK_WIDTH; private Paint paint = new Paint(); private RectF rectf; public Brick(Context context, boolean [][] map, float BRICK_WIDTH) { super (context); this .map = map; this .BRICK_WIDTH = BRICK_WIDTH; setBackgroundResource(R.drawable.one); } public void onDraw(Canvas canvas) { super .onDraw(canvas); Points = new Point[ 10 ][ 15 ]; for ( int j = 0 ; j < 15 ; j++) { for ( int i = 0 ; i < 10 ; i++) { Points[i][j] = new Point(i * ( int ) BRICK_WIDTH, j* ( int ) BRICK_WIDTH); } } for ( int j = 0 ; j < 15 ; j++) { for ( int i = 0 ; i < 10 ; i++) { if (map[i][j]) { paint.setColor(Color.parseColor( "#f7faf3" )); float x = Points[i][j].x; float y = Points[i][j].y; rectf = new RectF(x, y, x + BRICK_WIDTH, y + BRICK_WIDTH); canvas.drawRect(rectf, paint); paint.setColor(Color.parseColor( "#4c8e0b" )); rectf = new RectF(x + PADDING, y + PADDING, x + BRICK_WIDTH- PADDING, y + BRICK_WIDTH - PADDING); canvas.drawRect(rectf, paint); } } } } } |
当中,map是一个boolean类型的二维数组,因为我将游戏区域的比例设为10乘15,所以map的大小即为map[10][15],map[0][0]即为游戏区域的左上角,map[9][14]为游戏区域的右下角。如果方块落在了某个坐标点,则该坐标值设为true,否则为false。则当方块不断下落时,通过计算方块的新的坐标点并重新构建新的map,即可获得新的view对象。
BRICK_WIDTH为每个方块的宽度,在构造函数中获得。
因此,如果new一个Brick对象,且map的值均设为true,将之添加到FrameLayout当中,即可获得如下效果:
在Activity类中,通过如下代码可获得屏幕信息:
1
2
3
4
5
|
//获取屏幕的宽度和高度 DisplayMetrics metric = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metric); SCREEN_WIDTH = metric.widthPixels; SCREEN_HIGHT = metric.heightPixels; |
SCREEN_WIDTH 是屏幕宽度,SCREEN_HIGHT 是屏幕高度,则SCREEN_WIDTH 减去两倍PADDING,再除以十后,就可以得到方块的宽度BRICK_WIDTH,而BRICK_WIDTH乘以十五后,即游戏区域的高度了,这样就可以算出文字区域的高度了
1
2
3
4
5
6
|
BRICK_WIDTH = (SCREEN_WIDTH - 2 * PADDING) / 10 ; GAME_HIGHT = 15 * BRICK_WIDTH; TEXT_HIGHT = SCREEN_HIGHT - 2 * PADDING - 30 - GAME_HIGHT; text = (TextView) findViewById(R.id.text1); frame = (FrameLayout) findViewById(R.id.frame); text.setHeight(( int ) TEXT_HIGHT); |
下落方法的基本形状有如下6种,每个方块下落时的初始坐标点亦如下所示:
如正方形方块有四个点,坐标分别为(4,0)(5,0)(4,1)(5,1)。用List< Point[] >类型的listPoints来保存坐标集合。注意:每个下落方块的第一个坐标点均是有特殊作用的,这个后边会说到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private static List<Point[]> listPoints; static { listPoints = new ArrayList<Point[]>( 6 ); listPoints.add( new Point[] { new Point( 4 , 0 ), new Point( 5 , 0 ), new Point( 4 , 1 ), new Point( 5 , 1 ) }); listPoints.add( new Point[] { new Point( 4 , 1 ), new Point( 4 , 0 ), new Point( 4 , 2 ), new Point( 4 , 3 ) }); listPoints.add( new Point[] { new Point( 4 , 1 ), new Point( 5 , 0 ), new Point( 4 , 0 ), new Point( 4 , 2 ) }); listPoints.add( new Point[] { new Point( 5 , 1 ), new Point( 5 , 0 ), new Point( 4 , 0 ), new Point( 5 , 2 ) }); listPoints.add( new Point[] { new Point( 5 , 1 ), new Point( 5 , 0 ), new Point( 4 , 1 ), new Point( 5 , 2 ) }); listPoints.add( new Point[] { new Point( 4 , 1 ), new Point( 4 , 0 ), new Point( 5 , 1 ), new Point( 5 , 2 ) }); } |
在程序中,我的想法是new两个map对象,map1用来保存所有固定不动的方块坐标点,map2用来保存还在下落的方块的坐标点,这样就能够new两个Brick对象,然后通过覆盖的方法来使之同时显示在同个区域内。这也是我将它们添加到FrameLayout布局的原因。
下落方块的移动算法如下,适用于左移还有右移
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
//移动 public void move( int moveX, int moveY) { for ( int i = 0 ; i < point.length; i++) { int newX = point[i].x + moveX; int newY = point[i].y + moveY; if (newX < 0 || newX > 9 || newY > 14 || map1[newX][newY]) { return ; } } for ( int k = 0 ; k < 15 ; k++) { for ( int t = 0 ; t < 10 ; t++) { map2[t][k] = false ; } } for ( int j = 0 ; j < point.length; j++) { point[j].x = point[j].x + moveX; point[j].y = point[j].y + moveY; } for ( int j = 0 ; j < point.length; j++) { int x = point[j].x; int y = point[j].y; map2[x][y] = true ; } frame.removeView(brick2); brick2 = new Brick( this , map2, BRICK_WIDTH); frame.addView(brick2); } |
则要左移和右移时只要分别为move(int moveX, int moveY)函数传入不同参数即可实现对应操作:
1
2
3
4
5
6
7
8
9
|
// 左移 public void leftBrick(View view) { move(- 1 , 0 ); } // 右移 public void rightBrick(View view) { move( 1 , 0 ); } |
变形操作我在我的另一篇博文中也写到过:用Java写俄罗斯方块,需要下落方块有一个固定的旋转点,这个旋转点我设为下落方块的第一个坐标点,这也是我前边所说的第一个坐标点的特殊作用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// 变形 public void changeBrick(View view) { if (point[ 0 ].x + 1 == point[ 1 ].x && point[ 2 ].x + 1 == point[ 3 ].x && point[ 0 ].y + 1 == point[ 2 ].y) { return ; } for ( int i = 0 ; i < point.length; i++) { int newX = point[ 0 ].y + point[ 0 ].x - point[i].y; int newY = point[ 0 ].y - point[ 0 ].x + point[i].x; if (newX < 0 || newX > 9 || newY > 14 || map1[newX][newY]) { return ; } } for ( int i = 0 ; i < point.length; i++) { int newX = point[ 0 ].y + point[ 0 ].x - point[i].y; int newY = point[ 0 ].y - point[ 0 ].x + point[i].x; point[i].x = newX; point[i].y = newY; } for ( int k = 0 ; k < 15 ; k++) { for ( int t = 0 ; t < 10 ; t++) { map2[t][k] = false ; } } for ( int j = 0 ; j < point.length; j++) { int x = point[j].x; int y = point[j].y; map2[x][y] = true ; } frame.removeView(brick2); brick2 = new Brick( this , map2, BRICK_WIDTH); frame.addView(brick2); } |
消行操作需要在每次下落方块无法再下落时检查是否需要实行,所以检查是从第十五行开始直到第一行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 消行 public void MoveLine() { for ( int j = 14 ; j >= 0 ; j--) { int n = 0 ; for ( int i = 0 ; i < 10 ; i++) { if (map1[i][j]) { n++; } } if (n == 10 ) { for ( int k = j; k > 0 ; k--) { for ( int i = 0 ; i < 10 ; i++) { map1[i][k] = map1[i][k - 1 ]; } } j = j + 1 ; } } } |
注意当中的j = j + 1语句,因为当第j行消行后上方区域需要整个“下沉”一行,所以原来的第j-1行就变成了第j行,所以还需要再从现在的第j行检查起,加一的原因是在for循环中j会减一,所以这里先加一。
下移操作是整个程序设计中的难点,在这里面要实现消行检查,map1和map2的数据更新,以便刷新新的界面,这里代码就不再贴出了。
此外,我原本是打算用手势操作来控制方块的移动的,可因为根据手指滑动来判断方向会有很大误差,所以我最终还是采用Button来实现控制操作,可以看到效果图当中有三个不同形状的图片,分别对应左移,变形和右移,且该三个Button组件是在Brick之后添加到布局文件当中的,这样才能使按钮图片是覆盖在方块表面。
源代码下载:Android版俄罗斯方块
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/new_one_object/article/details/50606452