小多的Android入門教程系列---之1---貪喫蛇改進版

本教程PDF版下載

如果上面的鏈接失效,請用新浪微盤下載:http://vdisk.weibo.com/s/pcDT

 

Android_Tutorial-Advanced_Snake

小多的Android入門教程系列

1

貪喫蛇改進版

 

 

 

背景

android 2.3.3 SDK 10

Eclipse 3.5.1

 

反饋

[email protected]

 

  

時間

事件

版本

人物

2011-06-17

創建文檔

0.1

連小多

 

 

 


      學習Androdi的最有效的辦法就是讀實例代碼,實例當然是SDK自帶的sample。本套入門教程系列的目的是讓你快速掌握Android開發的要點,只要認真看,跟着做,就一定可以掌握。

 

1.   開始前的準備

1.準備好android SDK的開發環境,WINDOWSLINUX的都可以,分別都要改一些環境變量。

2.安裝好eclipse,然後eclipse上安裝好ADT

3.android管理裏面,安裝好我們要用的SDK android-10,及其sample部分

4.建一個android 2.3.3 (也就是android-10)的模擬器。

 

鑑於Window下如何配置的文章網上很多,這裏我簡要給一個linux下配置的過程。我使用的操作系統是fedora 12,如果不需要可以跳過下一小節。


 

1.1.   LinuxFedora)下配置Android的開發環境

1.        下載android sdk文件,比如我下的android-sdk_r11-linux_x86.tgz

2.        解壓它: gtar xvf android-sdk_r11-linux_x86.tgz

3.        修改 ~/.bash_profile文件,在最後添加類似下面幾行,請不要直接copy 路徑都取決於你解壓sdk包的位置

PATH=$PATH:/0/software/android_osg/android-sdk-linux_x86/tools

ANDROID_SDK_HOME=/0/software/android_osg/android-sdk-linux_x86/

export PATH ANDROID_SDK_HOME

這裏PATH是爲了把一些可執行文件加入調用路徑,比如android, adb神馬的

ANDROID_SDK_HOME這個環境變量的目的是爲了給一個自定義的模擬器建立的AVD虛擬機的位置,如果你狠開心它就放在/home下面的話,可以忽略這個變量。

4.        安裝個eclipse,我裝系統的時候好像就直接裝上了。

5.        運行eclipse,現在安裝android插件ADT,這裏偷個懶,抄下官網的步驟:網址在這裏:http://developer.android.com/sdk/eclipse-adt.html#installing

 

   1.  Start Eclipse, then select Help > Install New Software....

   2. Click Add, in the top-right corner.

   3. In the Add Repository dialog that appears, enter "ADT Plugin" for the Name and the following URL for the Location:

 

      https://dl-ssl.google.com/android/eclipse/

 

   4. Click OK

 

      Note: If you have trouble acquiring the plugin, try using "http" in the Location URL, instead of "https" (https is preferred for security reasons).

   5. In the Available Software dialog, select the checkbox next to Developer Tools and click Next.

   6. In the next window, you'll see a list of the tools to be downloaded. Click Next.

   7. Read and accept the license agreements, then click Finish.

 

      Note: If you get a security warning saying that the authenticity or validity of the software can't be established, click OK.

   8. When the installation completes, restart Eclipse.

 

6.        現在還沒完,再來一步,在eclipse裏,Window->Preferences->左邊點Android,右邊在SDK Location中填寫你解壓的路徑,比如我填的是:/0/software/android_osg/android-sdk-linux_x86

 

 

1.2.   Sample創建android項目

這次我們要動手的smaplesnake,所以,來導入吧!

File->New->Android Project,選擇Create project from existing sample, 選擇snake finish!

 

PS 應該會注意到還有一個項目是snake-test, 這個是一個test的示例項目,也可以如上創建,不過會顯示有錯誤,這是因爲沒有添加項目依賴關係,可以在test項目的properties裏:左邊->Java Build Path, 右邊->Projects tab, add,添加選擇snake,就OK了。

 

這個test項目是個空殼,沒有任何實際的測試實施。


 

2.   snake添加障礙物

2.1.   繪出障礙物

原遊戲中,只有一圈圍牆,我們這次修改的目的是,在圍牆內添加一些障礙物,如果不慎撞到,也算輸,增加遊戲的難度。由於障礙物應該隨機出現,我們很自然的就聯想到遊戲中存在的蘋果,障礙物很大程度上和蘋果類似,只是撞上會死,不會被喫掉。

 

1.        更改

SnakeView.java中,我們看到類裏面對蘋果的記錄,是用了一個ArrayList<Coordinate>來存儲所有的蘋果,同樣,snake的身體也用了這麼一個列表來存儲。

    /**

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

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

     */

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

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

SnakeView.java

所以呢?當然!我們也要添加對於障礙物的存儲哦!!於是添加如下:

(對於新添加的代碼部分,我們使用背景黃色高亮提示。)

(註釋不是必須的,但是相當的有必要!)

    /**

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

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

     * mBarrierList: the list of Coordinates that store the position of barriers.

     */

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

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

    private ArrayList<Coordinate> mBarrierList = new ArrayList<Coordinate>();

SnakeView.java

 

遊戲一開始就有提示:按向上鍵開始,那麼這裏發生了什麼呢?我們來看看對於按鍵事件的處理函數:

    public boolean onKeyDown(int keyCode, KeyEvent msg) {

 

        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {

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

                /*

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

                 * we should start a new game.

                 */

                initNewGame();

                setMode(RUNNING);

                update();

                return (true);

            }

 

            if (mMode == PAUSE) {

                /*

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

                 * we left off.

                 */

                setMode(RUNNING);

                update();

                return (true);

            }

 

            if (mDirection != SOUTH) {

                mNextDirection = NORTH;

            }

            return (true);

        }

 

        if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {

            if (mDirection != NORTH) {

                mNextDirection = SOUTH;

            }

            return (true);

        }

 

        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {

            if (mDirection != EAST) {

                mNextDirection = WEST;

            }

            return (true);

        }

 

        if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {

            if (mDirection != WEST) {

                mNextDirection = EAST;

            }

            return (true);

        }

 

        return super.onKeyDown(keyCode, msg);

    }

SnakeView.java

 

可以看到,在按了向上鍵以後,發生的事情根據mMode值的不同,有不同的作用。現在重點來看看當mMode值爲READY或者LOSE的時候:

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

                /*

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

                 * we should start a new game.

                 */

                initNewGame();

                setMode(RUNNING);

                update();

                return (true);

            }

SnakeView.java

可見mMode值在控制着遊戲的狀態,而initNewGame()顧名思義是在初始化遊戲,我們來看看它是如何初始化的:(爲了方便閱讀,去掉了空行)

    private void initNewGame() {

        mSnakeTrail.clear();

        mAppleList.clear();

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

        // that's just turned north

        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));

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

        mNextDirection = NORTH;

        // Two apples to start with

        addRandomApple();

        addRandomApple();

        mMoveDelay = 600;

        mScore = 0;

    }

SnakeView.java

非常清楚,首先清空蛇的身體列表,然後是蘋果列表,然後一個一個方塊的添加了蛇的身體,並且指定了mNextDirection = NORTH也就是說,朝向是北,這個參數我們在之前的按鍵事件裏看見過,看來它是用來控制在下一個屏幕刷新時,蛇的移動方向。

最後添加了兩個隨機的蘋果,然後設置了初始的時延爲600,得分清空爲0

 

至此,整個初始化的過程已經很明白,而我們在這裏要做的非常簡單,就是也清空一下障礙物的列表即可,還記得之前我們的障礙物的列表的名字嗎?mBarrierList 沒錯,就是它!

 

2.        更改

原來的代碼

    private void initNewGame() {

        mSnakeTrail.clear();

        mAppleList.clear();

 

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

        // that's just turned north

 

       

        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));

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

       

        mNextDirection = NORTH;

 

        // Two apples to start with

        addRandomApple();

        addRandomApple();

 

        mMoveDelay = 600;

        mScore = 0;

    }

SnakeView.java

 

修改後的代碼:

    private void initNewGame() {

        mSnakeTrail.clear();

        mAppleList.clear();

        mBarrierList.clear();

       

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

        // that's just turned north

 

       

        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));

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

       

        mNextDirection = NORTH;

 

        // Two apples to start with

        addRandomApple();

        addRandomApple();

 

        mMoveDelay = 600;

        mScore = 0;

    }

