亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android開(kāi)發(fā)之經(jīng)典游戲貪吃蛇

 更新時(shí)間:2016年07月28日 15:10:18   投稿:daisy  
貪吃蛇是一款足夠經(jīng)典的游戲。它的經(jīng)典,在于用戶(hù)操作的簡(jiǎn)單,在于技術(shù)實(shí)現(xiàn)的簡(jiǎn)介,在于他的經(jīng)久不衰。下面來(lái)看下如何在Android開(kāi)發(fā)這款經(jīng)典游戲。

前言

這款游戲?qū)崿F(xiàn)的思路和源碼參考了Google自帶的Snake的例子,其中修改了一些個(gè)人認(rèn)為還不夠完善的地方,加入了一些新的功能,比如屏幕上的方向操作盤(pán),暫停按鈕,開(kāi)始按鈕,退出按鈕。另外,為了稍微增加些用戶(hù)體驗(yàn),除了游戲的主界面,本人自己新增了5個(gè)界面,分別是登陸界面,菜單界面,背景音樂(lè)設(shè)置界面,難度設(shè)置界面,還有個(gè)關(guān)于游戲的介紹界面。個(gè)人覺(jué)得在新手階段,參考現(xiàn)成的思路和實(shí)現(xiàn)方式是難以避免的。重要的是我們需要有自己的理解,讀懂代碼之后,需要思考代碼背后的實(shí)現(xiàn)邏輯,形成自己的思維。這樣在下次開(kāi)發(fā)工作時(shí),就不用參考別人自己也能涌現(xiàn)出解決的思路。

我覺(jué)得經(jīng)過(guò)自己的構(gòu)思和實(shí)踐,做出一個(gè)可操作有界面的小作品還是挺有成就感的,在探索和思考的過(guò)程中時(shí)間過(guò)的很快。好了,下面切入正題,我考慮了下講述的順序,決定就以進(jìn)入軟件后的界面順序來(lái)把。

由于篇幅的關(guān)系,布局的XML文件就不發(fā)了,而且我把導(dǎo)包的語(yǔ)句也省略了,反正像AS,eclipse這些工具都是可以智能導(dǎo)包的。

那么,首先是登陸界面,找了些網(wǎng)上的資源當(dāng)背景。布局還是比較簡(jiǎn)單的。

下圖中,上圖為效果圖,下圖為邏輯實(shí)現(xiàn)的流程圖。

[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);   //獲取代表是否保存密碼的變量值,這里初值設(shè)為false 
 
    if(isRemember) { 
      //如果記住密碼,則將賬號(hào)和密碼自動(dòng)填充到文本框中 
      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(){   //開(kāi)啟線程運(yùn)行進(jìn)度條,減少主線程的壓力,這里不用子線程也影響不大 
      @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();  //這個(gè)方法用于向SharedPreferences文件中寫(xiě)數(shù)據(jù) 
      if(checkbox.isChecked()) { 
        editor.putBoolean("rem",true); 
        editor.putString("account",account); 
        editor.putString("password",password); 
      } 
      else { 
        editor.clear(); 
      } 
      editor.commit();  //這個(gè)方法必須要有,不然數(shù)據(jù)不會(huì)被保存。生效后,就可以從該文件中讀取數(shù)據(jù)。 
      Intent intent=new Intent(MainActivity.this,SecondActivity.class); 
      startActivity(intent); 
    } 
    else{  //如果用戶(hù)名或密碼不正確,這里會(huì)彈出一個(gè)提示框 
      Toast.makeText(MainActivity.this,"賬號(hào)或用戶(hù)名錯(cuò)誤",Toast.LENGTH_SHORT).show(); 
    } 
  } 
} 

這個(gè)邏輯還算比較簡(jiǎn)單,實(shí)現(xiàn)了記住密碼的功能,這里的數(shù)據(jù)存儲(chǔ)使用的是SharedPreferences。點(diǎn)擊登陸后,會(huì)進(jìn)入一個(gè)菜單界面,這里設(shè)置幾個(gè)四個(gè)按鈕,分別做好監(jiān)聽(tīng)就可以了,然后用Intent在活動(dòng)間跳轉(zhuǎn)就好了。

效果圖也分享一下。

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

下面先講難度設(shè)置界面吧,這個(gè)和背景音樂(lè)開(kāi)關(guān)其實(shí)差不多,所以以此為例,背景音樂(lè)開(kāi)關(guān)界面就不啰嗦了。這里也是用的SharedPreferences存儲(chǔ)數(shù)據(jù)。這里布局文件里把三個(gè)RadioButton放入RadioGroup,實(shí)現(xiàn)單選的效果。給三個(gè)按鈕設(shè)置監(jiān)聽(tīng),觸發(fā)事件后分別返回對(duì)應(yīng)的三個(gè)變量,這三個(gè)變量控制的是貪吃蛇運(yùn)行的速度。

