Android开发之经典游戏贪吃蛇

前言

这款游戏实现的思路和源码参考了Google自带的Snake的例子,其中修改了一些个人认为还不够完善的地方,加入了一些新的功能,比如屏幕上的方向操作盘,暂停按钮,开始按钮,退出按钮。另外,为了稍微增加些用户体验,除了游戏的主界面,本人自己新增了5个界面,分别是登陆界面,菜单界面,背景音乐设置界面,难度设置界面,还有个关于游戏的介绍界面。个人觉得在新手阶段,参考现成的思路和实现方式是难以避免的。重要的是我们需要有自己的理解,读懂代码之后,需要思考代码背后的实现逻辑,形成自己的思维。这样在下次开发工作时,就不用参考别人自己也能涌现出解决的思路。

我觉得经过自己的构思和实践,做出一个可操作有界面的小作品还是挺有成就感的,在探索和思考的过程中时间过的很快。好了,下面切入正题,我考虑了下讲述的顺序,决定就以进入软件后的界面顺序来把。

由于篇幅的关系,布局的XML文件就不发了,而且我把导包的语句也省略了,反正像AS,eclipse这些工具都是可以智能导包的。

那么,首先是登陆界面,找了些网上的资源当背景。布局还是比较简单的。

下图中,上图为效果图,下图为逻辑实现的流程图。

[java] view plain copy
// MainActivity.java
package con.example.wang.game;
public class MainActivity extends Activity implements OnClickListener{
  Button button;
  EditText edit1,edit2;
  CheckBox checkbox;
  ProgressBar bar;
  SharedPreferences pref;
  SharedPreferences.Editor editor;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    button=(Button) findViewById(R.id.login_button);
    edit1=(EditText) findViewById(R.id.input1);
    edit2=(EditText) findViewById(R.id.input2);
    checkbox=(CheckBox) findViewById(R.id.remember_button);
    bar=(ProgressBar) findViewById(R.id.progress);
    pref= PreferenceManager.getDefaultSharedPreferences(this);
    boolean isRemember=pref.getBoolean("rem",false);   //获取代表是否保存密码的变量值,这里初值设为false 

    if(isRemember) {
      //如果记住密码,则将账号和密码自动填充到文本框中
      String account=pref.getString("account","");
      String password=pref.getString("password","");
      edit1.setText(account);
      edit2.setText(password);
      checkbox.setChecked(true);
    }
    button.setOnClickListener(this);
  }
  @Override
  public void onClick(View v){
    new Thread(new Runnable(){   //开启线程运行进度条,减少主线程的压力,这里不用子线程也影响不大
      @Override
      public void run() {
        for (int i = 0; i < 25; i++) {
          int progress = bar.getProgress();
          progress = progress + 10;
          bar.setProgress(progress);
        }
      }
    }).start(); 

    String account=edit1.getText().toString();
    String password=edit2.getText().toString();
    if(account.equals("admin") && password.equals("123456")) {
      editor = pref.edit();  //这个方法用于向SharedPreferences文件中写数据
      if(checkbox.isChecked()) {
        editor.putBoolean("rem",true);
        editor.putString("account",account);
        editor.putString("password",password);
      }
      else {
        editor.clear();
      }
      editor.commit();  //这个方法必须要有,不然数据不会被保存。生效后,就可以从该文件中读取数据。
      Intent intent=new Intent(MainActivity.this,SecondActivity.class);
      startActivity(intent);
    }
    else{  //如果用户名或密码不正确,这里会弹出一个提示框
      Toast.makeText(MainActivity.this,"账号或用户名错误",Toast.LENGTH_SHORT).show();
    }
  }
} 

这个逻辑还算比较简单,实现了记住密码的功能,这里的数据存储使用的是SharedPreferences。点击登陆后,会进入一个菜单界面,这里设置几个四个按钮,分别做好监听就可以了,然后用Intent在活动间跳转就好了。

效果图也分享一下。

[java] view plain copy
// SecondActivity.java
package com.example.wang.game;
public class SecondActivity extends Activity implements OnClickListener{ 

