android 游戏:俄罗斯方块的小结和开发过程

       最近觉得在android上开发游戏是一件很有趣的事情,所以查找了一些资料,自己做出了一个简单俄罗斯方块游戏,现将其总结如下:

1、基础数据模型

     一个俄罗斯方块都是以一个4*4的二维数组来存储的,在我的demo游戏中,一共有7种方块类型: I S Z J O L  T七种类型。见下图:

 
android 游戏:俄罗斯方块的小结和开发过程
每种类型都有四种旋转状态, 如何在一个4*4的二位数组表示呢?可以用0和1来表示,1表示该小单元格需要显示,0表示该小单元格不需要显示。
设置屏幕为320*480,每个小单元格是20像素,这样宽就是COLS=16,高就是ROWS=24。

2、涉及到的几个JavaBean

2.1   Shape:把每种图形抽象出来为一个对象,具有的属性是

         int的 left(x方向)和top(y方向)、

         int的 status 旋转状态,可选择的值是0,1,2,3   默认是0

         int[][] body 存储四种旋转状态的数据

         int LEFT = 1, RIGHT = 2, DOWN = 3, ROTATE = 4

       

        Shape具有的方法是moveLeft()    moveRight()   moveDown()和rotate()四种基本方法,对应模拟器上的四个方向键。如果模拟器的方向键不能使用,自己在百度或google上看看是怎么解决的,这里不记录。

       同时Shape还应该具有drawMe()画出值为1的单元格、checkBound()检查是否出了边界 和 checkValue(x, y)检查对应的小单元格的值是否为1或0,  1显示0不显示。

2.2 Ground:地面类 就是各个小单元格堆放起来的那个地面,我们也把它抽象成为一个JavaBean,经过分析,它也应该具有如下的属性和方法。

        int[][] body  这个body就是面板的最大面积COLS * ROWS =16*24

        它应该有一个drawMe()的方法,显示他目前已经接纳且未消行的小单元格,另外它应该有消行和统计分数的方法removeRows(),检查某个小单元格是否为1的方法checkValue(),接纳Shape的方法addShape()。

2.3 定义一个GameView,用来显示Shape和Ground的图形变化,需要重写的方法有

     onDraw():主要是画Shape和Ground

     onKeyDown():处理方向键按下的事件

     在它的构造函数里面需要实现小单元格的静态资源的获取,

     调用setFocusable(true);  以显示图形

     在正式运行时需要执行定时器,以3秒钟不停刷新页面的数据。

2.4 MainActivity  将系统的标题栏去掉,调用

    //以下去掉标题和全屏
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
          WindowManager.LayoutParams.FLAG_FULLSCREEN);

   然后调用GameView。

===================================================================

按照以上的步骤,我开始逐步编程

 1、ShapeFactory的代码

  

import java.util.Random;

public class ShapeFactory {
	public static Shape getShape(){
		Shape shape = new Shape();
		int type = new Random().nextInt(shapes.length);
		shape.setBody(shapes[type]);
		shape.setStatus(0);
		return shape;
	}
	
