Android 2d游戏开发之贪吃蛇基于surfaceview
前两个游戏是基于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;
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;
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;
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;
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;
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
就是刚开始介绍的那个类,这里就不重复贴出了
到此这篇关于Android 2d游戏开发之贪吃蛇基于surfaceview的文章就介绍到这了,更多相关Android 贪吃蛇内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341