天天看點

【貪吃蛇—Java程式員寫Android遊戲】系列 1.Android SDK Sample-Snake詳解

Snake也是一個經典遊戲了,Nokia藍屏機的王牌遊戲之一。Android SDK 1.5就有了它的身影。我們這裡就來詳細解析一下Android SDK Sample中的Snake工程。本工程基于SDK 2.3.3版本中的工程,路徑為:%Android_SDK_HOME% /samples/android-10/Snake

Snake也是一個經典遊戲了,Nokia藍屏機的王牌遊戲之一。Android SDK 1.5就有了它的身影。我們這裡就來詳細解析一下Android SDK Sample中的Snake工程。本工程基于SDK 2.3.3版本中的工程,路徑為:%Android_SDK_HOME% /samples/android-10/Snake

一、Eclipse工程

通過File-New Project-Android-Android Project,選擇“Create project from existing sample”建立自己的應用SnakeAndroid,如下圖:

【貪吃蛇—Java程式員寫Android遊戲】系列 1.Android SDK Sample-Snake詳解

運作效果如下圖:

【貪吃蛇—Java程式員寫Android遊戲】系列 1.Android SDK Sample-Snake詳解
【貪吃蛇—Java程式員寫Android遊戲】系列 1.Android SDK Sample-Snake詳解

二、工程結構和類圖

其實Snake的工程蠻簡單的,源檔案就三個:Snake.java SnakeView.java TileView.java。Snake類是這個遊戲的入口點,TitleView類進行遊戲的繪畫,SnakeView類則是對遊戲控制操作的處理。Coordinate,RefreshHandler是2個輔助類,也是SnakeView類中的内部類。其中,Coordinate是一個點的坐标(x,y),RefreshHandler将RefreshHandler對象綁定某個線程并給它發送消息。如下圖:

【貪吃蛇—Java程式員寫Android遊戲】系列 1.Android SDK Sample-Snake詳解

任何遊戲都需要有個引擎來推動遊戲的運作,最簡化的遊戲引擎就是:在一個線程中While循環,檢測使用者操作,對使用者的操作作出反應,更新遊戲的界面,直到使用者退出遊戲。

在Snake這個遊戲中,輔助類RefreshHandler繼承自Handler,用來把RefreshHandler與目前線程進行綁定,進而可以直接給線程發送消息并處理消息。注意一點:Handle對消息的處理都是異步。RefreshHandler在Handler的基礎上增加sleep()接口,用來每隔一個時間段後給目前線程發送一個消息。handleMessage()方法在接受消息後,根據目前的遊戲狀态重繪界面,運作機制如下:

【貪吃蛇—Java程式員寫Android遊戲】系列 1.Android SDK Sample-Snake詳解

這比較類似定時器的概念,在特定的時刻發送消息,根據消息處理相應的事件。update()與sleep()間接的互相調用就構成了一個循環。這裡要注意:mRedrawHandle綁定的是Avtivity所在的線程,也就是程式的主線程;另外由于sleep()是個異步函數,是以update()與sleep()之間的互相調用才沒有構成死循環。

最後分析下遊戲資料的儲存機制,如下:

【貪吃蛇—Java程式員寫Android遊戲】系列 1.Android SDK Sample-Snake詳解

這裡考慮了Activity的生命周期:如果使用者在遊戲期間離開遊戲界面,遊戲暫停;或者由于記憶體比較緊張,Android關閉遊戲釋放記憶體,那麼當使用者傳回遊戲界面的時候恢複到上次離開時的界面。

三、源碼解析

詳細解析下源代碼,由于代碼量不大,以注釋的方式列出如下:

1、Snake.java

View Code

 1 /**

 2  * <p>Title: Snake</p>

 3  * <p>Copyright: (C) 2007 The Android Open Source Project. Licensed under the Apache License, Version 2.0 (the "License")</p>

 4  * @author Gavin 标注

 5  */

 6 

 7 package com.deaboway.snake;

 8 

 9 import android.app.Activity;

10 import android.os.Bundle;

11 import android.widget.TextView;

12 

13 /**

14  * Snake: a simple game that everyone can enjoy.

15  * 

16  * This is an implementation of the classic Game "Snake", in which you control a

17  * serpent roaming around the garden looking for apples. Be careful, though,

18  * because when you catch one, not only will you become longer, but you\'ll move

19  * faster. Running into yourself or the walls will end the game.

20  * 

21  */