SnakeView.java

 

可以看到!!非常簡單,就一個清空列表的語句!我們已經完成了對障礙物的初始化!!Android是不是很簡單^_^

 

初始化完畢,我們再回過頭來繼續看看初始化遊戲的那段代碼:

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

                /*

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

                 * we should start a new game.

                 */

                initNewGame();

                setMode(RUNNING);

                update();

                return (true);

            }

SnakeView.java

它將遊戲的Mode設置爲RUNNING,這個明顯就是一個簡單的成員變量賦值。我們好奇的是下面的一句話:update()

很明顯,經過了這個函數,一下子就完成了對整個案件事件的處理,遊戲也就開始了,那麼,它是如何update的呢?一起來看看:

 

    /**

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

     * state, determining if a move should be made, updating the snake's location.

     */

    public void update() {

        if (mMode == RUNNING) {

            long now = System.currentTimeMillis();

 

            if (now - mLastMove > mMoveDelay) {

                clearTiles();

                updateWalls();

                updateSnake();

                updateApples();

                mLastMove = now;

            }

            mRedrawHandler.sleep(mMoveDelay);

        }

 

    }

SnakeView.java

可以看到,只有mMode值是RUNNING的時候,這個函數纔會真的有所行動,不過行動的內容。。。我相信你和我一樣失望,原來在時延判斷夠了以後,它完全是掉用了一堆其他的update函數,我們還是完全不曉得到底什麼在被update!!!

 

帶着滿腔問號!!我們趕緊的,分別看看這幾個update函數,但是,首先,先看看clearTiles()吧,如果你是第一次接觸面向對象編程,或者並不熟悉編程,會相當奇怪,這個函數怎麼在SnakeView.java裏沒有呢!?

 

其實呢,SnakeView這個類,是TileView的子類,他們的關係如下:

 

SnakeView繼承了TileView的一些默認行爲,其中就包括這個函數clearTiles(),所以,它位於TileView.java裏:

 

    /**

     * Resets all tiles to 0 (empty)

     *

     */

    public void clearTiles() {

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

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

                setTile(0, x, y);

            }

        }

    }

 

    /**

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

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

     * next invalidate/draw cycle.

     *

     * @param tileindex

     * @param x

     * @param y

     */

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

        mTileGrid[x][y] = tileindex;

    }

TileView.java

通過連帶着看函數setTile(),我們看到,那麼clearTile()這個函數無非就是遍歷所有的tile,將其賦值爲0,存儲每個tile屬性值的傢伙是mTileGrid,它的定義如下:

 

    /**

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

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

     */

    private int[][] mTileGrid;

 

TileView.java

可見非常簡單,就是一個整形二維數組!!你敢說到這裏你什麼不懂麼!!?都很簡單!!!對不對!!

 

好,掃清這個障礙,我們終於可以來看看這個幾個update函數的真面目了!!

先看看updateWalls()updateApples()他們倆比較短小

 

    /**

     * Draws some walls.

     *

     */

    private void updateWalls() {

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

            setTile(GREEN_STAR, x, 0);

            setTile(GREEN_STAR, x, mYTileCount - 1);

        }

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

            setTile(GREEN_STAR, 0, y);

            setTile(GREEN_STAR, mXTileCount - 1, y);

        }

    }

 

    /**

     * Draws some apples.

     *

     */

    private void updateApples() {

        for (Coordinate c : mAppleList) {

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

        }

    }

SnakeView.java

 

非常清晰,可見updateWalls就是畫了一圈綠方塊的牆,而updateApples就是畫了蘋果列表裏的蘋果,那麼我們的障礙物呢,對啊,沒忘了吧,瘋,就是要加它啊,這裏我們也把它加上,完全類似畫蘋果的,對吧,至於怎麼添加障礙物,那個隨後再說!!!

 

3.        更改

原來的代碼

    /**

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

     * state, determining if a move should be made, updating the snake's location.

     */

    public void update() {

        if (mMode == RUNNING) {

            long now = System.currentTimeMillis();

 

            if (now - mLastMove > mMoveDelay) {

                clearTiles();

                updateWalls();

                updateSnake();

                updateApples();

                mLastMove = now;

            }

            mRedrawHandler.sleep(mMoveDelay);

        }

 

    }