	// 方块的形状 第一组代表方块类型有S、Z、L、J、I、O、T 7种
	// 第二组为图型的型状态
	//每三组图型数据,根据这些数来描绘在view中
	private final static int shapes[][][] = new int[][][] {
	   // i
	   { { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 },
	     { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 } },
	   // s
	   { { 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
	     { 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 } },
	   // z
	   { { 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
	     { 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 } },
	   // J
	   { { 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 },
	     { 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
	     { 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
	   // o(田字)
	   { { 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
	   // l(竖右勾)
	   { { 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 },
	     { 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
	     { 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
	   // T
	   { { 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
	     { 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	     { 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0 } } };
}

配置文件

public class Config {
	public static final int CELL_SIZE = 20;
	//320*480  宽 *高
	public static final int ROWS = 24;//行--高
	public static final int COLS = 16;//列 --宽
}

2、Shape的代码

import android.graphics.Canvas;
import android.graphics.drawable.Drawable;

public class Shape {
	private int left;
	private int top;
	private int[][] body;
	
	private int status;
	public static final int LEFT = 1, RIGHT = 2, DOWN = 3, ROTATE = 4;
	
	public void moveLeft(){
		left--;
	}	
	public void moveRight(){
		left++;
	}
	//move down
	//must controller multiple thread(1-refresh schedule, 2-event) 
	public synchronized void moveDown(Ground ground) {   
		if (checkBound(Shape.DOWN, ground))    
			top ++;  
	}
	
	//旋转其实就是转到下一个图形中,通过改变status的值即可
	public void rotate(){
		status = (status+1)%body.length;
	}
	
	/**绘制Shape需要传入画布Canvas和图片资源**/
	public void drawMe(Canvas canvas, Drawable d){
		for(int x=0; x<4; x++){
			for(int y=0; y<4; y++){
				if(checkValue(x,y)){//如果单元格的值不为0则为1,需要绘制单元格
					//绘制之前需要先设置边界,设置边界有两种方法,这里选择四个参数的
					//这四个参数需要计算出其绝对坐标值
					int tempx = (x+left)*Config.CELL_SIZE;
					int tempy = (y+top)*Config.CELL_SIZE;
					int right = tempx+Config.CELL_SIZE;
					int bottom = tempy+Config.CELL_SIZE;
					d.setBounds(tempx, tempy, right, bottom);
					d.draw(canvas);
				}
			}
		}
		
	}
	
	//检查Shape是否到达边界,这里需要考虑向左 向右和向下三种情况
	public boolean checkBound(int action, Ground ground){
		int temptop = top;
		int templeft = left;
		switch(action){
		case LEFT:
			templeft--;
			break;
		case RIGHT:
			templeft++;
			break;
		case DOWN:
			temptop++;
			break;
		case ROTATE:
			break;
		}
		for(int x=0; x<4; x++){
			for(int y=0; y<4; y++){
				if(checkValue(x,y)){//如果单元格的值为1,需要判断它是否到达边界					
					if(templeft<0 || templeft >Config.COLS //X方向的边界判断
							|| temptop > Config.ROWS //Y方向的边界判断
							|| ground.checkValue(templeft,temptop,x,y) ){
						return false;
					}//end if					
				}
			}
		}//end for
		return true;		
	}
	
	//判断x,y点的值是否为1,1返回true 0返回false
	public boolean checkValue(int x, int y){
		return body[status][4*y+x]==1;		
	}	
	public int[][] getBody() {
		return body;
	}
	public void setBody(int[][] body) {
		this.body = body;
	}
	public int getStatus() {
		return status;
	}
	public void setStatus(int status) {
		this.status = status;
	}

3、 Ground的代码

import android.graphics.Canvas;
import android.graphics.drawable.Drawable;

public class Ground {

	public boolean checkValue(int left, int top, int x, int y) {
		if (body[left + x][top + y] == 1) {
		  return true;
	    }
	    return false;
	}
	
   public int[][] body = new int[Config.COLS][Config.ROWS];
	
	public void drawMe(Canvas canvas, Drawable d) {
		for (int x = 0; x < Config.COLS; x++) {//列 --> X方向
			for (int y = 0; y < Config.ROWS; y++) {//行--> Y方向
				if (body[x][y] == 1) {
					int tempX = x * Config.CELL_SIZE;
					int tempY = y * Config.CELL_SIZE;
		            int right = tempX + Config.CELL_SIZE;
		            int bottom = tempY + Config.CELL_SIZE;
		            d.setBounds(tempX, tempY, right, bottom);
		            d.draw(canvas);
		        }
		   }
	    }
   }
	
	/**
	 * remember drawed block 
	 * @param type  block type(seven type)  
	 * @param status block status
	 * @param left   x coordinate 
	 * @param top   y coordinate
	 */
	 public void add(int left, int top, Shape shape) {
		 for (int x = 0; x < 4; x++) {
			 for (int y = 0; y < 4; y++) {
				 if (shape.checkValue(x, y)){
					 try{
						 body[left + x][top + y] = 1;  
					 } catch(Exception e) {
						 e.printStackTrace();
					 }//end try-catch
				 }//end if
			 }//end for loop
		 }//end for loop
	}
	 
	 /**
     * remove rows 当所堆积的小框框满一行时,就要进行消行。
     * 消行的逻辑是:以Y方向为基准,从最底下一行的开始检查,当某一行全部满格时,则进行消行
     * 消行其实就是把上一行的数据填充到下一行的位置上,以此进行循环操作
     */
	public void removeRow() {//消掉多少行
		int c = 0;
	    int removeRows = 0;//counter 
	    for (int y = Config.ROWS - 1; y > 0; y--) {//y coordinate
	    	for (int x = 0; x < Config.COLS; x++) {//x coordinate
	    		if (body[x][y] == 1){
	    			c++;
		            if (c == Config.COLS) {//should remove the row
		            	removeRows++;
		            	for (int j = y; j > 0; j--) {//reset all block in body y coordinate
		            		for (int z = 0; z < Config.COLS; z++) {//x coordinate
		            			body[z][j] = body[z][j - 1];
		            		}//end for loop
		            	}//end for loop
		            	y++;//because the row has fall down one row.
		            }//end if
	    		}//end if
	    	}//end for
	    	c = 0;
	    }//end for
	    countScore(removeRows);
	}
	   
	/**
	 * count score统计分数
	 * @param removeRows
	 */
	public void countScore(int removeRows) {
	}
}

4、GameView

import java.util.Timer;
import java.util.TimerTask;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.KeyEvent;
import android.view.View;

public class GameView extends View {

	private Shape shape;
	private Drawable d;
	Ground ground = new Ground();
	public GameView(Context context) {
		super(context);
		d = context.getResources().getDrawable(R.drawable.brick);
		shape = ShapeFactory.getShape();
		//new Thread(this).start();
		setFocusable(true);
		schedule();
	}

	/**
	* onDraw()方法只会在初始化后被调用一次,或者在使用postInvalidate()的时候会被调用
	*/
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		shape.drawMe(canvas, d);
		ground.drawMe(canvas, d);
	}
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		switch (keyCode) {   
			case KeyEvent.KEYCODE_DPAD_UP:    
				shape.rotate();    
				break;  
			case KeyEvent.KEYCODE_DPAD_DOWN:   
				if(shape.checkBound(Shape.DOWN, ground))
					shape.moveDown(ground);   				  
				break;   
			case KeyEvent.KEYCODE_DPAD_LEFT:  
				if(shape.checkBound(Shape.LEFT,ground))
					shape.moveLeft();    
				break;   
			case KeyEvent.KEYCODE_DPAD_RIGHT:   
				if(shape.checkBound(Shape.RIGHT, ground))
					shape.moveRight();    
				break;   
			default:    
				break;   
		}    
		return super.onKeyDown(keyCode, event);  
	}
	
	private void schedule(){
		 Timer nT1 = new Timer();
		 nT1.schedule(new TimerTask(){ //计划运行时间间隔
			 public void run(){
				 refresh(); //过3秒调用一下refresh()
	         }
	     },0,500);
	}
	
	 public void refresh(){   
		 //auto down   
		 if (shape.checkBound(Shape.DOWN, ground)){    
			 shape.moveDown(ground);    
			 this.postInvalidate(); //使用postInvalidate();刷新   
		 } else{//generator new block        
			 ground.add(shape.left, shape.top, shape);        
			 ground.removeRow();        
			 shape = ShapeFactory.getShape();   
		 }      
	}
	 
	/*@Override
	public void run() {
		while(true){
			if(shape.checkBound(Shape.DOWN))
				shape.moveDown();
			postInvalidate();
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
	}*/
}

 6、MainActivity

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		//setContentView(R.layout.activity_main);
		
      //以下去掉标题和全屏
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
          WindowManager.LayoutParams.FLAG_FULLSCREEN);
        GameView gameView = new GameView(this);
        setContentView(gameView);
	}

}

  

6、

相关推荐