參考下流程圖更好理解。

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

其它的兩個(gè)輔助界面比較簡(jiǎn)單,背景音樂(lè)開(kāi)關(guān)界面也是通過(guò)SharedPreferences文件存儲(chǔ)一個(gè)boolean的值,true的話就播放音樂(lè),false就不播放。關(guān)于游戲的介紹界面就加一些文字。上述這些都是輔助,下面是游戲的主體部分。

游戲界面的設(shè)計(jì)思路就是將手機(jī)屏幕分為多行多列的像素塊,以像素塊為最小單位,確定各點(diǎn)的坐標(biāo)。這里每個(gè)像素塊的大小設(shè)置為32像素。我的手機(jī)模擬器的屏幕分辨率為768*1280,由公式可算出,我的游戲界面x軸上坐標(biāo)最大為24,y軸上坐標(biāo)最大為35。坐標(biāo)完成后,這里會(huì)使用三種顏色不同的圖片來(lái)填充像素塊,這里就叫磚塊把。

根據(jù)java面向?qū)ο蟮倪壿?,需要給各塊內(nèi)容分類(lèi),蛇,蘋(píng)果,邊界的墻都是必不可少的元素。視圖的初始化也是圍繞著這三個(gè)元素展開(kāi)的。其實(shí)這里蛇,蘋(píng)果和邊界墻就是由不同顏色的磚塊表示出來(lái)的。

該部分內(nèi)容包含三個(gè)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; //地圖上所能容納的格數(shù) 
  public static int mYTileCount; 
  public static int mXOffset;   //偏移量 
  public static int mYOffset; 
  Bitmap[] mTileArray;      //放置圖片的數(shù)組 
  int[][] mTileGrid;       //存放各坐標(biāo)對(duì)應(yīng)的圖片 
 
  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; 
  } 
  //給地圖數(shù)組賦值 
  public void setTile(int tileindex, int x, int y) { 
    mTileGrid[x][y] = tileindex; 
  } 
  public void resetTiles(int tilecount) { 
    mTileArray = new Bitmap[tilecount]; 
  } 
  //我的游戲界面不是占滿(mǎn)整個(gè)屏幕,所以地圖遍歷的時(shí)候,y不是從0開(kāi)始 
  public void clearTiles() { 
    for (int x = 0; x < mXTileCount; x++) { 
      for (int y = 2; y < mYTileCount-8; y++) { 
        setTile(0, x, y); 
      } 
    } 
  } 
  //計(jì)算當(dāng)前屏幕在X,Y軸上分別所能容納的最大磚塊數(shù)量 
  //這里輸出</span><span style="font-size:14px;">“mXTileCount"和”mYTileCount"的值后面會(huì)用到 
  @Override 
  public void onSizeChanged(int w, int h, int oldw, int oldh){ 
    //地圖數(shù)組初始化 
    mXTileCount = (int) Math.floor(w / mTileSize); 
    mYTileCount = (int) Math.floor(h / mTileSize); 
//    System.out.println("-------"+mXTileCount+"----------"); 
//    System.out.println("-------"+mYTileCount+"----------"); 
    //可能屏幕的長(zhǎng)寬不能整除,所以夠分成一格的分成一格, 剩下不夠一格的分成兩份,左邊一份,右邊一份 
    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); 
  } 
} 

其實(shí)上述這段程序就是實(shí)現(xiàn)了幾個(gè)方法,loadTile()用于加載圖片,setTile()resetTile()是把圖片與坐標(biāo)聯(lián)系起來(lái),而onSizedChanged()是把手機(jī)屏幕像素塊化。這些方法都將為以下這個(gè)類(lèi)服務(wù)。為了便于利用這些方法,以下這個(gè)類(lèi)繼承自TileView。

由于我加了一些按鈕,所以游戲界面生成時(shí)沒(méi)有占滿(mǎn)整個(gè)屏幕,所以在設(shè)置坐標(biāo)和遍歷地圖時(shí)與源碼的數(shù)據(jù)相差較多。