22 // 貪吃蛇: 經典遊戲,在一個花園中找蘋果吃,吃了蘋果會變長,速度變快。碰到自己和牆就挂掉。

23 public class Snake extends Activity {

24 

25     private SnakeView mSnakeView;

26 

27     private static String ICICLE_KEY = "snake-view";

28 

29     /**

30      * Called when Activity is first created. Turns off the title bar, sets up

31      * the content views, and fires up the SnakeView.

32      * 

33      */

34     // 在 activity 第一次建立時被調用

35     @Override

36     public void onCreate(Bundle savedInstanceState) {

37 

38         super.onCreate(savedInstanceState);

39         setContentView(R.layout.snake_layout);

40 

41         mSnakeView = (SnakeView) findViewById(R.id.snake);

42         mSnakeView.setTextView((TextView) findViewById(R.id.text));

43 

44         // 檢查存貯狀态以确定是重新開始還是恢複狀态

45         if (savedInstanceState == null) {

46             // 存儲狀态為空,說明剛啟動可以切換到準備狀态

47             mSnakeView.setMode(SnakeView.READY);

48         } else {

49             // 已經儲存過,那麼就去恢複原有狀态

50             Bundle map = savedInstanceState.getBundle(ICICLE_KEY);

51             if (map != null) {

52                 // 恢複狀态

53                 mSnakeView.restoreState(map);

54             } else {

55                 // 設定狀态為暫停

56                 mSnakeView.setMode(SnakeView.PAUSE);

57             }

58         }

59     }

60 

61     // 暫停事件被觸發時

62     @Override

63     protected void onPause() {

64         super.onPause();

65         // Pause the game along with the activity

66         mSnakeView.setMode(SnakeView.PAUSE);

67     }

68 

69     // 狀态儲存

70     @Override

71     public void onSaveInstanceState(Bundle outState) {

72         // 存儲遊戲狀态到View裡

73         outState.putBundle(ICICLE_KEY, mSnakeView.saveState());

74     }

75 

76 }

2、SnakeView.java

View Code

  1 /**

  2  * <p>Title: Snake</p>

  3  * <p>Copyright: (C) 2007 The Android Open Source Project. Licensed under the Apache License, Version 2.0 (the "License")</p>

  4  * @author Gavin 标注

  5  */

  6 

  7 package com.deaboway.snake;

  8 

  9 import java.util.ArrayList;

 10 import java.util.Random;

 11 

 12 import android.content.Context;

 13 import android.content.res.Resources;

 14 import android.os.Handler;

 15 import android.os.Message;

 16 import android.util.AttributeSet;

 17 import android.os.Bundle;

 18 import android.util.Log;

 19 import android.view.KeyEvent;

 20 import android.view.View;

 21 import android.widget.TextView;

 22 

 23 /**

 24  * SnakeView: implementation of a simple game of Snake

 25  * 

 26  * 

 27  */

 28 public class SnakeView extends TileView {

 29 

 30     private static final String TAG = "Deaboway";

 31 

 32     /**

 33      * Current mode of application: READY to run, RUNNING, or you have already

 34      * lost. static final ints are used instead of an enum for performance

 35      * reasons.

 36      */

 37     // 遊戲狀态,預設值是準備狀态

 38     private int mMode = READY;

 39 

 40     // 遊戲的四個狀态 暫停 準備 運作 和 失敗

 41     public static final int PAUSE = 0;

 42     public static final int READY = 1;

 43     public static final int RUNNING = 2;

 44     public static final int LOSE = 3;

 45 

 46     // 遊戲中蛇的前進方向,預設值北方

 47     private int mDirection = NORTH;

 48     // 下一步的移動方向,預設值北方

 49     private int mNextDirection = NORTH;

 50 

 51     // 遊戲方向設定 北 南 東 西

 52     private static final int NORTH = 1;

 53     private static final int SOUTH = 2;

 54     private static final int EAST = 3;

 55     private static final int WEST = 4;

 56 

 57     /**

 58      * Labels for the drawables that will be loaded into the TileView class

 59      */

 60     // 三種遊戲元

 61     private static final int RED_STAR = 1;

 62     private static final int YELLOW_STAR = 2;

 63     private static final int GREEN_STAR = 3;

 64 

 65     /**

 66      * mScore: used to track the number of apples captured mMoveDelay: number of

 67      * milliseconds between snake movements. This will decrease as apples are

 68      * captured.

 69      */

 70     // 遊戲得分

 71     private long mScore = 0;

 72 

 73     // 移動延遲

 74     private long mMoveDelay = 600;

 75 

 76     /**

 77      * mLastMove: tracks the absolute time when the snake last moved, and is

 78      * used to determine if a move should be made based on mMoveDelay.

 79      */

 80     // 最後一次移動時的毫秒時刻

 81     private long mLastMove;

 82 

 83     /**

 84      * mStatusText: text shows to the user in some run states

 85      */

 86     // 顯示遊戲狀态的文本元件

 87     private TextView mStatusText;

 88 

 89     /**

 90      * mSnakeTrail: a list of Coordinates that make up the snake\'s body

 91      * mAppleList: the secret location of the juicy apples the snake craves.

 92      */

 93     // 蛇身數組(數組以坐标對象為元素)

 94     private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();

 95 

 96     // 蘋果數組(數組以坐标對象為元素)

 97     private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();

 98 

 99     /**

100      * Everyone needs a little randomness in their life

101      */

102     // 随機數

103     private static final Random RNG = new Random();

104 

105     /**

106      * Create a simple handler that we can use to cause animation to happen. We

107      * set ourselves as a target and we can use the sleep() function to cause an

108      * update/invalidate to occur at a later date.

109      */

110     // 建立一個Refresh Handler來産生動畫: 通過sleep()來實作

111     private RefreshHandler mRedrawHandler = new RefreshHandler();

112 

113     // 一個Handler

114     class RefreshHandler extends Handler {

115 

116         // 處理消息隊列

117         @Override

118         public void handleMessage(Message msg) {

119             // 更新View對象

120             SnakeView.this.update();

121             // 強制重繪

122             SnakeView.this.invalidate();

123         }

124 

125         // 延遲發送消息

126         public void sleep(long delayMillis) {

127             this.removeMessages(0);

128             sendMessageDelayed(obtainMessage(0), delayMillis);

129         }

130     };

131 

132     /**

133      * Constructs a SnakeView based on inflation from XML

134      * 

135      * @param context

136      * @param attrs

137      */

138     // 構造函數

139     public SnakeView(Context context, AttributeSet attrs) {

140         super(context, attrs);

141         // 構造時初始化

142         initSnakeView();

143     }

144 

145     public SnakeView(Context context, AttributeSet attrs, int defStyle) {

146         super(context, attrs, defStyle);

147         initSnakeView();

148     }

149 

150     // 初始化

151     private void initSnakeView() {

152         // 可選焦點

153         setFocusable(true);

154 

155         Resources r = this.getContext().getResources();

156 

157         // 設定貼片圖檔數組

158         resetTiles(4);

159 

160         // 把三種圖檔存到Bitmap對象數組

161         loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));

162         loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));

163         loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));

164 

165     }

166 

167     // 開始新的遊戲——初始化

168     private void initNewGame() {

169         // 清空ArrayList清單

170         mSnakeTrail.clear();

171         mAppleList.clear();

172 

173         // For now we\'re just going to load up a short default eastbound snake

174         // that\'s just turned north

175         // 建立蛇身

176 

177         mSnakeTrail.add(new Coordinate(7, 7));

178         mSnakeTrail.add(new Coordinate(6, 7));

179         mSnakeTrail.add(new Coordinate(5, 7));

180         mSnakeTrail.add(new Coordinate(4, 7));

181         mSnakeTrail.add(new Coordinate(3, 7));

182         mSnakeTrail.add(new Coordinate(2, 7));

183 

184         // 新的方向 :北方

185         mNextDirection = NORTH;

186 

187         // 2個随機位置的蘋果

188         addRandomApple();

189         addRandomApple();

190 

191         // 移動延遲

192         mMoveDelay = 600;

193         // 初始得分0

194         mScore = 0;

195     }

View Code

  1     /**

  2      * Given a ArrayList of coordinates, we need to flatten them into an array

  3      * of ints before we can stuff them into a map for flattening and storage.

  4      * 

  5      * @param cvec

  6      *            : a ArrayList of Coordinate objects

  7      * @return : a simple array containing the x/y values of the coordinates as

  8      *         [x1,y1,x2,y2,x3,y3...]

  9      */

 10     // 坐标數組轉整數數組,把Coordinate對象的x y放到一個int數組中——用來儲存狀态

 11     private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {

 12         int count = cvec.size();

 13         int[] rawArray = new int[count * 2];

 14         for (int index = 0; index < count; index++) {

 15             Coordinate c = cvec.get(index);

 16             rawArray[2 * index] = c.x;

 17             rawArray[2 * index + 1] = c.y;

 18         }

 19         return rawArray;

 20     }

 21 

 22     /**

 23      * Save game state so that the user does not lose anything if the game

 24      * process is killed while we are in the background.

 25      * 

 26      * @return a Bundle with this view\'s state

 27      */

 28     // 儲存狀态

 29     public Bundle saveState() {

 30 

 31         Bundle map = new Bundle();

 32 

 33         map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));

 34         map.putInt("mDirection", Integer.valueOf(mDirection));

 35         map.putInt("mNextDirection", Integer.valueOf(mNextDirection));

 36         map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));

 37         map.putLong("mScore", Long.valueOf(mScore));

 38         map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));

 39 

 40         return map;

 41     }

 42 

 43     /**

 44      * Given a flattened array of ordinate pairs, we reconstitute them into a

 45      * ArrayList of Coordinate objects

 46      * 

 47      * @param rawArray

 48      *            : [x1,y1,x2,y2,...]

 49      * @return a ArrayList of Coordinates

 50      */

 51     // 整數數組轉坐标數組,把一個int數組中的x y放到Coordinate對象數組中——用來恢複狀态

 52     private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {

 53         ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();

 54 

 55         int coordCount = rawArray.length;

 56         for (int index = 0; index < coordCount; index += 2) {

 57             Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);

 58             coordArrayList.add(c);

 59         }

 60         return coordArrayList;

 61     }

 62 

 63     /**

 64      * Restore game state if our process is being relaunched

 65      * 

 66      * @param icicle

 67      *            a Bundle containing the game state

 68      */

 69     // 恢複狀态

 70     public void restoreState(Bundle icicle) {

 71 

 72         setMode(PAUSE);

 73 

 74         mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));

 75         mDirection = icicle.getInt("mDirection");

 76         mNextDirection = icicle.getInt("mNextDirection");

 77         mMoveDelay = icicle.getLong("mMoveDelay");

 78         mScore = icicle.getLong("mScore");

 79         mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));

 80     }

 81 

 82     /*

 83      * handles key events in the game. Update the direction our snake is

 84      * traveling based on the DPAD. Ignore events that would cause the snake to

 85      * immediately turn back on itself.

 86      * 

 87      * (non-Javadoc)

 88      * 

 89      * @see android.view.View#onKeyDown(int, android.os.KeyEvent)

 90      */

 91     // 監聽使用者鍵盤操作,并處理這些操作

 92     // 按鍵事件處理,確定貪吃蛇隻能90度轉向,而不能180度轉向

 93     @Override

 94     public boolean onKeyDown(int keyCode, KeyEvent msg) {

 95 

 96         // 向上鍵

 97         if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {

 98             // 準備狀态或者失敗狀态時

 99             if (mMode == READY | mMode == LOSE) {

100                 /*

101                  * At the beginning of the game, or the end of a previous one,

102                  * we should start a new game.

103                  */

104                 // 初始化遊戲

105                 initNewGame();

106                 // 設定遊戲狀态為運作

107                 setMode(RUNNING);

108                 // 更新

109                 update();

110                 // 傳回

111                 return (true);

112             }

113 

114             // 暫停狀态時

115             if (mMode == PAUSE) {

116                 /*

117                  * If the game is merely paused, we should just continue where

118                  * we left off.

119                  */

120                 // 設定成運作狀态

121                 setMode(RUNNING);

122                 update();

123                 // 傳回

124                 return (true);

125             }

126 

127             // 如果是運作狀态時,如果方向原有方向不是向南,那麼方向轉向北

128             if (mDirection != SOUTH) {

129                 mNextDirection = NORTH;

130             }

131             return (true);

132         }

133 

134         // 向下鍵

135         if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {

136             // 原方向不是向上時,方向轉向南

137             if (mDirection != NORTH) {

138                 mNextDirection = SOUTH;

139             }

140             // 傳回

141             return (true);

142         }

143 

144         // 向左鍵

145         if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {

146             // 原方向不是向右時,方向轉向西

147             if (mDirection != EAST) {

148                 mNextDirection = WEST;

149             }

150             // 傳回

151             return (true);

152         }

153 

154         // 向右鍵

155         if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {

156             // 原方向不是向左時,方向轉向東

157             if (mDirection != WEST) {

158                 mNextDirection = EAST;

159             }

160             // 傳回

161             return (true);

162         }

163 

164         // 按其他鍵時按原有功能傳回

165         return super.onKeyDown(keyCode, msg);

166     }

