小多的Android入门教程系列---之1---贪吃蛇改进版

来源:互联网 发布:申请网络出版要多久 编辑:程序博客网 时间:2024/05/16 04:48

本教程PDF版下载

如果上面的链接失效,请用新浪微盘下载:http://vdisk.weibo.com/s/pcDT

 

Android_Tutorial-Advanced_Snake

小多的Android入门教程系列

1

贪吃蛇改进版

 

 

 

背景

android 2.3.3 SDK 10

Eclipse 3.5.1

 

反馈

xiaoduo.lian@gmail.com

 

  

时间

事件

版本

人物

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

 

运行效果如下:

 

 

我们在每局开始的时候,随机放置了两个障碍物,蛇撞上就要死!!

一个天衣无缝的改动就这么完成了!如果你坚持跟着文章走到这里,恭喜,如果你只是看到这里,建议你实际动手试一遍,那是完全不一样的感受!

xiaoduo.lian@gmail.com

原创粉丝点击