android 游戏:俄罗斯方块的小结和开发过程
最近觉得在android上开发游戏是一件很有趣的事情,所以查找了一些资料,自己做出了一个简单俄罗斯方块游戏,现将其总结如下:
1、基础数据模型
一个俄罗斯方块都是以一个4*4的二维数组来存储的,在我的demo游戏中,一共有7种方块类型: IS ZJ O L T七种类型。见下图:
每种类型都有四种旋转状态,如何在一个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 forreturn true;}//判断x,y点的值是否为1,1返回true 0返回falsepublic 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()的时候会被调用*/@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);shape.drawMe(canvas, d);ground.drawMe(canvas, d);}@Overridepublic 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(); } } /*@Overridepublic 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 {@Overrideprotected 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、
更多相关文章
- 将VitamioBundle工程作为Android(安卓)Library引入Demo工程使用
- Android应用开发笔记(4):再探Android多应用间数据共享机制,自定义C
- Android(安卓)Activity启动流程分析--------基于Android(安卓)O
- Android(安卓)MVP模式 初步理解
- 第八章 网络的时代—网络开发(3)
- Android进阶知识树——Android(安卓)多进程、Binder 你必须知道
- android解析XML文件的三方法之DOM
- Android调用天气接口(和风天气)
- Android(安卓)界面滑动实现---Scroller类 从源码和开发文档中学