View Code

  1     /**

  2      * Sets the TextView that will be used to give information (such as "Game

  3      * Over" to the user.

  4      * 

  5      * @param newView

  6      */

  7     // 設定狀态顯示View

  8     public void setTextView(TextView newView) {

  9         mStatusText = newView;

 10     }

 11 

 12     /**

 13      * Updates the current mode of the application (RUNNING or PAUSED or the

 14      * like) as well as sets the visibility of textview for notification

 15      * 

 16      * @param newMode

 17      */

 18     // 設定遊戲狀态

 19     public void setMode(int newMode) {

 20 

 21         // 把目前遊戲狀态存入oldMode

 22         int oldMode = mMode;

 23         // 把遊戲狀态設定為新狀态

 24         mMode = newMode;

 25 

 26         // 如果新狀态是運作狀态,且原有狀态為不運作,那麼就開始遊戲

 27         if (newMode == RUNNING & oldMode != RUNNING) {

 28             // 設定mStatusTextView隐藏

 29             mStatusText.setVisibility(View.INVISIBLE);

 30             // 更新

 31             update();

 32             return;

 33         }

 34 

 35         Resources res = getContext().getResources();

 36         CharSequence str = "";

 37 

 38         // 如果新狀态是暫停狀态,那麼設定文本内容為暫停内容

 39         if (newMode == PAUSE) {

 40             str = res.getText(R.string.mode_pause);

 41         }

 42 

 43         // 如果新狀态是準備狀态,那麼設定文本内容為準備内容

 44         if (newMode == READY) {

 45             str = res.getText(R.string.mode_ready);

 46         }

 47 

 48         // 如果新狀态時失敗狀态,那麼設定文本内容為失敗内容

 49         if (newMode == LOSE) {

 50             // 把上輪的得分顯示出來

 51             str = res.getString(R.string.mode_lose_prefix) + mScore

 52                     + res.getString(R.string.mode_lose_suffix);

 53         }

 54 

 55         // 設定文本

 56         mStatusText.setText(str);

 57         // 顯示該View

 58         mStatusText.setVisibility(View.VISIBLE);

 59     }

 60 

 61     /**

 62      * Selects a random location within the garden that is not currently covered

 63      * by the snake. Currently _could_ go into an infinite loop if the snake

 64      * currently fills the garden, but we\'ll leave discovery of this prize to a

 65      * truly excellent snake-player.

 66      * 

 67      */

 68     // 添加蘋果

 69     private void addRandomApple() {

 70         // 新的坐标

 71         Coordinate newCoord = null;

 72         // 防止新蘋果出席在蛇身下

 73         boolean found = false;

 74         // 沒有找到合适的蘋果,就在循環體内一直循環,直到找到合适的蘋果

 75         while (!found) {

 76             // 為蘋果再找一個坐标,先随機一個X值

 77             int newX = 1 + RNG.nextInt(mXTileCount - 2);

 78             // 再随機一個Y值

 79             int newY = 1 + RNG.nextInt(mYTileCount - 2);

 80             // 新坐标

 81             newCoord = new Coordinate(newX, newY);

 82 

 83             // Make sure it\'s not already under the snake

 84             // 確定新蘋果不在蛇身下,先假設沒有發生沖突

 85             boolean collision = false;

 86 

 87             int snakelength = mSnakeTrail.size();

 88             // 和蛇占據的所有坐标比較

 89             for (int index = 0; index < snakelength; index++) {

 90                 // 隻要和蛇占據的任何一個坐标相同,即認為發生沖突了

 91                 if (mSnakeTrail.get(index).equals(newCoord)) {

 92                     collision = true;

 93                 }

 94             }

 95             // if we\'re here and there\'s been no collision, then we have

 96             // a good location for an apple. Otherwise, we\'ll circle back

 97             // and try again

 98             // 如果有沖突就繼續循環,如果沒沖突flag的值就是false,那麼自然會退出循環,新坐标也就誕生了

 99             found = !collision;

100         }

101 

102         if (newCoord == null) {

103             Log.e(TAG, "Somehow ended up with a null newCoord!");

104         }

105         // 生成一個新蘋果放在蘋果清單中(兩個蘋果有可能會重合——這時候雖然看到的是一個蘋果,但是呢,分數就是兩個分數。)

106         mAppleList.add(newCoord);

107     }

108 

109     /**

110      * Handles the basic update loop, checking to see if we are in the running

111      * state, determining if a move should be made, updating the snake\'s

112      * location.

113      */