[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種狀態(tài),便于邏輯實(shí)現(xiàn) 
  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;  // 用于每個(gè)狀態(tài)下的文字提醒 
  public long mScore = 0; 
 
  private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>(); // 存儲(chǔ)蛇的所有坐標(biāo)的數(shù)組 
  private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>(); // 存儲(chǔ)蘋(píng)果的所有坐標(biāo)的數(shù)組 
 
  private static final Random RNG = new Random();  //用于生成蘋(píng)果坐標(biāo)的隨機(jī)數(shù) 
//  private static final String TAG = "SnakeView"; 
 
  //開(kāi)啟線程,不斷調(diào)用更新和重繪。這里利用了Handler類(lèi)來(lái)實(shí)現(xiàn)異步消息處理機(jī)制 
  MyHandler handler=new MyHandler(); 
  class MyHandler extends Handler{ 
    @Override 
    public void handleMessage(Message msg) { 
      SnakeView.this.update();     //不斷調(diào)用update()方法 
      SnakeView.this.invalidate();  //請(qǐng)求重繪,不斷調(diào)用ondraw()方法 
    } 
    //調(diào)用sleep后,在一段時(shí)間后再sendmessage進(jìn)行UI更新 
    public void sleep(int delayMillis) { 
      this.removeMessages(0);     //清空消息隊(duì)列 
      sendMessageDelayed(obtainMessage(0), delayMillis); 
    } 
  } 
  //這是三個(gè)構(gòu)造方法,別忘了加上下面這個(gè)初始化方法 
  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); 
  } 
  //添加蘋(píng)果的方法,最后將生成的蘋(píng)果坐標(biāo)存儲(chǔ)在上面定義的數(shù)組中 
  private void addRandomApple() { 
    Coordinate newCoord = null; 
    boolean found = false; 
    while (!found) { 
      // 這里設(shè)定了蘋(píng)果坐標(biāo)能隨機(jī)生成的范圍,并生成隨機(jī)坐標(biāo)。這里Google源碼中是直接使用變量 
      // mXTileCount和mYTileCount,我編譯時(shí)會(huì)報(bào)錯(cuò),因?yàn)殡S機(jī)數(shù)不能生成負(fù)數(shù),而直接使用這兩個(gè)變量程序不能 
      // 識(shí)別這個(gè)變量減去一個(gè)數(shù)后是否會(huì)是負(fù)數(shù),所以我把TileView里輸出的確切值放了進(jìn)去 
      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體內(nèi), 如果是,重新生成坐標(biāo) 
      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); 
    } 
  } 
  //更新蛇的運(yùn)動(dòng)軌跡 
  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; 
      } 
    } 
    //檢測(cè)是否撞墻 
    if ((newHead.x < 1) || (newHead.y < 3) || (newHead.x > mXTileCount - 2) 
        || (newHead.y > mYTileCount - 9)) { 
      setMode(LOSE); 
      return; 
    } 
    //檢測(cè)蛇頭是否撞到自己 
    int snakelength = mSnakeTrail.size(); 
    for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) { 
      Coordinate c = mSnakeTrail.get(snakeindex); 
      if (c.equals(newHead)) { 
        setMode(LOSE); 
        return; 
      } 
    } 
    //檢測(cè)蛇是否吃到蘋(píng)果 
    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;  //蛇每遲到一個(gè)蘋(píng)果,延時(shí)就會(huì)減少,蛇的速度就會(huì)加快 
        growSnake = true; 
      } 
    } 
    mSnakeTrail.add(0,newHead); 
    if(!growSnake) { 
      mSnakeTrail.remove(mSnakeTrail.size() - 1); 
    } 
    //蛇頭和蛇身分別設(shè)置不同的圖片 
    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++; 
    } 
  } 
  給蘋(píng)果加載對(duì)應(yīng)的圖片 
  private void updateApples() { 
    for (Coordinate c : mAppleList) { 
      setTile(YELLOW_STAR, c.x, c.y); 
    } 
  } 
  // 該方法很重要,用于更新蛇,蘋(píng)果和墻的坐標(biāo) 
  // 這里設(shè)置了更新的時(shí)間間隔,我發(fā)現(xiàn)不加這個(gè)延時(shí)的話蛇運(yùn)動(dòng)時(shí)容易出現(xiàn)一下跳很多格的情況 
   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);   //添加焦點(diǎn) 
    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(); 
  } 
  // 數(shù)據(jù)初始化方法,定義蛇的起始坐標(biāo),運(yùn)動(dòng)方向和得分變量。給數(shù)組添加坐標(biāo)的時(shí)候注意順序,因?yàn)橛猩哳^和蛇尾的區(qū)別 
  public void initNewGame() { 
    mSnakeTrail.clear(); 
    mAppleList.clear(); 
    //snake初始狀態(tài)時(shí)的個(gè)數(shù)和位置,方向 
    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; // 這個(gè)變量必須初始化,不然每次游戲結(jié)束重新開(kāi)始后,蛇初始的方向?qū)⒉皇窍蛴?,而是你游戲結(jié)束時(shí)蛇的方向, 
                           // 如果死的時(shí)候,蛇的方向向左,那么再次點(diǎn)擊開(kāi)始時(shí)會(huì)無(wú)法繪出蛇的圖像 
    addRandomApple(); 
    mScore=0; 
  } 
  // 根據(jù)各個(gè)數(shù)組中的數(shù)據(jù),遍歷地圖設(shè)置各點(diǎn)的圖片 
  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) {  // 被加了圖片的點(diǎn)mTileGird是大于0的 
          canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x * mTileSize, mYOffset + y * mTileSize, paint); 
        } 
      } 
    } 
  } 
 
  //把蛇和蘋(píng)果各點(diǎn)對(duì)應(yīng)的坐標(biāo)利用一個(gè)一維數(shù)組儲(chǔ)存起來(lái) 
  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; 
  } 
 
  //將當(dāng)前所有的游戲數(shù)據(jù)全部保存 
  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()的逆過(guò)程,用來(lái)讀取數(shù)組中的坐標(biāo)數(shù)據(jù) 
  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()的逆過(guò)程,用于恢復(fù)游戲數(shù)據(jù) 
  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")); 
  } 
  // 設(shè)置鍵盤(pán)監(jiān)聽(tīng),在模擬器中可以使用電腦鍵盤(pán)控制蛇的方向 
  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; 
  } 
  // 設(shè)置不同狀態(tài)下提示文字的顯示內(nèi)容和可見(jiàn)狀態(tài) 
  public void setMode(int newMode) { 
    this.newMode=newMode; 
    int oldMode = mMode; 
    mMode = newMode; 
    if (newMode == RUNNING & oldMode != RUNNING) { 
      mStatusText.setVisibility(View.INVISIBLE); 
      update(); 
      return; 
    } 
    // 這里定義了一個(gè)空字符串,用于放入各個(gè)狀態(tài)下的提醒文字 
    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); 
  } 
 
  //記錄坐標(biāo)位置 
  private class Coordinate { 
    public int x; 
    public int y; 
    public Coordinate(int newX, int newY) { 
      x = newX; 
      y = newY; 
    } 
    //觸碰檢測(cè),看蛇是否吃到蘋(píng)果 
    public boolean equals(Coordinate other) { 
      if (x == other.x && y == other.y) { 
        return true; 
      } 
      return false; 
    } 
    // 這個(gè)方法沒(méi)研究過(guò)起什么作用,我注釋掉對(duì)程序的運(yùn)行沒(méi)有影響 
    @Override 
    public String toString() { 
      return "Coordinate: [" + x + "," + y + "]"; 
    } 
  } 
} 