  ImageButton button1,button2,button3,button4;
  @Override
  protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    button1=(ImageButton) findViewById(R.id.button_start);
    button2=(ImageButton) findViewById(R.id.button_difficulty);
    button3=(ImageButton) findViewById(R.id.button_music);
    button4=(ImageButton) findViewById(R.id.button_about);
    button4.setOnClickListener(this);
    button3.setOnClickListener(this);
    button2.setOnClickListener(this);
    button1.setOnClickListener(this);
  }
  @Override
  public void onClick(View v){
    switch(v.getId()) {    //看下Intent的用法,还是挺方便的,这里用的都是显式的方法
      case R.id.button_about:
        Intent intent1 = new Intent(SecondActivity.this, AboutActivity.class);
        startActivity(intent1);
        break;
      case R.id.button_music:
        Intent intent2 = new Intent(SecondActivity.this, MusicActivity.class);
        startActivity(intent2);
        break;
      case R.id.button_difficulty:
        Intent intent3 = new Intent(SecondActivity.this, DifficultyActivity.class);
        startActivity(intent3);
        break;
      case R.id.button_start:
        Intent intent4 = new Intent(SecondActivity.this, GameActivity.class);
        startActivity(intent4);
        break;
      default:
        break;
    }
  }
} 

下面先讲难度设置界面吧,这个和背景音乐开关其实差不多,所以以此为例,背景音乐开关界面就不啰嗦了。这里也是用的SharedPreferences存储数据。这里布局文件里把三个RadioButton放入RadioGroup,实现单选的效果。给三个按钮设置监听,触发事件后分别返回对应的三个变量,这三个变量控制的是贪吃蛇运行的速度。

参考下流程图更好理解。

[java] view plain copy
// DifficultyActivity.java
package com.example.wang.game;
public class DifficultyActivity extends Activity implements OnClickListener{
  private SharedPreferences saved;
  private SharedPreferences.Editor editor; 

  RadioButton button_jiandan,button_yiban,button_kunnan;
  @Override
  protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_difficulty);
    saved = PreferenceManager.getDefaultSharedPreferences(this);
    int level = saved.getInt("nandu",500); 

    button_jiandan = (RadioButton) findViewById(R.id.button_difficulty1);
    button_yiban = (RadioButton) findViewById(R.id.button_difficulty2);
    button_kunnan = (RadioButton) findViewById(R.id.button_difficulty3);
    button_jiandan.setOnClickListener(this);
    button_yiban.setOnClickListener(this);
    button_kunnan.setOnClickListener(this);
  }
  @Override
  public void onClick(View v){
    editor=saved.edit();
    switch(v.getId()){
      case R.id.button_difficulty1:
        if(button_jiandan.isChecked()){
          editor.putInt("nandu",500);
        }
        break;
      case R.id.button_difficulty2:
        if(button_yiban.isChecked()){
          editor.putInt("nandu",200);
        }
        break;
      case R.id.button_difficulty3:
        if(button_kunnan.isChecked()){
          editor.putInt("nandu",100);
        }
        break;
    }
    editor.commit();
  }
} 

其它的两个辅助界面比较简单,背景音乐开关界面也是通过SharedPreferences文件存储一个boolean的值,true的话就播放音乐,false就不播放。关于游戏的介绍界面就加一些文字。上述这些都是辅助,下面是游戏的主体部分。

游戏界面的设计思路就是将手机屏幕分为多行多列的像素块,以像素块为最小单位,确定各点的坐标。这里每个像素块的大小设置为32像素。我的手机模拟器的屏幕分辨率为768*1280,由公式可算出,我的游戏界面x轴上坐标最大为24,y轴上坐标最大为35。坐标完成后,这里会使用三种颜色不同的图片来填充像素块,这里就叫砖块把。

根据java面向对象的逻辑,需要给各块内容分类,蛇,苹果,边界的墙都是必不可少的元素。视图的初始化也是围绕着这三个元素展开的。其实这里蛇,苹果和边界墙就是由不同颜色的砖块表示出来的。

该部分内容包含三个java文件,首先是砖块的初始化。

[java] view plain copy
// TileView.java
package com.example.wang.game;
public class TileView extends View {
  public static int mTileSize =32;
  public static int mXTileCount; //地图上所能容纳的格数
  public static int mYTileCount;
  public static int mXOffset;   //偏移量
  public static int mYOffset;
  Bitmap[] mTileArray;      //放置图片的数组
  int[][] mTileGrid;       //存放各坐标对应的图片 

  public TileView(Context context, AttributeSet attrs,int defStyle){
    super(context,attrs,defStyle);
  }
  public TileView(Context context, AttributeSet attrs){
    super(context,attrs);
  }
  public TileView(Context context){
    super(context);
  }
  //加载三幅小图片
  public void loadTile(int key, Drawable tile) {
    Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    tile.setBounds(0, 0, mTileSize, mTileSize);
    tile.draw(canvas);
    mTileArray[key] = bitmap;
  }
  //给地图数组赋值
  public void setTile(int tileindex, int x, int y) {
    mTileGrid[x][y] = tileindex;
  }
  public void resetTiles(int tilecount) {
    mTileArray = new Bitmap[tilecount];
  }
  //我的游戏界面不是占满整个屏幕,所以地图遍历的时候,y不是从0开始
  public void clearTiles() {
    for (int x = 0; x < mXTileCount; x++) {
      for (int y = 2; y < mYTileCount-8; y++) {
        setTile(0, x, y);
      }
    }
  }
  //计算当前屏幕在X,Y轴上分别所能容纳的最大砖块数量
  //这里输出</span><span style="font-size:14px;">“mXTileCount"和”mYTileCount"的值后面会用到
  @Override
  public void onSizeChanged(int w, int h, int oldw, int oldh){
    //地图数组初始化
    mXTileCount = (int) Math.floor(w / mTileSize);
    mYTileCount = (int) Math.floor(h / mTileSize);
//    System.out.println("-------"+mXTileCount+"----------");
//    System.out.println("-------"+mYTileCount+"----------");
    //可能屏幕的长宽不能整除,所以够分成一格的分成一格, 剩下不够一格的分成两份,左边一份,右边一份
    mXOffset = ((w - (mTileSize * mXTileCount)) / 2);
    mYOffset = ((h - (mTileSize * mYTileCount)) / 2);
//    System.out.println("-------"+mXOffset+"----------");
//    System.out.println("-------"+mYOffset+"----------");
    mTileGrid = new int[mXTileCount][mYTileCount];
    clearTiles();
  }
  @Override
  public void onDraw(Canvas canvas){
    super.onDraw(canvas);
  }
} 

其实上述这段程序就是实现了几个方法,loadTile()用于加载图片,setTile()resetTile()是把图片与坐标联系起来,而onSizedChanged()是把手机屏幕像素块化。这些方法都将为以下这个类服务。为了便于利用这些方法,以下这个类继承自TileView

由于我加了一些按钮,所以游戏界面生成时没有占满整个屏幕,所以在设置坐标和遍历地图时与源码的数据相差较多。

[java] view plain copy
// SnakeView.java
package com.example.wang.game;
public class SnakeView extends TileView{ 

  static int mMoveDelay = 500;
  private long mLastMove; 

  private static final int RED_STAR = 1;
  private static final int YELLOW_STAR = 2;
  private static final int GREEN_STAR = 3; 

  private static final int UP = 1;
  private static final int DOWN = 2;
  private static final int RIGHT = 3;
  private static final int LEFT = 4;
  static int mDirection = RIGHT;
  static int mNextDirection = RIGHT;
  // 这里把游戏界面分为5种状态,便于逻辑实现
  public static final int PAUSE = 0;
  public static final int READY = 1;
  public static final int RUNNING = 2;
  public static final int LOSE = 3;
  public static final int QUIT = 4;
  public int mMode = READY;
  public int newMode; 

  private TextView mStatusText;  // 用于每个状态下的文字提醒
  public long mScore = 0; 

  private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>(); // 存储蛇的所有坐标的数组
  private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>(); // 存储苹果的所有坐标的数组 

  private static final Random RNG = new Random();  //用于生成苹果坐标的随机数
//  private static final String TAG = "SnakeView"; 