114     // 更新 各種動作,特别是 貪吃蛇 的位置, 還包括:牆、蘋果等的更新

115     public void update() {

116         // 如果是處于運作狀态

117         if (mMode == RUNNING) {

118 

119             long now = System.currentTimeMillis();

120 

121             // 如果目前時間距離最後一次移動的時間超過了延遲時間

122             if (now - mLastMove > mMoveDelay) {

123                 //

124                 clearTiles();

125                 updateWalls();

126                 updateSnake();

127                 updateApples();

128                 mLastMove = now;

129             }

130             // Handler 會話程序sleep一個延遲時間機關

131             mRedrawHandler.sleep(mMoveDelay);

132         }

133 

134     }

135 

136     /**

137      * Draws some walls.

138      * 

139      */

140     // 更新牆

141     private void updateWalls() {

142         for (int x = 0; x < mXTileCount; x++) {

143             // 給上邊線的每個貼片位置設定一個綠色索引辨別

144             setTile(GREEN_STAR, x, 0);

145             // 給下邊線的每個貼片位置設定一個綠色索引辨別

146             setTile(GREEN_STAR, x, mYTileCount - 1);

147         }

148         for (int y = 1; y < mYTileCount - 1; y++) {

149             // 給左邊線的每個貼片位置設定一個綠色索引辨別

150             setTile(GREEN_STAR, 0, y);

151             // 給右邊線的每個貼片位置設定一個綠色索引辨別

152             setTile(GREEN_STAR, mXTileCount - 1, y);

153         }

154     }

155 

156     /**

157      * Draws some apples.

158      * 

159      */

160     // 更新蘋果

161     private void updateApples() {

162         for (Coordinate c : mAppleList) {

163             setTile(YELLOW_STAR, c.x, c.y);

164         }

165     }

View Code

  1     /**

  2      * Figure out which way the snake is going, see if he\'s run into anything

  3      * (the walls, himself, or an apple). If he\'s not going to die, we then add

  4      * to the front and subtract from the rear in order to simulate motion. If

  5      * we want to grow him, we don\'t subtract from the rear.

  6      * 

  7      */

  8     // 更新蛇

  9     private void updateSnake() {

 10         // 生長标志

 11         boolean growSnake = false;

 12 

 13         // 得到蛇頭坐标

 14         Coordinate head = mSnakeTrail.get(0);

 15         // 初始化一個新的蛇頭坐标

 16         Coordinate newHead = new Coordinate(1, 1);

 17 

 18         // 目前方向改成新的方向

 19         mDirection = mNextDirection;

 20 

 21         // 根據方向确定蛇頭新坐标

 22         switch (mDirection) {

 23         // 如果方向向東(右),那麼X加1

 24         case EAST: {

 25             newHead = new Coordinate(head.x + 1, head.y);

 26             break;

 27         }

 28             // 如果方向向西(左),那麼X減1

 29         case WEST: {

 30             newHead = new Coordinate(head.x - 1, head.y);

 31             break;

 32         }

 33             // 如果方向向北(上),那麼Y減1

 34         case NORTH: {

 35             newHead = new Coordinate(head.x, head.y - 1);

 36             break;

 37         }

 38             // 如果方向向南(下),那麼Y加1

 39         case SOUTH: {

 40             newHead = new Coordinate(head.x, head.y + 1);

 41             break;

 42         }

 43         }

 44 

 45         // Collision detection

 46         // For now we have a 1-square wall around the entire arena

 47         // 沖突檢測 新蛇頭是否四面牆重疊,那麼遊戲結束

 48         if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)

 49                 || (newHead.y > mYTileCount - 2)) {

 50             // 設定遊戲狀态為Lose

 51             setMode(LOSE);

 52             // 傳回

 53             return;

 54 

 55         }

 56 

 57         // Look for collisions with itself

 58         // 沖突檢測 新蛇頭是否和自身坐标重疊,重疊的話遊戲也結束

 59         int snakelength = mSnakeTrail.size();

 60 

 61         for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {

 62             Coordinate c = mSnakeTrail.get(snakeindex);

 63             if (c.equals(newHead)) {

 64                 // 設定遊戲狀态為Lose

 65                 setMode(LOSE);

 66                 // 傳回

 67                 return;

 68             }

 69         }

 70 

 71         // Look for apples

 72         // 看新蛇頭和蘋果們是否重疊

 73         int applecount = mAppleList.size();

 74         for (int appleindex = 0; appleindex < applecount; appleindex++) {

 75             Coordinate c = mAppleList.get(appleindex);

 76             if (c.equals(newHead)) {

 77                 // 如果重疊,蘋果坐标從蘋果清單中移除

 78                 mAppleList.remove(c);

 79                 // 再立刻增加一個新蘋果

 80                 addRandomApple();

 81                 // 得分加一

 82                 mScore++;

 83                 // 延遲是以前的90%

 84                 mMoveDelay *= 0.9;

 85                 // 蛇增長标志改為真

 86                 growSnake = true;

 87             }

 88         }

 89 

 90         // push a new head onto the ArrayList and pull off the tail

 91         // 在蛇頭的位置增加一個新坐标

 92         mSnakeTrail.add(0, newHead);

 93         // except if we want the snake to grow

 94         // 如果沒有增長

 95         if (!growSnake) {

 96             // 如果蛇頭沒增長則删去最後一個坐标,相當于蛇向前走了一步

 97             mSnakeTrail.remove(mSnakeTrail.size() - 1);

 98         }

 99 