以上是自定義View的實(shí)現(xiàn),實(shí)現(xiàn)了繪制游戲界面的主邏輯。將SnakeView作為一個(gè)UI控件插入到該界面的布局中。

下面開(kāi)啟活動(dòng),顯示界面。

[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";  // 個(gè)人認(rèn)為這個(gè)變量就是一個(gè)中間值,在該類(lèi)的最后一個(gè)方法中傳入該變量,完成操作。 
  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實(shí)例化,把這個(gè)布局當(dāng)一個(gè)UI控件一樣插入進(jìn)來(lái) 
    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);   // 獲取背景音樂(lè)開(kāi)關(guān)的狀態(tài)變量,在設(shè)置開(kāi)關(guān)界面存儲(chǔ),在這里讀取 
    if(playMusic) {  // 如果設(shè)置背景音樂(lè)打開(kāi),則開(kāi)啟服務(wù),播放音樂(lè) 
      Intent intent_service = new Intent(GameActivity.this, MusicService.class); 
      startService(intent_service); 
    } 
    SnakeView.mMoveDelay=saved.getInt("nandu",500);   // 獲取當(dāng)前設(shè)置的代表游戲難度的變量,在難度設(shè)置界面保存,在這里讀取 
 
    // 判斷是否有保存數(shù)據(jù),如果數(shù)據(jù)為空就準(zhǔn)備重新開(kāi)始游戲 
    if (savedInstanceState == null) { 
      mSnakeView.setMode(SnakeView.READY); 
    } else { 
      // 暫停后的恢復(fù) 
      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); 
    } 
 
  } 
  // 給開(kāi)始,暫停,退出,上下左右按鈕設(shè)置監(jiān)聽(tīng)。根據(jù)當(dāng)前的狀態(tài)來(lái)決定界面的更新操作 
  public void onClick(View v) { 
    switch (v.getId()) { 
      case R.id.game_start: 
         // 重新開(kāi)始游戲,這里延時(shí)變量必須初始化,不然每次游戲重新開(kāi)始之后,蛇的運(yùn)動(dòng)速度不會(huì)初始化 
        if ( mSnakeView.mMode == SnakeView.READY || mSnakeView.mMode == SnakeView.LOSE) { 
          SnakeView.mMoveDelay=saved.getInt("nandu",500); 
          mSnakeView.initNewGame(); 
          mSnakeView.setMode(SnakeView.RUNNING); 
          mSnakeView.update(); 
        } 
        // 暫停后開(kāi)始游戲,繼續(xù)暫停前的界面 
        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) { 
    //保存游戲狀態(tài) 
    outState.putBundle(ICICLE_KEY, mSnakeView.saveState()); 
  } 
} 