SnakeView.java

 

修改後的代碼:

    /**

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

     * state, determining if a move should be made, updating the snake's location.

     */

    public void update() {

        if (mMode == RUNNING) {

            long now = System.currentTimeMillis();

 

            if (now - mLastMove > mMoveDelay) {

                clearTiles();

                updateWalls();

                updateSnake();

                updateApples();

                updateBarrier();

                mLastMove = now;

            }

            mRedrawHandler.sleep(mMoveDelay);

        }

}

 

    /**

     * Draws some Barrier

     *

     */

    private void updateBarrier() {

        for (Coordinate c : mBarrierList) {

            setTile(GREEN_STAR, c.x, c.y);

        }

}

SnakeView.java

 

OK,到這裏,我們也可以畫出障礙物啦!!可以看到,我讓障礙物是綠色的,這樣和邊界一樣,都代表撞上就要死!!!(當然,以目前的代碼,撞上還死不了的,慢慢來)

 

目前是可以把它畫出來了,我覺得你很可能已經迫不及待了,既然能畫,能給看看麼?!好的!!我們重新來看看函數initNewGame

 

4.        臨時更改

如下面的代碼所示,灰色底的一行代碼是在座標(2,2)處添加了一個Barrier,它只是臨時的,爲了讓你看看運行的效果,看完記得刪去。

    private void initNewGame() {

        mSnakeTrail.clear();

        mAppleList.clear();

        mBarrierList.clear();

 

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

        // that's just turned north

        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));

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

        mNextDirection = NORTH;

        // Two apples to start with

        addRandomApple();

        addRandomApple();  

        mBarrierList.add(new Coordinate(2,2));

        mMoveDelay = 600;

        mScore = 0;

    }

SnakeView.java

好的,就這樣,保存,然後運行,選擇作爲Android Application運行,看看有什麼出現!?

 

 

是滴!!它已經顯示出來了,你的障礙物!!當然,你也可以添加多個,但是目前,它只是個畫在畫面上的綠方塊,而且由於在update()中,它是最後被畫上的東西,所以,它會顯示在畫面的最前端!!有點像photoshop中的層,它會覆蓋之前的層,所以update()中不同部件的更新順序很重要!!也許你還雲裏霧裏,看看下面這張,或者你自己試試,讓蛇跑到我們的障礙物上去!

 

 

看見否?蛇身子是在障礙物下面的!!完全是因爲update()裏的更新順序,                updateWalls();

                updateSnake();

                updateApples();

                updateBarrier();

你試試吧順序成下面的,看看會蛇和障礙物重合的時候會發生什麼?

updateWalls();

               updateBarrier();

                updateSnake();

                updateApples();

 

小結:我們總共進行了3處小更改(不包括最後一次臨時更改),就看到了我們的障礙物華麗出現,是不是很振奮呢!?下來我們讓這個障礙物不只是畫出來的,而且成爲真正的可以撞死蛇的障礙物!!像不像神筆馬良,哈哈

 

2.2.   成真吧!障礙物

 

請忽略這一節的弱智名字,其實這一步相當的簡單,所謂畫龍點睛,其實一筆就夠,不過在此之前,我們先看看之前遺漏的還未見過真容的updateSnake()函數:

這次函數還挺長的,耐心看一下

 

    /**

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

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

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

     * grow him, we don't subtract from the rear.

     *

     */

    private void updateSnake() {

        boolean growSnake = false;

 

        // grab the snake by the head

        Coordinate head = mSnakeTrail.get(0);

        Coordinate newHead = new Coordinate(1, 1);

 

        mDirection = mNextDirection;

 

        switch (mDirection) {

        case EAST: {

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

            break;

        }

        case WEST: {

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

            break;

        }

        case NORTH: {

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

            break;

        }

        case SOUTH: {

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

            break;

        }

        }

 

        // Collision detection

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

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

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

            setMode(LOSE);

            return;

 

        }

 

        // Look for collisions with itself

        int snakelength = mSnakeTrail.size();

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

            Coordinate c = mSnakeTrail.get(snakeindex);

            if (c.equals(newHead)) {

                setMode(LOSE);

                return;

            }

        }

       

        // Look for apples

        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.9;

 

                growSnake = true;

            }

        }

 

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

        mSnakeTrail.add(0, newHead);

        // except if we want the snake to grow

        if (!growSnake) {

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

        }

 

        int index = 0;

        for (Coordinate c : mSnakeTrail) {

            if (index == 0) {

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

            } else {

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

            }

            index++;

        }

 

    }

