Android 贪吃蛇 Android 2d游戏开发之贪吃蛇基于surfaceview
potato47 人气:0想了解Android 2d游戏开发之贪吃蛇基于surfaceview的相关内容吗,potato47在本文为您仔细讲解Android 贪吃蛇的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:Android,贪吃蛇,Android,游戏,下面大家一起来学习吧。
前两个游戏是基于View游戏框架的,View游戏框架只适合做静止的,异步触发的游戏,如果做一直在动的游戏,View的效率就不高了,我们需要一种同步触发的游戏框架,也就是surfaceview游戏框架,你可能会问,什么乱七八糟的,啥叫同步?啥叫异步?。。。我就不告诉你。。。我们先看一下这个同步框架,看看骚年你能不能自己领悟。
GameView.java(继承自SurfaceView)
package com.next.eatsnake; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnTouchListener; import java.util.Random; /** * Created by Next on 2016/3/24 0024. */ public class GameView extends SurfaceView implements SurfaceHolder.Callback,OnTouchListener,Runnable { enum GameState{ Menu, Playing, Over; } GameState gameState;//游戏状态 Thread thread;//游戏线程 Boolean flag;//游戏循环控制标志 Canvas canvas;//画布 Paint paint;//画笔 SurfaceHolder holder;//SurfaceView控制句柄 Random random;//随机数 NextEvent nextEvent;//游戏输入事件 int scrW,scrH;//屏幕的宽和高 public GameView(Context context) { super(context); gameState = GameState.Menu; flag = true; paint = new Paint(); paint.setAntiAlias(true);//笔迹平滑 thread = new Thread(this); random = new Random(); nextEvent = new NextEvent(); holder = this.getHolder(); holder.addCallback(this); this.setOnTouchListener(this); setKeepScreenOn(true); scrW = ((MainActivity)context).getWindowManager().getDefaultDisplay().getWidth(); scrH = ((MainActivity)context).getWindowManager().getDefaultDisplay().getHeight(); } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { nextEvent.setDownX((int) event.getX()); nextEvent.setDownY((int) event.getY()); }else if (event.getAction() == MotionEvent.ACTION_UP) { nextEvent.setUpX((int) event.getX()); nextEvent.setUpY((int) event.getY()); } return true; } private void mLogic(){ switch (gameState){ case Menu: menuLogic(); break; case Playing: playingLogic(); break; case Over: overLogic(); break; } nextEvent.init();//每次逻辑循环后,清空事件 } private void menuLogic(){ if(nextEvent.getUpX() > 0){ gameState = GameState.Playing; } } private void playingLogic(){ if(nextEvent.getDir() == NextEvent.DOWN){ gameState = GameState.Over; } } private void overLogic(){ if(nextEvent.getDir() == NextEvent.RIGHT){ gameState = GameState.Menu; } } private void mDraw(){ try { canvas = holder.lockCanvas(); canvas.drawColor(Color.WHITE); switch (gameState){ case Menu: menuDraw(canvas); break; case Playing: playingDraw(canvas); break; case Over: overDraw(canvas); break; } }catch (Exception e){ e.printStackTrace(); }finally { holder.unlockCanvasAndPost(canvas); } } private void menuDraw(Canvas canvas){ paint.setColor(Color.RED); paint.setTextSize(50); canvas.drawText("I am in menu.Touch me to next scence",100,100,paint); } private void playingDraw(Canvas canvas){ paint.setColor(Color.RED); paint.setTextSize(50); canvas.drawText("I am in playing,Slide down to next scence ",100,100,paint); } private void overDraw(Canvas canvas){ paint.setColor(Color.RED); paint.setTextSize(50); canvas.drawText("I am in over,Slide right to next scence",100,100,paint); } //这里就是同步触发机制了 //每一个时钟周期,执行一次逻辑方法和绘图方法 @Override public void run() { while(flag){ mLogic(); mDraw(); try { Thread.sleep(500); }catch (Exception e){ e.printStackTrace(); } } } //SurfaceView创建时调用 @Override public void surfaceCreated(SurfaceHolder holder) { thread.start(); } //SurfaceView发生改变时调用 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } //SurfaceView销毁时调用 @Override public void surfaceDestroyed(SurfaceHolder holder) { flag = false; } }
这只是一个游戏框架,还没有往里写任何内容,但根据我的经验(虽然我也没有很多经验),这里已经包括了所有游戏(这里特指小游戏-_-)的主体框架代码,有了这个框架,我们就只需要写游戏的逻辑和绘图方法了,不用纠结怎么搭建游戏框架了。
这里我还加了一个NextEvent的方法,在里面我封装了上个游戏用到的滑动手势,在这个挨踢圈里,人们最常说的一句话就是:不要重复造轮子。当我们看到写了很多重复代码时,就是我们需要精简的时候了。
上代码:
NextEvent.java
package com.next.eatsnake; /** * Created by Next on 2016/3/24 0024. */ public class NextEvent { public static final int LEFT = 1; public static final int RIGHT = 2; public static final int UP = 3; public static final int DOWN = 4; public static final int QUIET = -1;//没有滑动 private int dir; private int downX,downY,upX,upY; public NextEvent() { init(); } public void init(){ this.dir = QUIET; this.downX = -1; this.downY = -1; this.upX = -1; this.upY = -1; } public int getDir(){ float offsetX,offsetY; offsetX = upX - downX; offsetY = upY - downY; if (Math.abs(offsetX) > Math.abs(offsetY)) { if (offsetX > 5 ) { dir = RIGHT; }else if (offsetX < -5) { dir = LEFT; } }else { if (offsetY > 5) { dir = DOWN; }else if (offsetY < -5) { dir = UP; } } return dir; } public int getDownX() { return downX; } public void setDownX(int downX) { this.downX = downX; } public int getDownY() { return downY; } public void setDownY(int downY) { this.downY = downY; } public int getUpX() { return upX; } public void setUpX(int upX) { this.upX = upX; } public int getUpY() { return upY; } public void setUpY(int upY) { this.upY = upY; } }
这个NextEvent是用来存储用户输入事件的,我们只是存储,而没有直接触发游戏逻辑。那么什么时候用到读取这个NextEvent呢?如果你仔细看了第一段代码,应该已经知道了——在每一个时钟周期的逻辑方法里判断NextEvent,并由此改变游戏逻辑。这种就是同步机制,而用户输入事件游戏逻辑就随之改变的就是异步机制。
下面我们用这个框架做一个贪吃蛇游戏,效果图如下:
MainActivity.java
package com.next.eatsnake; 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); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(new GameView(this)); } }
GameView.java
package com.next.eatsnake; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnTouchListener; import java.util.ArrayList; import java.util.Random; /** * Created by Administrator on 2016/3/24 0024. */ public class GameView extends SurfaceView implements SurfaceHolder.Callback,OnTouchListener,Runnable { enum GameState{ Menu, Playing, Over; } GameState gameState;//游戏状态 Thread thread;//游戏线程 Boolean flag;//游戏循环控制标志 Canvas canvas;//画布 Paint paint;//画笔 SurfaceHolder holder;//SurfaceView控制句柄 public static Random random;//随机数 NextEvent nextEvent;//游戏输入事件 int scrW,scrH;//屏幕的宽和高 final int MAX_X = 15; int MAX_Y;//竖向tile数量根据MAX_X动态计算,保证tile是正方形 public static Tile[][] tiles; Snake snake; public static boolean isEatFood; public GameView(Context context) { super(context); gameState = GameState.Menu; flag = true; paint = new Paint(); paint.setAntiAlias(true);//笔迹平滑 paint.setTextAlign(Paint.Align.CENTER);//文字中间对齐 thread = new Thread(this); random = new Random(); nextEvent = new NextEvent(); holder = this.getHolder(); holder.addCallback(this); this.setOnTouchListener(this); setKeepScreenOn(true); scrW = ((MainActivity)context).getWindowManager().getDefaultDisplay().getWidth(); scrH = ((MainActivity)context).getWindowManager().getDefaultDisplay().getHeight(); Tile.width = scrW/MAX_X; MAX_Y = scrH/Tile.width; tiles = new Tile[MAX_X][MAX_Y]; isEatFood = false; } private void newGame(){ for (int x = 0;x < MAX_X;x++){ for (int y = 0;y < MAX_Y;y++){ if (x == 0||y == 0||x == MAX_X-1||y == MAX_Y-1){ tiles[x][y] = new Tile(x,y,Tile.TYPE_WALL); }else { tiles[x][y] = new Tile(x,y,Tile.TYPE_NULL); } } } snake = new Snake(tiles[4][4],tiles[5][4],tiles[6][4], NextEvent.DOWN); addFood(); addFood(); addFood(); } public void addFood(){ ArrayList<Tile> nullList = new ArrayList<Tile>(); for (int x = 0;x < MAX_X;x++){ for (int y = 0;y < MAX_Y;y++){ if (tiles[x][y].getType() == Tile.TYPE_NULL){ nullList.add(tiles[x][y]); } } } if (nullList.size()!=0){ nullList.get(random.nextInt(nullList.size())).setType(Tile.TYPE_FOOD); } } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { nextEvent.setDownX((int) event.getX()); nextEvent.setDownY((int) event.getY()); }else if (event.getAction() == MotionEvent.ACTION_UP) { nextEvent.setUpX((int) event.getX()); nextEvent.setUpY((int) event.getY()); } return true; } private void mLogic(){ switch (gameState){ case Menu: menuLogic(); break; case Playing: playingLogic(); break; case Over: overLogic(); break; } nextEvent.init();//每次逻辑循环后,清空事件 } private void menuLogic(){ if(nextEvent.getUpX() > 0){ gameState = GameState.Playing; newGame(); } } private void playingLogic(){ if (nextEvent.getDir()!=NextEvent.QUIET){ snake.setDir(nextEvent.getDir()); } snake.move(); if (isEatFood){ addFood(); isEatFood = false; } if(!snake.isAlive()){ gameState = GameState.Over; } } private void overLogic(){ if(nextEvent.getUpX() > 0){ gameState = GameState.Playing; newGame(); } } private void mDraw(){ try { canvas = holder.lockCanvas(); canvas.drawColor(Color.WHITE); switch (gameState){ case Menu: menuDraw(canvas); break; case Playing: playingDraw(canvas); break; case Over: overDraw(canvas); break; } }catch (Exception e){ e.printStackTrace(); }finally { holder.unlockCanvasAndPost(canvas); } } private void menuDraw(Canvas canvas){ paint.setColor(Color.BLACK); paint.setTextSize(50); canvas.drawText("Eat Snake,Touch me and start",scrW/2,scrH/2,paint); } private void playingDraw(Canvas canvas){ for (int x = 0;x < MAX_X;x++){ for (int y = 0;y < MAX_Y;y++){ tiles[x][y].draw(canvas,paint); } } } private void overDraw(Canvas canvas){ paint.setColor(Color.BLACK); paint.setTextSize(50); canvas.drawText("Your score is:"+snake.getLength(),scrW/2,scrH/4,paint); canvas.drawText("Touch me and try again",scrW/2,scrH/2,paint); } @Override public void run() { while(flag){ mLogic(); mDraw(); try { Thread.sleep(500); }catch (Exception e){ e.printStackTrace(); } } } @Override public void surfaceCreated(SurfaceHolder holder) { thread.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { flag = false; } }
Tile.java
package com.next.eatsnake; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; /** * Created by Next on 2016/3/26 0026. */ public class Tile { public static final int TYPE_NULL = 0;//空 public static final int TYPE_WALL = 1;//墙 public static final int TYPE_HEAD = 2;//蛇头 public static final int TYPE_BODY = 3;//蛇身 public static final int TYPE_TAIL = 4;//蛇尾 public static final int TYPE_FOOD = 5;//食物 private int x,y; private int type; public static int width; public Tile(int x, int y, int type) { this.x = x; this.y = y; this.type = type; } public void draw(Canvas canvas,Paint paint){ switch (type){ case TYPE_NULL: break; case TYPE_WALL: paint.setColor(Color.BLACK); canvas.drawCircle(x*width+width/2,y*width+width/2,width/2,paint); break; case TYPE_HEAD: paint.setColor(Color.MAGENTA); canvas.drawCircle(x*width+width/2,y*width+width/2,width/2,paint); paint.setColor(Color.WHITE); canvas.drawCircle(x*width+width/2,y*width+width/2,width/8,paint); break; case TYPE_BODY: paint.setColor(Color.MAGENTA); canvas.drawCircle(x*width+width/2,y*width+width/2,width/2,paint); break; case TYPE_TAIL: paint.setColor(Color.MAGENTA); paint.setStrokeWidth(10); canvas.drawLine(x * width, y * width + width / 2, x * width + width / 2, y * width, paint); canvas.drawLine(x*width+ width / 2,y*width,x*width+width,y*width+width/2,paint); canvas.drawLine(x*width+width,y*width+width/2,x*width+width/2,y*width+width,paint); canvas.drawLine(x*width+width/2,y*width+width,x*width,y*width+ width / 2,paint); break; case TYPE_FOOD: switch (GameView.random.nextInt(3)){ case 0: paint.setColor(Color.YELLOW); break; case 1: paint.setColor(Color.GREEN); break; case 2: paint.setColor(Color.CYAN); break; } canvas.drawCircle(x*width+width/2,y*width+width/2,width/2,paint); break; } } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getType() { return type; } public void setType(int type) { this.type = type; } }
Snake.java
package com.next.eatsnake; import java.util.ArrayList; /** * Created by Administrator on 2016/3/26 0026. */ public class Snake { private ArrayList<Tile> snake; private int dir; private boolean isAlive; public Snake(Tile head,Tile body,Tile tail,int dir){ snake = new ArrayList<Tile>(); head.setType(Tile.TYPE_HEAD); body.setType(Tile.TYPE_BODY); tail.setType(Tile.TYPE_TAIL); snake.add(head); snake.add(body); snake.add(tail); isAlive = true; this.dir = dir; } public void move(){ if (!isAlive) return; switch (dir){ case NextEvent.LEFT: switch (GameView.tiles[snake.get(0).getX()-1][snake.get(0).getY()].getType()){ case Tile.TYPE_WALL: case Tile.TYPE_BODY: case Tile.TYPE_TAIL: isAlive = false; break; case Tile.TYPE_FOOD: GameView.tiles[snake.get(0).getX()-1][snake.get(0).getY()].setType(Tile.TYPE_HEAD); GameView.tiles[snake.get(0).getX()][snake.get(0).getY()].setType(Tile.TYPE_BODY); snake.add(0, GameView.tiles[snake.get(0).getX() - 1][snake.get(0).getY()]); GameView.isEatFood = true; break; case Tile.TYPE_NULL: GameView.tiles[snake.get(0).getX()-1][snake.get(0).getY()].setType(Tile.TYPE_HEAD); GameView.tiles[snake.get(0).getX()][snake.get(0).getY()].setType(Tile.TYPE_BODY); snake.add(0, GameView.tiles[snake.get(0).getX() - 1][snake.get(0).getY()]); snake.get(snake.size()-1).setType(Tile.TYPE_NULL); snake.remove(snake.size()-1); snake.get(snake.size()-1).setType(Tile.TYPE_TAIL); break; } break; case NextEvent.RIGHT: switch (GameView.tiles[snake.get(0).getX()+1][snake.get(0).getY()].getType()){ case Tile.TYPE_WALL: case Tile.TYPE_BODY: case Tile.TYPE_TAIL: isAlive = false; break; case Tile.TYPE_FOOD: GameView.tiles[snake.get(0).getX()+1][snake.get(0).getY()].setType(Tile.TYPE_HEAD); GameView.tiles[snake.get(0).getX()][snake.get(0).getY()].setType(Tile.TYPE_BODY); snake.add(0, GameView.tiles[snake.get(0).getX() + 1][snake.get(0).getY()]); GameView.isEatFood = true; break; case Tile.TYPE_NULL: GameView.tiles[snake.get(0).getX()+1][snake.get(0).getY()].setType(Tile.TYPE_HEAD); GameView.tiles[snake.get(0).getX()][snake.get(0).getY()].setType(Tile.TYPE_BODY); snake.add(0, GameView.tiles[snake.get(0).getX() + 1][snake.get(0).getY()]); snake.get(snake.size()-1).setType(Tile.TYPE_NULL); snake.remove(snake.size() - 1); snake.get(snake.size()-1).setType(Tile.TYPE_TAIL); break; } break; case NextEvent.UP: switch (GameView.tiles[snake.get(0).getX()][snake.get(0).getY()-1].getType()){ case Tile.TYPE_WALL: case Tile.TYPE_BODY: case Tile.TYPE_TAIL: isAlive = false; break; case Tile.TYPE_FOOD: GameView.tiles[snake.get(0).getX()][snake.get(0).getY()-1].setType(Tile.TYPE_HEAD); GameView.tiles[snake.get(0).getX()][snake.get(0).getY()].setType(Tile.TYPE_BODY); snake.add(0, GameView.tiles[snake.get(0).getX()][snake.get(0).getY()-1]); GameView.isEatFood = true; break; case Tile.TYPE_NULL: GameView.tiles[snake.get(0).getX()][snake.get(0).getY()-1].setType(Tile.TYPE_HEAD); GameView.tiles[snake.get(0).getX()][snake.get(0).getY()].setType(Tile.TYPE_BODY); snake.add(0, GameView.tiles[snake.get(0).getX()][snake.get(0).getY()-1]); snake.get(snake.size()-1).setType(Tile.TYPE_NULL); snake.remove(snake.size()-1); snake.get(snake.size()-1).setType(Tile.TYPE_TAIL); break; } break; case NextEvent.DOWN: switch (GameView.tiles[snake.get(0).getX()][snake.get(0).getY()+1].getType()){ case Tile.TYPE_WALL: case Tile.TYPE_BODY: case Tile.TYPE_TAIL: isAlive = false; break; case Tile.TYPE_FOOD: GameView.tiles[snake.get(0).getX()][snake.get(0).getY()+1].setType(Tile.TYPE_HEAD); GameView.tiles[snake.get(0).getX()][snake.get(0).getY()].setType(Tile.TYPE_BODY); snake.add(0, GameView.tiles[snake.get(0).getX()][snake.get(0).getY() + 1]); GameView.isEatFood = true; break; case Tile.TYPE_NULL: GameView.tiles[snake.get(0).getX()][snake.get(0).getY()+1].setType(Tile.TYPE_HEAD); GameView.tiles[snake.get(0).getX()][snake.get(0).getY()].setType(Tile.TYPE_BODY); snake.add(0, GameView.tiles[snake.get(0).getX()][snake.get(0).getY()+1]); snake.get(snake.size()-1).setType(Tile.TYPE_NULL); snake.remove(snake.size()-1); snake.get(snake.size()-1).setType(Tile.TYPE_TAIL); break; } break; } } public void setDir(int dir){ if (this.dir == dir||this.dir == -dir||dir == NextEvent.QUIET) return; else this.dir = dir; } public boolean isAlive(){ return isAlive; } public int getLength(){ return snake.size(); } }
NextEvent.java
就是刚开始介绍的那个类,这里就不重复贴出了
加载全部内容