下面是游戲效果圖,運(yùn)行界面和暫停界面。我把邏輯流程圖也貼出來(lái),有什么問(wèn)題大家可以留言,多多交流!

本文通過(guò)帶大家回憶經(jīng)典游戲的同時(shí),學(xué)習(xí)了利用java開(kāi)發(fā)Android游戲——貪吃蛇,希望本文在大家學(xué)習(xí)Android開(kāi)發(fā)有所幫助。     

相關(guān)文章

  • Kotlin比較與解釋Lazy與Lateinit的用法

    Kotlin比較與解釋Lazy與Lateinit的用法

    在使用kotlin開(kāi)發(fā)中,因?yàn)楦鞣N原因,我們會(huì)經(jīng)常需要使用到延遲加載的功能,目前kotlin的延遲加載主要有兩種:lateinit和lazy,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值
    2023-02-02
  • Android應(yīng)用關(guān)閉的情況以及識(shí)別方法詳解

    Android應(yīng)用關(guān)閉的情況以及識(shí)別方法詳解

    對(duì)于現(xiàn)在的安卓手機(jī)而言,很多功能都是在逐步完善的,這篇文章主要給大家介紹了關(guān)于Android應(yīng)用關(guān)閉的情況以及識(shí)別的相關(guān)資料,文章通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • Android實(shí)現(xiàn)帶進(jìn)度條的WebView

    Android實(shí)現(xiàn)帶進(jìn)度條的WebView

    這篇文章主要介紹了Android實(shí)現(xiàn)帶進(jìn)度條的WebView,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • Android studio 如何刪除項(xiàng)目 module

    Android studio 如何刪除項(xiàng)目 module

    本篇文章主要介紹了Android studio 如何刪除項(xiàng)目module的相關(guān)知識(shí),具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧
    2017-05-05
  • Android Studio 利用Splash制作APP啟動(dòng)界面的方法

    Android Studio 利用Splash制作APP啟動(dòng)界面的方法

    這篇文章主要介紹了Android Studio 利用Splash制作APP啟動(dòng)界面,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05
  • Android應(yīng)用?;顚?shí)踐詳解

    Android應(yīng)用保活實(shí)踐詳解

    這篇文章主要介紹了Android應(yīng)用?;顚?shí)踐詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Android中多個(gè)ContentProvider的初始化順序詳解

    Android中多個(gè)ContentProvider的初始化順序詳解

    在日常Android開(kāi)發(fā)中經(jīng)常會(huì)寫(xiě)一些sdk來(lái)供他人或者自己調(diào)用,一般這些sdk都涉及到初始化,下面這篇文章主要給大家介紹了關(guān)于Android中多個(gè)ContentProvider的初始化順序的相關(guān)資料,需要的朋友可以參考下
    2022-04-04
  • 常用Android布局文件優(yōu)化技巧總結(jié)

    常用Android布局文件優(yōu)化技巧總結(jié)

    Android布局加載是Android應(yīng)用程序的重要組成部分,布局加載是指將 XML文件中定義的視圖層次結(jié)構(gòu)加載到內(nèi)存中,在這篇文章中,我們將深入探討 Android 布局加載的原理,包括 Android 布局文件的結(jié)構(gòu)和布局文件的常見(jiàn)問(wèn)題等方面,需要的朋友可以參考下
    2023-07-07
  • Android仿淘寶首頁(yè)頭條View垂直滾動(dòng)效果

    Android仿淘寶首頁(yè)頭條View垂直滾動(dòng)效果

    這篇文章主要為大家詳細(xì)介紹了Android仿淘寶首頁(yè)頭條View垂直滾動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • android surfaceView實(shí)現(xiàn)播放視頻功能

    android surfaceView實(shí)現(xiàn)播放視頻功能

    這篇文章主要為大家詳細(xì)介紹了android surfaceView實(shí)現(xiàn)播放視頻功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05

最新評(píng)論