欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android开发之经典游戏贪吃蛇

程序员文章站 2024-03-06 14:46:32
前言 这款游戏实现的思路和源码参考了google自带的snake的例子,其中修改了一些个人认为还不够完善的地方,加入了一些新的功能,比如屏幕上的方向操作盘,暂停按钮,开始...

前言

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

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

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

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

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

Android开发之经典游戏贪吃蛇

Android开发之经典游戏贪吃蛇

[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在活动间跳转就好了。

效果图也分享一下。

Android开发之经典游戏贪吃蛇

[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,实现单选的效果。给三个按钮设置监听,触发事件后分别返回对应的三个变量,这三个变量控制的是贪吃蛇运行的速度。

参考下流程图更好理解。

Android开发之经典游戏贪吃蛇

Android开发之经典游戏贪吃蛇

[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()); 
  } 
} 

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

Android开发之经典游戏贪吃蛇

Android开发之经典游戏贪吃蛇

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