  //开启线程,不断调用更新和重绘。这里利用了Handler类来实现异步消息处理机制
  MyHandler handler=new MyHandler();
  class MyHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
      SnakeView.this.update();     //不断调用update()方法
      SnakeView.this.invalidate();  //请求重绘,不断调用ondraw()方法
    }
    //调用sleep后,在一段时间后再sendmessage进行UI更新
    public void sleep(int delayMillis) {
      this.removeMessages(0);     //清空消息队列
      sendMessageDelayed(obtainMessage(0), delayMillis);
    }
  }
  //这是三个构造方法,别忘了加上下面这个初始化方法
  public SnakeView(Context context, AttributeSet attrs, int defStyle){
    super(context,attrs,defStyle);
    initNewGame();
  }
  public SnakeView(Context context, AttributeSet attrs){
    super(context,attrs);
    setFocusable(true);
    initNewGame();
  }
  public SnakeView(Context context){
    super(context);
  }
  //添加苹果的方法,最后将生成的苹果坐标存储在上面定义的数组中
  private void addRandomApple() {
    Coordinate newCoord = null;
    boolean found = false;
    while (!found) {
      // 这里设定了苹果坐标能随机生成的范围,并生成随机坐标。这里Google源码中是直接使用变量
      // mXTileCount和mYTileCount,我编译时会报错,因为随机数不能生成负数,而直接使用这两个变量程序不能
      // 识别这个变量减去一个数后是否会是负数,所以我把TileView里输出的确切值放了进去
      int newX = 1 + RNG.nextInt(24-2);
      int newY = 3 + RNG.nextInt(35-12);
      newCoord = new Coordinate(newX, newY); 

      boolean collision = false;
      int snakelength = mSnakeTrail.size();
      //遍历snake, 看新添加的apple是否在snake体内, 如果是,重新生成坐标
      for (int index = 0; index < snakelength; index++) {
        if (mSnakeTrail.get(index).equals(newCoord)) {
          collision = true;
        }
      }
      found = !collision;
    }
//    if (newCoord == null) {
//      Log.e(TAG, "Somehow ended up with a null newCoord!");
//    }
    mAppleList.add(newCoord);
  } 

  //绘制边界的墙
  private void updateWalls() {
    for (int x = 0; x < mXTileCount; x++) {
      setTile(GREEN_STAR, x, 2);
      setTile(GREEN_STAR, x, mYTileCount - 8);
    }
    for (int y = 2; y < mYTileCount - 8; y++) {
      setTile(GREEN_STAR, 0, y);
      setTile(GREEN_STAR, mXTileCount - 1, y);
    }
  }
  //更新蛇的运动轨迹
  private void updateSnake(){
    boolean growSnake = false;
    Coordinate head = mSnakeTrail.get(0);
    Coordinate newHead = new Coordinate(1, 1); 

    mDirection = mNextDirection;
    switch (mDirection) {
      case RIGHT: {
        newHead = new Coordinate(head.x + 1, head.y);
        break;
      }
      case LEFT: {
        newHead = new Coordinate(head.x - 1, head.y);
        break;
      }
      case UP: {
        newHead = new Coordinate(head.x, head.y - 1);
        break;
      }
      case DOWN: {
        newHead = new Coordinate(head.x, head.y + 1);
        break;
      }
    }
    //检测是否撞墙
    if ((newHead.x < 1) || (newHead.y < 3) || (newHead.x > mXTileCount - 2)
        || (newHead.y > mYTileCount - 9)) {
      setMode(LOSE);
      return;
    }
    //检测蛇头是否撞到自己
    int snakelength = mSnakeTrail.size();
    for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
      Coordinate c = mSnakeTrail.get(snakeindex);
      if (c.equals(newHead)) {
        setMode(LOSE);
        return;
      }
    }
    //检测蛇是否吃到苹果
    int applecount = mAppleList.size();
    for (int appleindex = 0; appleindex < applecount; appleindex++) {
      Coordinate c = mAppleList.get(appleindex);
      if (c.equals(newHead)) {
        mAppleList.remove(c);
        addRandomApple();
        mScore++;
        mMoveDelay *= 0.95;  //蛇每迟到一个苹果,延时就会减少,蛇的速度就会加快
        growSnake = true;
      }
    }
    mSnakeTrail.add(0,newHead);
    if(!growSnake) {
      mSnakeTrail.remove(mSnakeTrail.size() - 1);
    }
    //蛇头和蛇身分别设置不同的图片
    int index=0;
    for(Coordinate c:mSnakeTrail) {
      if(index == 0) {
        setTile(RED_STAR, c.x, c.y);
      } else {
        setTile(YELLOW_STAR,c.x,c.y);
      }
      index++;
    }
  }
  给苹果加载对应的图片
  private void updateApples() {
    for (Coordinate c : mAppleList) {
      setTile(YELLOW_STAR, c.x, c.y);
    }
  }
  // 该方法很重要,用于更新蛇,苹果和墙的坐标
  // 这里设置了更新的时间间隔,我发现不加这个延时的话蛇运动时容易出现一下跳很多格的情况
   public void update(){
    if(mMode == RUNNING) {
      long now = System.currentTimeMillis();
      if (now - mLastMove > mMoveDelay) {
        clearTiles();
        updateWalls();
        updateSnake();
        updateApples();
        mLastMove = now;
      }
      handler.sleep(mMoveDelay);
    }
  } 

  //图像初始化,引入图片资源
  private void initSnakeView() {
    setFocusable(true);   //添加焦点
    Resources r = this.getContext().getResources();
    //添加几种不同的tile
    resetTiles(4);
    //从文件中加载图片
    loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
    loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
    loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));
    update();
  }
  // 数据初始化方法,定义蛇的起始坐标,运动方向和得分变量。给数组添加坐标的时候注意顺序,因为有蛇头和蛇尾的区别
  public void initNewGame() {
    mSnakeTrail.clear();
    mAppleList.clear();
    //snake初始状态时的个数和位置,方向
    mSnakeTrail.add(new Coordinate(8, 7));
    mSnakeTrail.add(new Coordinate(7, 7));
    mSnakeTrail.add(new Coordinate(6, 7));
    mSnakeTrail.add(new Coordinate(5, 7));
    mSnakeTrail.add(new Coordinate(4, 7));
    mSnakeTrail.add(new Coordinate(3, 7));
    mDirection = RIGHT;
    mNextDirection = RIGHT; // 这个变量必须初始化,不然每次游戏结束重新开始后,蛇初始的方向将不是向右,而是你游戏结束时蛇的方向,
                           // 如果死的时候,蛇的方向向左,那么再次点击开始时会无法绘出蛇的图像
    addRandomApple();
    mScore=0;
  }
  // 根据各个数组中的数据,遍历地图设置各点的图片
  public void onDraw(Canvas canvas){
    super.onDraw(canvas);
    Paint paint=new Paint();
    initSnakeView();
    //遍历地图绘制界面
    for (int x = 0; x < mXTileCount; x++) {
      for (int y = 0; y < mYTileCount; y++) {
        if (mTileGrid[x][y] > 0) {  // 被加了图片的点mTileGird是大于0的
          canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x * mTileSize, mYOffset + y * mTileSize, paint);
        }
      }
    }
  } 

  //把蛇和苹果各点对应的坐标利用一个一维数组储存起来
  private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {
    int count = cvec.size();
    int[] rawArray = new int[count * 2];
    for (int index = 0; index < count; index++) {
      Coordinate c = cvec.get(index);
      rawArray[2 * index] = c.x;
      rawArray[2 * index + 1] = c.y;
    }
    return rawArray;
  } 

  //将当前所有的游戏数据全部保存
  public Bundle saveState() {
    Bundle map = new Bundle();
    map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
    map.putInt("mDirection", Integer.valueOf(mDirection));
    map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
    map.putInt("mMoveDelay", Integer.valueOf(mMoveDelay));
    map.putLong("mScore", Long.valueOf(mScore));
    map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));
    return map;
  }
  //是coordArrayListToArray()的逆过程,用来读取数组中的坐标数据
  private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {
    ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();
    int coordCount = rawArray.length;
    for (int index = 0; index < coordCount; index += 2) {
      Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
      coordArrayList.add(c);
    }
    return coordArrayList;
  }
  //saveState()的逆过程,用于恢复游戏数据
  public void restoreState(Bundle icicle) {
    setMode(PAUSE);
    mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
    mDirection = icicle.getInt("mDirection");
    mNextDirection = icicle.getInt("mNextDirection");
    mMoveDelay = icicle.getInt("mMoveDelay");
    mScore = icicle.getLong("mScore");
    mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
  }
  // 设置键盘监听,在模拟器中可以使用电脑键盘控制蛇的方向
  public boolean onKeyDown(int keyCode, KeyEvent event){
    if(keyCode == KeyEvent.KEYCODE_DPAD_UP){
      if(mDirection != DOWN) {
        mNextDirection = UP;
      }
      return (true);
    }
    if(keyCode == KeyEvent.KEYCODE_DPAD_DOWN){
      if(mDirection != UP) {
        mNextDirection = DOWN;
      }
      return (true);
    }
    if(keyCode == KeyEvent.KEYCODE_DPAD_RIGHT){
      if(mDirection != LEFT) {
        mNextDirection = RIGHT;
      }
      return (true);
    }
    if(keyCode == KeyEvent.KEYCODE_DPAD_LEFT){
      if(mDirection != RIGHT) {
        mNextDirection = LEFT;
      }
      return (true);
    }
    return super.onKeyDown(keyCode,event);
  } 

  public void setTextView(TextView newView) {
    mStatusText = newView;
  }
  // 设置不同状态下提示文字的显示内容和可见状态
  public void setMode(int newMode) {
    this.newMode=newMode;
    int oldMode = mMode;
    mMode = newMode;
    if (newMode == RUNNING & oldMode != RUNNING) {
      mStatusText.setVisibility(View.INVISIBLE);
      update();
      return;
    }
    // 这里定义了一个空字符串,用于放入各个状态下的提醒文字
    Resources res = getContext().getResources();
    CharSequence str = "";
    if (newMode == PAUSE) {
      str = res.getText(R.string.mode_pause);
    }
    if (newMode == READY) {
      str = res.getText(R.string.mode_ready);
    }
    if (newMode == LOSE) {
      str = res.getString(R.string.mode_lose_prefix) + mScore
          + res.getString(R.string.mode_lose_suffix);
    }
    if (newMode == QUIT){
      str = res.getText(R.string.mode_quit);
    }
    mStatusText.setText(str);
    mStatusText.setVisibility(View.VISIBLE);
  } 

  //记录坐标位置
  private class Coordinate {
    public int x;
    public int y;
    public Coordinate(int newX, int newY) {
      x = newX;
      y = newY;
    }
    //触碰检测,看蛇是否吃到苹果
    public boolean equals(Coordinate other) {
      if (x == other.x && y == other.y) {
        return true;
      }
      return false;
    }
    // 这个方法没研究过起什么作用,我注释掉对程序的运行没有影响
    @Override
    public String toString() {
      return "Coordinate: [" + x + "," + y + "]";
    }
  }
} 