100         int index = 0;

101         // 重新設定一下顔色,蛇頭是黃色的(同蘋果一樣),蛇身是紅色的

102         for (Coordinate c : mSnakeTrail) {

103             if (index == 0) {

104                 setTile(YELLOW_STAR, c.x, c.y);

105             } else {

106                 setTile(RED_STAR, c.x, c.y);

107             }

108             index++;

109         }

110 

111     }

112 

113     /**

114      * Simple class containing two integer values and a comparison function.

115      * There\'s probably something I should use instead, but this was quick and

116      * easy to build.

117      * 

118      */

119     // 坐标内部類——原作者說這是臨時做法

120     private class Coordinate {

121         public int x;

122         public int y;

123 

124         // 構造函數

125         public Coordinate(int newX, int newY) {

126             x = newX;

127             y = newY;

128         }

129 

130         // 重寫equals

131         public boolean equals(Coordinate other) {

132             if (x == other.x && y == other.y) {

133                 return true;

134             }

135             return false;

136         }

137 

138         // 重寫toString

139         @Override

140         public String toString() {

141             return "Coordinate: [" + x + "," + y + "]";

142         }

143     }

144 

145 }

3、TileView.java

View Code

  1 /**

  2  * <p>Title: Snake</p>

  3  * <p>Copyright: (C) 2007 The Android Open Source Project. Licensed under the Apache License, Version 2.0 (the "License")</p>

  4  * @author Gavin 标注

  5  */

  6 

  7 package com.deaboway.snake;

  8 

  9 import android.content.Context;

 10 import android.content.res.TypedArray;

 11 import android.graphics.Bitmap;

 12 import android.graphics.Canvas;

 13 import android.graphics.Paint;

 14 import android.graphics.drawable.Drawable;

 15 import android.util.AttributeSet;

 16 import android.view.View;

 17 

 18 /**

 19  * TileView: a View-variant designed for handling arrays of "icons" or other

 20  * drawables.

 21  * 

 22  */

 23 // View 變種,用來處理 一組 貼片—— “icons”或其它可繪制的對象

 24 public class TileView extends View {

 25 

 26     /**

 27      * Parameters controlling the size of the tiles and their range within view.

 28      * Width/Height are in pixels, and Drawables will be scaled to fit to these

 29      * dimensions. X/Y Tile Counts are the number of tiles that will be drawn.

 30      */

 31 

 32     protected static int mTileSize;

 33 

 34     // X軸的貼片數量

 35     protected static int mXTileCount;

 36     // Y軸的貼片數量

 37     protected static int mYTileCount;

 38 

 39     // X偏移量

 40     private static int mXOffset;

 41     // Y偏移量

 42     private static int mYOffset;

 43 

 44     /**

 45      * A hash that maps integer handles specified by the subclasser to the

 46      * drawable that will be used for that reference

 47      */

 48     // 貼片圖像的圖像數組

 49     private Bitmap[] mTileArray;

 50 

 51     /**

 52      * A two-dimensional array of integers in which the number represents the

 53      * index of the tile that should be drawn at that locations

 54      */

 55     // 儲存每個貼片的索引——二維數組

 56     private int[][] mTileGrid;

 57 

 58     // Paint對象(畫筆、顔料)

 59     private final Paint mPaint = new Paint();

 60 

 61     // 構造函數

 62     public TileView(Context context, AttributeSet attrs, int defStyle) {

 63         super(context, attrs, defStyle);

 64 

 65         TypedArray a = context.obtainStyledAttributes(attrs,

 66                 R.styleable.TileView);

 67 

 68         mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);

 69 

 70         a.recycle();

 71     }

 72 

 73     public TileView(Context context, AttributeSet attrs) {

 74         super(context, attrs);

 75 

 76         TypedArray a = context.obtainStyledAttributes(attrs,

 77                 R.styleable.TileView);

 78 

 79         mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);

 80 

 81         a.recycle();

 82     }

 83 

 84     /**

 85      * Rests the internal array of Bitmaps used for drawing tiles, and sets the

 86      * maximum index of tiles to be inserted

 87      * 

 88      * @param tilecount

 89      */

 90     // 設定貼片圖檔數組

 91     public void resetTiles(int tilecount) {

 92         mTileArray = new Bitmap[tilecount];

 93     }

 94 

 95     // 回調:當該View的尺寸改變時調用,在onDraw()方法調用之前就會被調用,是以用來設定一些變量的初始值

 96     // 在視圖大小改變的時候調用,比如說手機由垂直旋轉為水準

 97     @Override

 98     protected void onSizeChanged(int w, int h, int oldw, int oldh) {

 99 

100         // 定義X軸貼片數量

101         mXTileCount = (int) Math.floor(w / mTileSize);

102         mYTileCount = (int) Math.floor(h / mTileSize);

103 

104         // X軸偏移量

105         mXOffset = ((w - (mTileSize * mXTileCount)) / 2);

106 

107         // Y軸偏移量

108         mYOffset = ((h - (mTileSize * mYTileCount)) / 2);

109 

110         // 定義貼片的二維數組

111         mTileGrid = new int[mXTileCount][mYTileCount];

112 

113         // 清空所有貼片

114         clearTiles();

115     }

