我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android开发之经典游戏贪吃蛇

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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开发有所帮助。     

您可能感兴趣的文章:Android仿开心消消乐大树星星无限循环效果Android游戏源码分享之2048Unity3D游戏引擎实现在Android中打开WebView的实例Android游戏开发实践之人物移动地图的平滑滚动处理Android 游戏开发之Canvas画布的介绍及方法Android游戏开发之碰撞检测(矩形碰撞、圆形碰撞、像素碰撞)Android五子棋游戏程序完整实例分析以一个着色游戏展开讲解Android中区域图像填色的方法Android高仿2048小游戏实现代码Android开心消消乐代码实例详解


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android开发之经典游戏贪吃蛇

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android开发之经典游戏贪吃蛇

前言 这款游戏实现的思路和源码参考了Google自带的Snake的例子,其中修改了一些个人认为还不够完善的地方,加入了一些新的功能,比如屏幕上的方向操作盘,暂停按钮,开始按钮,退出按钮。另外,为了稍微增加些用户体验,除了游戏的主界面,本人自
2022-06-06

C#游戏开发之实现贪吃蛇游戏

这篇文章主要为大家详细介绍了C#如何实现经典贪吃蛇游戏,文中的示例代码讲解详细,对我们学习C#有一定的帮助,感兴趣的小伙伴可以跟随小编一起了解一下
2023-01-04

如何用Silverlight开发贪吃蛇游戏

今天就跟大家聊聊有关如何用Silverlight开发贪吃蛇游戏,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。介绍使用 Silverlight 3.0(c#) 开发一个贪吃蛇游戏玩法W
2023-06-17

怎样用python打造最经典的贪吃蛇游戏

这篇文章将为大家详细讲解有关怎样用python打造最经典的贪吃蛇游戏,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。前言今天为大家介绍一个用Python开发的经典游戏|贪吃蛇,只需要短短的30
2023-06-02

C语言实现贪吃蛇小游戏开发

这篇文章主要为大家详细介绍了C语言实现贪吃蛇小游戏开发,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
2022-11-13

python实战之利用pygame实现贪吃蛇游戏(一)

目录一、前言二、搭建界面三、运行结果四、结语一、前言 之前尝试了自己用pygame写井字棋,这次玩的是贪吃蛇系列。 个人感觉模块可能会比较大,所以选择将函数和主要逻辑代码分在了两个文件中。fuc为函数模块,存储了事件感应和刷新界面等部分。
2022-06-02

贪食蛇小游戏开发设计基础教程

编程学习网: 贪吃蛇是家喻户晓的益智类小游戏,大家小时候应该都有玩过,编程学习网这里就不多介绍了,本教程将教你用MicrosoftVisualC++来制作它。
贪食蛇小游戏开发设计基础教程
2024-04-23

Java GUI编程之贪吃蛇游戏简单实现方法【附demo源码下载】

本文实例讲述了Java GUI编程之贪吃蛇游戏简单实现方法。分享给大家供大家参考,具体如下:例子简单,界面简陋 请见谅项目结构如下Constant.jvava 代码如下:package snake;/** * * @author hjn *
2023-05-31

Android 之游戏开发流程

游戏开发流程在不同的项目中会有一些差异,但是一般来说,Android 游戏开发流程可以大致分为以下几个步骤:1. 设计阶段:在这个阶段,你需要确定游戏的类型,玩法以及整体的游戏设计。你可以创建一个游戏概念,制定游戏规则,设计游戏界面和角色等
2023-09-28

Android游戏开发之黑白棋

黑白棋介绍 黑白棋,又叫苹果棋,最早流行于西方国家。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。黑白棋非常易于上手,但精通则需要考虑许多因素,比如角边这样的特殊位置、稳定度、行动力等。本游戏取名为黑白棋大师,提供了8种难度
2022-06-06

Android实现疯狂连连看游戏之开发游戏界面(二)

连连看的游戏界面十分简单,大致可以分为两个区域: --游戏主界面区 --控制按钮和数据显示区1、开发界面布局本程序使用一个RelativeLayout作为整体的界面布局元素,界面布局上面是一个自定义组件,下面是一个水平排列的LinearLa
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录