以上是自定义View的实现,实现了绘制游戏界面的主逻辑。将SnakeView作为一个UI控件插入到该界面的布局中。

下面开启活动,显示界面。

[java] view plain copy
// GameActivity.java
package com.example.wang.game;
public class GameActivity extends Activity implements OnClickListener{
  private SharedPreferences saved;
  private static String ICICLE_KEY = "snake-view";  // 个人认为这个变量就是一个中间值,在该类的最后一个方法中传入该变量,完成操作。
  private SnakeView mSnakeView;
  private ImageButton change_stop,change_start,change_quit;
  private ImageButton mLeft;
  private ImageButton mRight;
  private ImageButton mUp;
  private ImageButton mDown;
  private static final int UP = 1;
  private static final int DOWN = 2;
  private static final int RIGHT = 3;
  private static final int LEFT = 4; 

  @Override
  protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_game);
    mSnakeView = (SnakeView) findViewById(R.id.snake); //给自定义View实例化,把这个布局当一个UI控件一样插入进来
    mSnakeView.setTextView((TextView) findViewById(R.id.text_show)); 

    change_stop = (ImageButton) findViewById(R.id.game_stop);
    change_start = (ImageButton) findViewById(R.id.game_start);
    change_quit = (ImageButton) findViewById(R.id.game_quit); 

    mLeft = (ImageButton) findViewById(R.id.left);
    mRight = (ImageButton) findViewById(R.id.right);
    mUp = (ImageButton) findViewById(R.id.up);
    mDown = (ImageButton) findViewById(R.id.down); 

    change_start = (ImageButton) findViewById(R.id.game_start);
    change_stop = (ImageButton) findViewById(R.id.game_stop);
    change_quit = (ImageButton) findViewById(R.id.game_quit); 

    saved = PreferenceManager.getDefaultSharedPreferences(this);
    boolean playMusic = saved.getBoolean("ifon" ,true);   // 获取背景音乐开关的状态变量,在设置开关界面存储,在这里读取
    if(playMusic) {  // 如果设置背景音乐打开,则开启服务,播放音乐
      Intent intent_service = new Intent(GameActivity.this, MusicService.class);
      startService(intent_service);
    }
    SnakeView.mMoveDelay=saved.getInt("nandu",500);   // 获取当前设置的代表游戏难度的变量,在难度设置界面保存,在这里读取 

    // 判断是否有保存数据,如果数据为空就准备重新开始游戏
    if (savedInstanceState == null) {
      mSnakeView.setMode(SnakeView.READY);
    } else {
      // 暂停后的恢复
      Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
      if (map != null) {
        mSnakeView.restoreState(map);
      } else {
        mSnakeView.setMode(SnakeView.PAUSE);
      }
    }
    mDown.setOnClickListener(this);
    mUp.setOnClickListener(this);
    mRight.setOnClickListener(this);
    mLeft.setOnClickListener(this);
    change_start.setOnClickListener(this);
    change_stop.setOnClickListener(this);
    change_quit.setOnClickListener(this);
  }
  @Override
  public void onDestroy(){
    super.onDestroy();
    saved = PreferenceManager.getDefaultSharedPreferences(this);
    boolean playMusic = saved.getBoolean("ifon" ,true);
    if(playMusic) {
      Intent intent_service = new Intent(GameActivity.this, MusicService.class);
      stopService(intent_service);
    } 

  }
  // 给开始,暂停,退出,上下左右按钮设置监听。根据当前的状态来决定界面的更新操作
  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.game_start:
         // 重新开始游戏,这里延时变量必须初始化,不然每次游戏重新开始之后,蛇的运动速度不会初始化
        if ( mSnakeView.mMode == SnakeView.READY || mSnakeView.mMode == SnakeView.LOSE) {
          SnakeView.mMoveDelay=saved.getInt("nandu",500);
          mSnakeView.initNewGame();
          mSnakeView.setMode(SnakeView.RUNNING);
          mSnakeView.update();
        }
        // 暂停后开始游戏,继续暂停前的界面
        if ( mSnakeView.mMode == SnakeView.PAUSE) {
          mSnakeView.setMode(SnakeView.RUNNING);
          mSnakeView.update();
        }
        break;
      case R.id.game_stop:  // 暂停
        if(mSnakeView.mMode == SnakeView.RUNNING) {
          mSnakeView.setMode(SnakeView.PAUSE);
        }
        break;
      case R.id.game_quit:  // 退出,返回菜单界面
        mSnakeView.setMode(SnakeView.QUIT);
        finish();
        break;
      // 使界面上的方向按钮起作用
      case R.id.left:
        if (SnakeView.mDirection != RIGHT) {
          SnakeView.mNextDirection = LEFT;
        }
        break;
      case R.id.right:
        if (SnakeView.mDirection != LEFT) {
          SnakeView.mNextDirection = RIGHT;
        }
        break;
      case R.id.up:
        if (SnakeView.mDirection != DOWN) {
          SnakeView.mNextDirection = UP;
        }
        break;
      case R.id.down:
        if (SnakeView.mDirection != UP) {
          SnakeView.mNextDirection = DOWN;
        }
        break;
      default:
        break;
    }
  } 

  @Override
  protected void onPause() {
    super.onPause();
    mSnakeView.setMode(SnakeView.PAUSE);
  } 

  @Override
  public void onSaveInstanceState(Bundle outState) {
    //保存游戏状态
    outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
  }
} 

