Android 2d游戲開發(fā)之貪吃蛇基于surfaceview
前兩個游戲是基于View游戲框架的,View游戲框架只適合做靜止的,異步觸發(fā)的游戲,如果做一直在動的游戲,View的效率就不高了,我們需要一種同步觸發(fā)的游戲框架,也就是surfaceview游戲框架,你可能會問,什么亂七八糟的,啥叫同步?啥叫異步?。。。我就不告訴你。。。我們先看一下這個同步框架,看看騷年你能不能自己領(lǐng)悟。
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;//游戲狀態(tài)
Thread thread;//游戲線程
Boolean flag;//游戲循環(huán)控制標志
Canvas canvas;//畫布
Paint paint;//畫筆
SurfaceHolder holder;//SurfaceView控制句柄
Random random;//隨機數(shù)
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();//每次邏輯循環(huán)后,清空事件
}
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);
}
//這里就是同步觸發(fā)機制了
//每一個時鐘周期,執(zhí)行一次邏輯方法和繪圖方法
@Override
public void run() {
while(flag){
mLogic();
mDraw();
try {
Thread.sleep(500);
}catch (Exception e){
e.printStackTrace();
}
}
}
//SurfaceView創(chuàng)建時調(diào)用
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread.start();
}
//SurfaceView發(fā)生改變時調(diào)用
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
//SurfaceView銷毀時調(diào)用
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
flag = false;
}
}
這只是一個游戲框架,還沒有往里寫任何內(nèi)容,但根據(jù)我的經(jīng)驗(雖然我也沒有很多經(jīng)驗),這里已經(jīng)包括了所有游戲(這里特指小游戲-_-)的主體框架代碼,有了這個框架,我們就只需要寫游戲的邏輯和繪圖方法了,不用糾結(jié)怎么搭建游戲框架了。
這里我還加了一個NextEvent的方法,在里面我封裝了上個游戲用到的滑動手勢,在這個挨踢圈里,人們最常說的一句話就是:不要重復(fù)造輪子。當我們看到寫了很多重復(fù)代碼時,就是我們需要精簡的時候了。
上代碼:
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是用來存儲用戶輸入事件的,我們只是存儲,而沒有直接觸發(fā)游戲邏輯。那么什么時候用到讀取這個NextEvent呢?如果你仔細看了第一段代碼,應(yīng)該已經(jīng)知道了——在每一個時鐘周期的邏輯方法里判斷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;//游戲狀態(tài)
Thread thread;//游戲線程
Boolean flag;//游戲循環(huán)控制標志
Canvas canvas;//畫布
Paint paint;//畫筆
SurfaceHolder holder;//SurfaceView控制句柄
public static Random random;//隨機數(shù)
NextEvent nextEvent;//游戲輸入事件
int scrW,scrH;//屏幕的寬和高
final int MAX_X = 15;
int MAX_Y;//豎向tile數(shù)量根據(jù)MAX_X動態(tài)計算,保證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();//每次邏輯循環(huán)后,清空事件
}
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
就是剛開始介紹的那個類,這里就不重復(fù)貼出了
到此這篇關(guān)于Android 2d游戲開發(fā)之貪吃蛇基于surfaceview的文章就介紹到這了,更多相關(guān)Android 貪吃蛇內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Android中使用TextureView播放視頻
- Android中TextureView與SurfaceView用法區(qū)別總結(jié)
- Android使用MediaPlayer和TextureView實現(xiàn)視頻無縫切換
- Android OpenGL入門之GLSurfaceView
- Android中SurfaceView和普通view的區(qū)別及使用
- Android SurfaceView基礎(chǔ)用法詳解
- Android中SurfaceTexture TextureView SurfaceView GLSurfaceView的區(qū)別
- Android?中TextureView和SurfaceView的屬性方法及示例說明
相關(guān)文章
詳解Android短信的發(fā)送和廣播接收實現(xiàn)短信的監(jiān)聽
本篇文章主要介紹了Android短信的發(fā)送和廣播接收實現(xiàn)短信的監(jiān)聽,可以實現(xiàn)短信收發(fā),有興趣的可以了解一下。2016-11-11
Android 利用反射+try catch實現(xiàn)sdk按需引入依賴庫的方法
這篇文章主要介紹了Android 利用反射+try catch來實現(xiàn)sdk按需引入依賴庫,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
Android編程中File文件常見存儲與讀取操作demo示例
這篇文章主要介紹了Android編程中File文件常見存儲與讀取操作,結(jié)合實例形式分析了Android針對文件的打開、讀寫及布局等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09