SnakeView.java

 

看下來其實也還好,主要乾了這麼些事情,我們來總結一下:

1.      取得方向,在方向上前進一格,取得新的蛇頭座標

2.      檢查是否撞邊,撞了算輸

3.      看看新的頭位置有沒有和身體的某一部分重合,如果重合,輸

4.      看看新的頭位置有沒有和某個蘋果重合,有的話加分(mScore++;)去掉此蘋果,隨機位置生成一個新的蘋果,並且蛇身增長,其實就是不增長的情況下去掉最後一個蛇身的方塊,如果增長了就不去掉了。

另外每次分數增加,就減少刷新時延到之前的90%mMoveDelay *= 0.9;),所以你會越玩感覺速度越快。

5.      最後,把蛇身給畫出來,完事兒!

 

我們要幹什麼呢?對,就是再加一條,如果新頭和某個障礙物重合,算輸!

於是,就有了下面的更改:

 

5.        更改

從這次更改起,我們只把增加和改動的部分用黃色背景高亮標出。

    /**

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

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

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

     * grow him, we don't subtract from the rear.

     *

     */

    private void updateSnake() {

        boolean growSnake = false;

 

        // grab the snake by the head

        Coordinate head = mSnakeTrail.get(0);

        Coordinate newHead = new Coordinate(1, 1);

 

        mDirection = mNextDirection;

 

        switch (mDirection) {

        case EAST: {

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

            break;

        }

        case WEST: {

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

            break;

        }

        case NORTH: {

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

            break;

        }

        case SOUTH: {

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

            break;

        }

        }

 

        // Collision detection

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

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

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

            setMode(LOSE);

            return;

 

        }

 

        // Look for collisions with itself

        int snakelength = mSnakeTrail.size();

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

            Coordinate c = mSnakeTrail.get(snakeindex);

            if (c.equals(newHead)) {

                setMode(LOSE);

                return;

            }

        }

       

        // Look for collisions with barrier

        int barriercount = mBarrierList.size();

        for (int barrierindex = 0; barrierindex < barriercount; barrierindex++) {

            Coordinate c = mBarrierList.get(barrierindex);

            if (c.equals(newHead)) {

                setMode(LOSE);

                return;

            }

        }

 

        // Look for apples

        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.9;

 

                growSnake = true;

            }

        }

 

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

        mSnakeTrail.add(0, newHead);

        // except if we want the snake to grow

        if (!growSnake) {

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

        }

 

        int index = 0;

        for (Coordinate c : mSnakeTrail) {

            if (index == 0) {

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

            } else {

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

            }

            index++;

        }

 

}

SnakeView.java

 

 

OK,改動完畢,再試着運行一下,撞上去!!撞死了!!有木有!!

 

 

 

 

 

 


 

2.3.   隨機生成障礙物

既然是隨機添加,我們又想起了蘋果同學,它不也是隨機添加的麼?

OK,那就看看這個被我們忽略了好幾次的函數addRandomApple()

 

    /**

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

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

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

     * truly excellent snake-player.

     *

     */

    private void addRandomApple() {

        Coordinate newCoord = null;

        boolean found = false;

        while (!found) {

            // Choose a new location for our apple

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

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

            newCoord = new Coordinate(newX, newY);

 

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

            boolean collision = false;

            int snakelength = mSnakeTrail.size();

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

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

                    collision = true;

                }

            }

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

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

            // and try again

            found = !collision;

        }

        if (newCoord == null) {

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

        }

        mAppleList.add(newCoord);

    }

SnakeView.java

 

這個的步驟我們也來看看:

1.1到邊界的範圍內,隨機生成XY座標