下面是游戏效果图,运行界面和暂停界面。我把逻辑流程图也贴出来,有什么问题大家可以留言,多多交流!

本文通过带大家回忆经典游戏的同时,学习了利用java开发Android游戏——贪吃蛇,希望本文在大家学习Android开发有所帮助。

(0)

相关推荐

  • Android游戏开发实践之人物移动地图的平滑滚动处理

    如图所示为程序效果动画图 地图滚动的原理 在本人之前博客的文章中介绍过人物在屏幕中的移动方式,因为之前拼的游戏地图是完全填充整个手机屏幕的,所以无需处理地图的平滑滚动.这篇文章我着重的向 大家介绍一下控制人物移动后地图滚动的处理方式.举个例子 如上图所示 比如人物向右移动,如果地图贴在屏幕左边边界 将先移动人物在地图的坐标,当人物在屏幕中超过三分之二后 则将地图向人物行走的反方向移动给玩家一种人物还在向右移动的假象,其实这时候人物只是播放向右行走的动画 在屏幕中的坐标不变 ,当地图向人物行走反方

  • Android 游戏引擎libgdx 资源加载进度百分比显示案例分析

    因为案例比较简单,所以简单用AndroidApplication -> Game -> Stage 搭建框架 一.主入口,无特殊 复制代码 代码如下: public class App extends AndroidApplication { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //初始化Demo initialize(new Demo()

  • Android游戏开发学习①弹跳小球实现方法

    本文实例讲述了Android游戏开发学习①弹跳小球实现方法.分享给大家供大家参考.具体如下: 在学习了一点点Android之后,觉得有必要记录下来,于是就开了这个新坑,慢慢来填吧. 1.运动体Movable类 本例主要模拟了一组大小不一的球以一定的水平初速度从高处落下的运动轨迹.其中的小球为一个可移动物体Movable对象,该类中除了包含小球图片对象之外,还包括了如位置坐标.水平速度.垂直速度等一系列用于模拟小球运动的成员变量和一些方法. Movable类: package com.ball;

  • Android五子棋游戏程序完整实例分析

    最近学习了五子棋的课程,感觉挺不错.然后自己写了个关于五子棋的android程序,从中还是能够学习到很多东西的.现在我们开始今天五子棋程序的编写历程.程序的源码请参见友情链接: 好了,我们现在开始一步步的构建出项目来,首先是如下的项目结构图: 运行的效果图: 一些前期做准备的代码 1. 主活动类MainActivity,在菜单中加入了再来一局的功能: public class MainActivity extends AppCompatActivity { private ChessBoardV

  • Android实现疯狂连连看游戏之实现游戏逻辑(五)

    在上一篇<我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)>中提到的两个类: GameConf:负责管理游戏的初始化设置信息. GameService:负责游戏的逻辑实现. 其中GameConf的代码如下:cn\oyp\link\utils\GameConf.java package cn.oyp.link.utils; import android.content.Context; /** * 保存游戏配置的对象

  • 解析Android游戏中获取电话状态进行游戏暂停或继续的解决方法

    对智能手机有所了解的朋友都知道其中一个应用广泛的手机操作系统Android 开源手机操作系统.那么在这一系统中想要实现通话的监听功能的话,我们应当如何操作呢?在这里就为大家详细介绍了Android监听通话的相关实现方法. 开发应用程序的时候,我们希望能够监听电话的呼入,以便执行暂停音乐播放器等操作,当电话结束之后,再次恢复播放.在Android平台可以通过TelephonyManager和PhoneStateListener来完成此任务.TelephonyManager作为一个Service接口

  • Unity3D游戏引擎实现在Android中打开WebView的实例

    本文讲述了如何在Unity中调用Android中的WebView组件,实现内部浏览器样式的页面切换.首先打开Eclipse创建一个Android的工程: UnityTestActivity.java 入口Activity ,Unity中会调用这个Activity中的方法从而打开网页. package com.xys; import android.content.Context; import android.content.Intent; import android.os.Bundle; i

  • Android 游戏开发之Canvas画布的介绍及方法

    Canvas,在英语中,这个单词的意思是帆布.在Android中,则把Canvas当做画布,只要我们借助设置好的画笔(Paint类)就可以在画布上绘制我们想要的任何东西:另外它也是显示位图(Bitmap类)的核心类.随用户的喜好,Canvas还可设置一些关于画布的属性,比如,画布的颜色.尺寸等.Canvas提供了如下一些方法:    Canvas(): 创建一个空的画布,可以使用setBitmap()方法来设置绘制具体的画布.    Canvas(Bitmap bitmap): 以bitmap对

  • Android游戏源码分享之2048

    引言 程序猿们,是否还在为你的老板辛辛苦苦的打工而拿着微薄的薪水呢,还是不知道如何用自己的应用或游戏来赚钱呢! 在这里IQuick将教您如何同过自己的应用来赚取自己的第一桶金! 你是说自己的应用还没有做出来? 不,在這里已经为你提供好了一个完整的游戏应用了,在文章的下面有源码的地址哦.你只要稍做修改就可以变成一个完全属于自己的应用了,比如将4*4换成5*5,甚至是其它的.如果你实在是慵懒至极的话,你只要将本应用的包名及广告换成自己的,就可以上传到市场上轻轻松松赚取自己的第一桶金了. 如果你觉得本

  • android游戏载入的activity跳转到游戏主菜单的activity具体实现

    复制代码 代码如下: public class LoadActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏 getWindow().setFlags(WindowManager.Layou

随机推荐