116 

117     /**

118      * Function to set the specified Drawable as the tile for a particular

119      * integer key.

120      * 

121      * @param key

122      * @param tile

123      */

124     // 給mTileArray這個Bitmap圖檔數組設定值

125     public void loadTile(int key, Drawable tile) {

126         Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize,

127                 Bitmap.Config.ARGB_8888);

128         Canvas canvas = new Canvas(bitmap);

129         tile.setBounds(0, 0, mTileSize, mTileSize);

130         // 把一個drawable轉成一個Bitmap

131         tile.draw(canvas);

132         // 在數組裡存入該Bitmap

133         mTileArray[key] = bitmap;

134     }

135 

136     /**

137      * Resets all tiles to 0 (empty)

138      * 

139      */

140     // 清空所有貼片

141     public void clearTiles() {

142         for (int x = 0; x < mXTileCount; x++) {

143             for (int y = 0; y < mYTileCount; y++) {

144                 // 全部設定為0

145                 setTile(0, x, y);

146             }

147         }

148     }

149 

150     /**

151      * Used to indicate that a particular tile (set with loadTile and referenced

152      * by an integer) should be drawn at the given x/y coordinates during the

153      * next invalidate/draw cycle.

154      * 

155      * @param tileindex

156      * @param x

157      * @param y

158      */

159     // 給某個貼片位置設定一個狀态索引

160     public void setTile(int tileindex, int x, int y) {

161         mTileGrid[x][y] = tileindex;

162     }

163 

164     // onDraw 在視圖需要重畫的時候調用,比如說使用invalidate重新整理界面上的某個矩形區域

165     @Override

166     public void onDraw(Canvas canvas) {

167 

168         super.onDraw(canvas);

169         for (int x = 0; x < mXTileCount; x += 1) {

170             for (int y = 0; y < mYTileCount; y += 1) {

171                 // 當索引大于零,也就是不空時

172                 if (mTileGrid[x][y] > 0) {

173                     // mTileGrid中不為零時畫此貼片

174                     canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x

175                             * mTileSize, mYOffset + y * mTileSize, mPaint);

176                 }

177             }

178         }

179 

180     }

181 }

四、工程檔案下載下傳

為了友善大家閱讀,可以到如下位址下載下傳工程源代碼:

 http://ishare.iask.sina.com.cn/f/14312223.html

五、小結及下期預告:

本次詳細解析了Android SDK 自帶 Sample——Snake的結構和功能。下次将會把這個遊戲移植到J2ME平台上,并且比較Android和J2ME的差別和相通之處,讓從事過J2ME開發的朋友對Android開發有個更加直覺的認識。

【貪吃蛇—Java程式員寫Android遊戲】系列 1.Android SDK Sample-Snake詳解