2.沿着蛇身遍歷,看看剛生成的座標有沒有不巧在蛇身上

3.沒有衝突的話,OK,就是它了

4.添加這個新生成的蘋果座標到mAppleList

 

也非常好理解,我們可以仿照此函數來隨機生成障礙物,不過在此之前,稍微修改一下這個函數,先讓隨機生成的蘋果位置和障礙物不要衝突

 

6.        更改

 

    /**

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

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

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

     * truly excellent snake-player.

     *

     */

    private void addRandomApple() {

        Coordinate newCoord = null;

        boolean found = false;

        while (!found) {

            // Choose a new location for our apple

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

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

            newCoord = new Coordinate(newX, newY);

 

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

            boolean collision = false;

            int snakelength = mSnakeTrail.size();

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

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

                    collision = true;

                }

            }

           

            int barrierListsnakelength = mBarrierList.size();

            for (int index = 0; index < barrierListsnakelength; index++) {

                if (mBarrierList.get(index).equals(newCoord)) {

                    collision = true;

                }

            }

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

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

            // and try again

            found = !collision;

        }

        if (newCoord == null) {

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

        }

        mAppleList.add(newCoord);

    }

SnakeView.java

 

 

好了,到這裏我們就可以開始仿照寫一個隨機添加障礙物的函數了,叫它addRandomBarrier()吧!!

 

這裏插一句啊,因爲真的很相像,如果在實際項目中遇到此類需要仿照寫函數的情況,請立刻考慮類的繼承和多態性!也就是,因爲蘋果和障礙物很大程度上非常相似,請考慮爲他們建一個基類,制定他們統一的行爲,然後從基類繼承出蘋果和障礙物,衍生彼此不同的行爲。

 

OK,但是在這個例子裏,我們力求最小化和最貼近原來代碼的改動,那麼,你可以試着寫出這個函數,然後來對比一下!?

 

7.        更改

(淡黃色背景,因爲這個函數是全新的。)

    /**

     * For adding barrier, selects a random location within the garden that is not currently covered

     * by the snake or barrier.

     *

     */

    private void addRandomBarrier() {

        Coordinate newCoord = null;

        boolean found = false;

        while (!found) {

            // Choose a new location for our apple

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

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

            newCoord = new Coordinate(newX, newY);

 

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

            boolean collision = false;

            int snakelength = mSnakeTrail.size();

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

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

                    collision = true;

                }

            }

           

            int barrierListsnakelength = mBarrierList.size();

            for (int index = 0; index < barrierListsnakelength; index++) {

                if (mBarrierList.get(index).equals(newCoord)) {

                    collision = true;

                }

            }

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

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

            // and try again

            found = !collision;

        }

        if (newCoord == null) {

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

        }

        mBarrierList.add(newCoord);

    }

SnakeView.java

 

 

其實可以看得出來,我們寫一個全新的程序,和改動一個已有的程序,遵循的方式不一樣,當進行改動的時候,要儘量理解原來的設計意圖,以最小的變化和最貼近原先代碼的方式進行改動,就像穿上迷彩服在……好吧,我聽見有人打斷我了,跑題了,我承認……

 

那麼這個函數完工之後,你已經迫不及待了吧,那麼我們趕緊來做一個臨時更改,看看效果吧!!

 

8.        臨時更改

上次在第4次更改中,我們也曾經在這裏做過類似的事情,但那次是臨時更改,相信你已經在試驗之後,將臨時更改的內容移去了。

    private void initNewGame() {

        mSnakeTrail.clear();

        mAppleList.clear();

        mBarrierList.clear();                 

 

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

        // that's just turned north

        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));

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

        mNextDirection = NORTH;

        // Two apples to start with

        addRandomApple();

        addRandomApple();

 

        addRandomBarrier();

        addRandomBarrier();

 

        mMoveDelay = 600;

        mScore = 0;

    }

SnakeView.java

 

運行效果如下:

 

 

我們在每局開始的時候,隨機放置了兩個障礙物,蛇撞上就要死!!

一個天衣無縫的改動就這麼完成了!如果你堅持跟着文章走到這裏,恭喜,如果你只是看到這裏,建議你實際動手試一遍,那是完全不一樣的感受!

[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章