Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> 【貪吃蛇—Java程序員寫Android游戲】系列 1.Android SDK Sample-Snake詳解

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

編輯:Android開發實例

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,如下圖:


 

 

運行效果如下圖:


 

 
 

 

 

 

二、工程結構和類圖

 

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

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

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

 
 

 

 

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

最後分析下游戲數據的保存機制,如下:

 

 

這裡考慮了Activity的生命周期:如果用戶在游戲期間離開游戲界面,游戲暫停;或者由於內存比較緊張,Android關閉游戲釋放內存,那麼當用戶返回游戲界面的時候恢復到上次離開時的界面。

三、源碼解析

 

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

1、Snake.java

 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

    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     }

  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     }

  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     }

    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

    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 }


 

 

四、工程文件下載

 

工程源代碼 

五、小結及下期預告:

 

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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved