你若敢不辞冰雪把爱写成兵临城下的传奇,我就敢披荆斩棘为情写下流芳千古的永恒。

一直想自己动手做一个手机游戏,安装在自己的手机上,尽管应用或许会看起来很简单效果也不是很拉风,可是自己做的,那心情那感觉终究是不一样。今天,让我们一起探秘贪吃蛇游戏,用自己的双手玩转java代码打造属于自己的游戏。

贪吃蛇是一款足够经典的游戏。它的经典,在于用户操作的简单,在于技术实现的简介,在于他的经久不衰,在于它的独领风骚。

这里的贪吃蛇的android实现,是SDK Samples中的开源例程。可能各位都有看过-界面如下图啦。。。

 

 

 

作为一个刚入门或者还没入门的新手,着实花了我一些力气来理解这段代码。

对于各种不懂的地方,慢慢查询资料,对于新的方法,通过修改代码尝试效果。到现在终于能算个一知半解。

在代码中,对于自己有所收获的地方,我都做了相应的注释。

回过头来,觉得从这段代码中,能学到不少东西~~

包括android应用的基本架构,他的面向对象的思想,以及代码的简洁明了。

于是,我想到,何不将这些东西分享出来,如果碰巧对感兴趣的朋友们有搜帮助,那就更好了~

好了,闲话不说~代码和注释如下(处于对源码的敬意,原本的英文注释部分都没有删去~大家可以配合理解):

 

************************************************************************************************************************************

Snake工程中,总共有三个文件: *TileView是基于Android的View类实现的方块图类,用来支撑上层类的调用,绘制方块图的显 示界面。通过这些代码,能打之了解如何 扩展View,实现特色的界面效果。 *SnakeView调用了TileView,实现了游戏逻辑 和 具体的显示。 *Snake为主Activity类。

 

建议大家按照上面的顺序看三个文件,可能逻辑上更舒服一点~~

下面贴上代码和注释。

PS:  调试版本为android2.2。 其他版本应该也没问题,不过得用虚拟机。

 

TileView.java

  1.   

 

 

  1. package com.example.android.snake;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.graphics.Bitmap;  
  6. import android.graphics.Canvas;  
  7. import android.graphics.Paint;  
  8. import android.graphics.drawable.Drawable;  
  9. import android.util.AttributeSet;  
  10. import android.view.View;  
  11.   
  12.   
  13. /** 
  14.  * TileView: a View-variant designed for handling arrays of "icons" or other 
  15.  * drawables. 
  16.  *  
  17.  */  
  18.   
  19. public class TileView extends View {  
  20.   
  21.     /** 
  22.      * Parameters controlling the size of the tiles and their range within view. 
  23.      * Width/Height are in pixels, and Drawables will be scaled to fit to these 
  24.      * dimensions. X/Y Tile Counts are the number of tiles that will be drawn. 
  25.      */  
  26.   
  27.     protected static int mTileSize; //每个tile的边长的像素数量  
  28.   
  29.     protected static int mXTileCount; //屏幕内能容纳的 X方向上方块的总数量  
  30.     protected static int mYTileCount;//屏幕内能容纳的 Y方向上方块的总数量  
  31.   
  32.     private static int mXOffset; //原点坐标,按pixel计。  
  33.     private static int mYOffset;  
  34.   
  35.   
  36.     /** 
  37.      * A hash that maps integer handles specified by the subclasser to the 
  38.      * drawable that will be used for that reference 
  39.      * 存储着不同种类的bitmap图。通过resetTiles,loadTile,将游戏中的方块加载到这个数组。 
  40.      * 可以理解为 砖块字典 
  41.      */  
  42.     private Bitmap[] mTileArray;      
  43.   
  44.     /** 
  45.      * A two-dimensional array of integers in which the number represents the 
  46.      * index of the tile that should be drawn at that locations 
  47.      * 存储整个界面内每个tile位置应该绘制的tile。 
  48.      * 可看作是我们直接操作的画布。 
  49.      * 通过setTile、clearTile 进行图形显示的修改操作。  
  50.      *  
  51.      */  
  52.     private int[][] mTileGrid;   
  53.   
  54.     //画笔,canvas的图形绘制,需要画笔Paint实现。  
  55.     private final Paint mPaint = new Paint();  
  56.   
  57.       
  58.     public TileView(Context context, AttributeSet attrs, int defStyle) {  
  59.         super(context, attrs, defStyle);  
  60.         //使用TypedArray,获取在attrs.xml中为TileView定义的新属性tileSize 。参考: http://weizhulin.blog.51cto.com/1556324/311453  
  61.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);  
  62.         mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);  
  63.         a.recycle();  
  64.     }  
  65.   
  66.     public TileView(Context context, AttributeSet attrs) {  
  67.         super(context, attrs);  
  68.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);  
  69.         mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);  
  70.         a.recycle();  
  71.     }  
  72.   
  73.       
  74.       
  75.     /** 
  76.      * Rests the internal array of Bitmaps used for drawing tiles, and 
  77.      * sets the maximum index of tiles to be inserted 
  78.      * 重置清零mTileArray,在游戏初始的时候使用。 
  79.      * 即清空砖块字典 
  80.      * @param tilecount 
  81.      */  
  82.     public void resetTiles(int tilecount) {  
  83.         mTileArray = new Bitmap[tilecount];  
  84.     }  
  85.   
  86.       
  87.     /* 
  88.      * 当改变屏幕大小尺寸时,同时修改tile的相关计数指标。 
  89.      */  
  90.       
  91.     @Override  
  92.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  93.         mXTileCount = (int) Math.floor(w / mTileSize);  
  94.         mYTileCount = (int) Math.floor(h / mTileSize);  
  95.   
  96.         //mXOffset mYOffset是绘图的起点坐标。  
  97.         mXOffset = ((w - (mTileSize * mXTileCount)) / 2);  
  98.         mYOffset = ((h - (mTileSize * mYTileCount)) / 2);  
  99.   
  100.         mTileGrid = new int[mXTileCount][mYTileCount];  
  101.         clearTiles();  
  102.     }  
  103.   
  104.       
  105.     /** 
  106.      * Function to set the specified Drawable as the tile for a particular 
  107.      * integer key. 
  108.      * 加载具体的砖块图片 到 砖块字典。 
  109.      * 即将对应的砖块的图片 对应的加载到 mTileArray数组中 
  110.      * @param key 
  111.      * @param tile 
  112.      */  
  113.     public void loadTile(int key, Drawable tile) {  
  114.         //这里做了一个 Drawable 到 bitmap 的转换。由于外部程序使用的时候是直接读取资源文件中的图片,  
  115.         //是drawable格式,而我们的数组是bitmap格式,方便最终的绘制。所以,需要进行一次到 bitmap的转换。  
  116.         Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888);  
  117.         Canvas canvas = new Canvas(bitmap);  
  118.         tile.setBounds(00, mTileSize, mTileSize);  
  119.         tile.draw(canvas);  
  120.           
  121.         mTileArray[key] = bitmap;  
  122.     }  
  123.   
  124.     /** 
  125.      * Used to indicate that a particular tile (set with loadTile and referenced 
  126.      * by an integer) should be drawn at the given x/y coordinates during the 
  127.      * next invalidate/draw cycle. 
  128.      * 在相应的坐标位置绘制相应的砖块 
  129.      * 记得哦,mTileGrid其实就是我们直接操作的画布。 
  130.      * @param tileindex 
  131.      * @param x 
  132.      * @param y 
  133.      */  
  134.     public void setTile(int tileindex, int x, int y) {  
  135.         mTileGrid[x][y] = tileindex;  
  136.     }  
  137.   
  138.     /** 
  139.      * Resets all tiles to 0 (empty) 
  140.      * 清空图形显示。 
  141.      * 用以更新画面。 
  142.      * 调用了绘图的setTile()。 
  143.      */  
  144.     public void clearTiles() {  
  145.         for (int x = 0; x < mXTileCount; x++) {  
  146.             for (int y = 0; y < mYTileCount; y++) {  
  147.                 setTile(0, x, y);  
  148.             }  
  149.         }  
  150.     }  
  151.   
  152. /* 
  153.  * 将我们直接操作的画布绘制到手机界面上! 
  154.  * @see android.view.View#onDraw(android.graphics.Canvas) 
  155.  */  
  156.     @Override  
  157.     public void onDraw(Canvas canvas) {  
  158.         super.onDraw(canvas);  
  159.         for (int x = 0; x < mXTileCount; x += 1) {  
  160.             for (int y = 0; y < mYTileCount; y += 1) {  
  161.                 if (mTileGrid[x][y] > 0) {  
  162.                     canvas.drawBitmap(mTileArray[mTileGrid[x][y]],   
  163.                             mXOffset + x * mTileSize,  
  164.                             mYOffset + y * mTileSize,  
  165.                             mPaint);  
  166.                 }  
  167.             }  
  168.         }  
  169.     }  
  170.   
  171. }  

 

 

 

 

SnakeView.java

 

  1. package com.example.android.snake;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Random;  
  5.   
  6. import android.content.Context;  
  7. import android.content.res.Resources;  
  8. import android.os.Handler;  
  9. import android.os.Message;  
  10. import android.util.AttributeSet;  
  11. import android.os.Bundle;  
  12. import android.util.Log;  
  13. import android.view.KeyEvent;  
  14. import android.view.View;  
  15. import android.widget.TextView;  
  16.   
  17. /** 
  18.  * SnakeView: implementation of a simple game of Snake 
  19.  */  
  20. public class SnakeView extends TileView {  
  21.   
  22.     private static final String TAG = "SnakeView";  
  23.   
  24.     /** 
  25.      * Current mode of application: READY to run, RUNNING, or you have already 
  26.      * lost. static final ints are used instead of an enum for performance 
  27.      * reasons. 
  28.      * 游戏的四种状态。初始时为 预备开始的状态。 
  29.      */  
  30.     private int mMode = READY;      
  31.     public static final int PAUSE = 0;  //暂停  
  32.     public static final int READY = 1;  //准备好了,预备开始  
  33.     public static final int RUNNING = 2;//正在运行  
  34.     public static final int LOSE = 3;   //结束,输了游戏  
  35.   
  36.     /** 
  37.      * Current direction the snake is headed. 
  38.      * 蛇体运动的方向标识。 
  39.      */  
  40.     private int mDirection = NORTH;  
  41.     private int mNextDirection = NORTH;  
  42.     private static final int NORTH = 1;  
  43.     private static final int SOUTH = 2;  
  44.     private static final int EAST = 3;  
  45.     private static final int WEST = 4;  
  46.   
  47.     /** 
  48.      * Labels for the drawables that will be loaded into the TileView class 
  49.      * 游戏中仅有的三种砖块对应的数值。 
  50.      */   
  51.     private static final int RED_STAR = 1;  
  52.     private static final int YELLOW_STAR = 2;  
  53.     private static final int GREEN_STAR = 3;  
  54.   
  55.     /** 
  56.      * mScore: used to track the number of apples captured mMoveDelay: number of 
  57.      * milliseconds between snake movements. This will decrease as apples are 
  58.      * captured. 
  59.      */  
  60.     private long mScore = 0;   //记录获得的分数。  
  61.     private long mMoveDelay = 600;  //每移动一步的延时。初始时设置为600ms,以后每吃一个果子,打个9折  
  62.                                 //造成的结果是速度越来越快。  
  63.       
  64.     /** 
  65.      * mLastMove: tracks the absolute time when the snake last moved, and is used 
  66.      * to determine if a move should be made based on mMoveDelay. 
  67.      * 记录上次移动的确切时间。 
  68.      * 同mMoveDelay一起处理与用户的异步操作的协同问题。 
  69.      */  
  70.     private long mLastMove;  
  71.       
  72.       
  73.     /** 
  74.      * mStatusText: text shows to the user in some run states 
  75.      * 用来显示游戏状态的TextView 
  76.      */  
  77.     private TextView mStatusText;  
  78.   
  79.     /** 
  80.      * mSnakeTrail: a list of Coordinates that make up the snake's body 
  81.      * mAppleList: the secret location of the juicy apples the snake craves. 
  82.      * 两个链表,分别用来存储 蛇体 和 果子的坐标。 
  83.      * 每次蛇体的运动,蛇体的增长,产生新的苹果,被吃掉苹果,都会在这里记录。 
  84.      */  
  85.     private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();  
  86.     private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();  
  87.   
  88.     /** 
  89.      * Everyone needs a little randomness in their life 
  90.      * 随机数生成器。用来产生随机的苹果。在addRandomApple()中使用。 
  91.      */  
  92.     private static final Random RNG = new Random();  
  93.   
  94.     /** 
  95.      * Create a simple handler that we can use to cause animation to happen.  We 
  96.      * set ourselves as a target and we can use the sleep() 
  97.      * function to cause an update/invalidate to occur at a later date. 
  98.      * 用Handler机制实现定时刷新。 
  99.      * 为什么使用Handler呢?大家可以参考 android 的线程模型(注意UI线程不是线程安全的~) 
  100.      * 具体使用方法网上的资源很多,在此不赘述~ 
  101.      */  
  102.     private RefreshHandler mRedrawHandler = new RefreshHandler();  
  103.   
  104.     class RefreshHandler extends Handler {  
  105.   
  106.         //获取消息并处理  
  107.         @Override  
  108.         public void handleMessage(Message msg) {  
  109.             SnakeView.this.update();  
  110.             SnakeView.this.invalidate(); //刷新view为基类的界面  
  111.         }  
  112.   
  113.         //定时发送消息给UI线程,以此达到更新的效果。  
  114.         public void sleep(long delayMillis) {  
  115.             this.removeMessages(0); //清空消息队列,Handler进入对新消息的等待  
  116.             sendMessageDelayed(obtainMessage(0), delayMillis); //定时发送新消息,激活handler  
  117.         }  
  118.     };  
  119.   
  120.   
  121.       
  122.     public SnakeView(Context context, AttributeSet attrs) {  
  123.         super(context, attrs);  
  124.         initSnakeView();  //构造函数中,别忘了,初始化游戏~  
  125.    }  
  126.   
  127.     public SnakeView(Context context, AttributeSet attrs, int defStyle) {  
  128.         super(context, attrs, defStyle);  
  129.         initSnakeView();  
  130.     }  
  131.   
  132.     //初始化SnakeView类,注意,这根初始化游戏是不一样的。  
  133.     private void initSnakeView() {  
  134.         setFocusable(true); //设置焦点,由于存在 文字界面 和 游戏界面的跳转。这个focus是不可或缺的。  
  135.   
  136.         //取得资源中的图片,加载到 砖块字典 中。  
  137.         Resources r = this.getContext().getResources();  
  138.         resetTiles(4);  
  139.         loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));  
  140.         loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));  
  141.         loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));  
  142.           
  143.     }  
  144.       
  145.     //如果不是从暂停中回复,就绪要 初始化游戏了。  
  146.     private void initNewGame() {  
  147.         //清空保存蛇体和果子的数据结构。  
  148.         mSnakeTrail.clear();  
  149.         mAppleList.clear();  
  150.   
  151.         // For now we're just going to load up a short default eastbound snake  
  152.         // that's just turned north  
  153.         // 设定初始状态的蛇体的位置。   
  154.           
  155.         mSnakeTrail.add(new Coordinate(77));  
  156.         mSnakeTrail.add(new Coordinate(67));  
  157.         mSnakeTrail.add(new Coordinate(57));  
  158.         mSnakeTrail.add(new Coordinate(47));  
  159.         mSnakeTrail.add(new Coordinate(37));  
  160.         mSnakeTrail.add(new Coordinate(27));  
  161.         mNextDirection = NORTH;  
  162.   
  163.         // Two apples to start with  
  164.         addRandomApple();  
  165.         addRandomApple();  
  166.   
  167.         mMoveDelay = 600;  
  168.         mScore = 0;  
  169.     }  
  170.   
  171.   
  172.     /** 
  173.      * Given a ArrayList of coordinates, we need to flatten them into an array of 
  174.      * ints before we can stuff them into a map for flattening and storage. 
  175.      *  
  176.      * @param cvec : a ArrayList of Coordinate objects 
  177.      * @return : a simple array containing the x/y values of the coordinates 
  178.      * as [x1,y1,x2,y2,x3,y3...】 
  179.      * 在游戏暂停时,需要通过Bundle方式保存数据。见saveState()。 
  180.      * Bundle支持简单的数组。 
  181.      * 所以需要将我们的部分数据结构,如蛇体和苹果位置的数组,转换成简单的序列化的int数组。 
  182.      */  
  183.     private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {  
  184.         int count = cvec.size();  
  185.         int[] rawArray = new int[count * 2];  
  186.         for (int index = 0; index < count; index++) {  
  187.             Coordinate c = cvec.get(index);  
  188.             rawArray[2 * index] = c.x;  
  189.             rawArray[2 * index + 1] = c.y;  
  190.         }  
  191.         return rawArray;  
  192.     }  
  193.   
  194.     /** 
  195.      * Save game state so that the user does not lose anything 
  196.      * if the game process is killed while we are in the  
  197.      * background. 
  198.      * 在意外情况下,暂时性保存游戏数据,在下次打开游戏时,可以继续游戏。如来电话了。 
  199.      * @return a Bundle with this view's state 
  200.      */  
  201.     public Bundle saveState() {  
  202.         Bundle map = new Bundle();  
  203.   
  204.         map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));  
  205.         map.putInt("mDirection", Integer.valueOf(mDirection));  
  206.         map.putInt("mNextDirection", Integer.valueOf(mNextDirection));  
  207.         map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));  
  208.         map.putLong("mScore", Long.valueOf(mScore));  
  209.         map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));  
  210.   
  211.         return map;  
  212.     }  
  213.   
  214.     /** 
  215.      * Given a flattened array of ordinate pairs, we reconstitute them into a 
  216.      * ArrayList of Coordinate objects 
  217.      * 是coordArrayListToArray()的逆过程,用来读取保存在Bundle中的数据。 
  218.      * @param rawArray : [x1,y1,x2,y2,...] 
  219.      * @return a ArrayList of Coordinates 
  220.      */  
  221.     private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {  
  222.         ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();  
  223.   
  224.         int coordCount = rawArray.length;  
  225.         for (int index = 0; index < coordCount; index += 2) {  
  226.             Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);  
  227.             coordArrayList.add(c);  
  228.         }  
  229.         return coordArrayList;  
  230.     }  
  231.   
  232.     /** 
  233.      * Restore game state if our process is being relaunched 
  234.      * 回复游戏数据。是saveState()的逆过程 
  235.      * @param icicle a Bundle containing the game state 
  236.      */  
  237.     public void restoreState(Bundle icicle) {  
  238.         setMode(PAUSE);  
  239.   
  240.         mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));  
  241.         mDirection = icicle.getInt("mDirection");  
  242.         mNextDirection = icicle.getInt("mNextDirection");  
  243.         mMoveDelay = icicle.getLong("mMoveDelay");  
  244.         mScore = icicle.getLong("mScore");  
  245.         mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));  
  246.     }  
  247.   
  248.     /* 
  249.      * handles key events in the game. Update the direction our snake is traveling 
  250.      * based on the DPAD. Ignore events that would cause the snake to immediately 
  251.      * turn back on itself. 
  252.      * 按键的监听。 
  253.      * 现在大多数的android手机都没有按键了。 
  254.      * 笔者就是在自己的模拟机上才能正常的使用这款小游戏的 - -# 
  255.      * @see android.view.View#onKeyDown(int, android.os.KeyEvent) 
  256.      */  
  257.     @Override  
  258.     public boolean onKeyDown(int keyCode, KeyEvent msg) {  
  259.         //这里是游戏的基本逻辑。如果你还没尝试一下它,先玩玩再说吧。那有助于你对代码的理解~  
  260.         if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {  
  261.             if (mMode == READY | mMode == LOSE) {  
  262.                 /* 
  263.                  * At the beginning of the game, or the end of a previous one, 
  264.                  * we should start a new game. 
  265.                  */  
  266.                 initNewGame();  
  267.                 setMode(RUNNING);  
  268.                 update(); //update()实现了对游戏数据的更新,是整个游戏的推动力。  
  269.                 return (true);  
  270.             }  
  271.   
  272.             if (mMode == PAUSE) {  
  273.                 /* 
  274.                  * If the game is merely paused, we should just continue where 
  275.                  * we left off. 
  276.                  */  
  277.                 setMode(RUNNING);  
  278.                 update();  
  279.                 return (true);  
  280.             }  
  281.   
  282.             if (mDirection != SOUTH) {  //如果按键的方向 跟蛇本身的运动方向完全相反,则无法执行  
  283.                 mNextDirection = NORTH;    
  284.             }  
  285.             return (true);  
  286.         }  
  287.   
  288.         if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {  
  289.             if (mDirection != NORTH) {  
  290.                 mNextDirection = SOUTH;  
  291.             }  
  292.             return (true);  
  293.         }  
  294.   
  295.         if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {  
  296.             if (mDirection != EAST) {  
  297.                 mNextDirection = WEST;  
  298.             }  
  299.             return (true);  
  300.         }  
  301.   
  302.         if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {  
  303.             if (mDirection != WEST) {  
  304.                 mNextDirection = EAST;  
  305.             }  
  306.             return (true);  
  307.         }  
  308.   
  309.         return super.onKeyDown(keyCode, msg);  
  310.     }  
  311.   
  312.     /** 
  313.      * Sets the TextView that will be used to give information (such as "Game 
  314.      * Over" to the user. 
  315.      * 起初不明白这个方法有什么作用。删除了以后才发现错误。Snake类会调用到它,来绑定到相应的textview. 
  316.      */  
  317.     public void setTextView(TextView newView) {    
  318.         mStatusText = newView;  
  319.     }  
  320.   
  321.     /** 
  322.      * Updates the current mode of the application (RUNNING or PAUSED or the like) 
  323.      * as well as sets the visibility of textview for notification 
  324.      *  
  325.      * @param newMode 
  326.      */  
  327.     public void setMode(int newMode) {  
  328.         int oldMode = mMode;  
  329.         mMode = newMode;  
  330.   
  331.         if (newMode == RUNNING & oldMode != RUNNING) {  
  332.             mStatusText.setVisibility(View.INVISIBLE); //游戏开始后,将TextView的文字显示设置为不可见。  
  333.             update(); //注意到,在initGame中也有update(),不过放心~ 多次重复 update不会影响效果的,  
  334.                         //蛇的移动有mLastMove 和 mMoveDelay 来校验。这会在Update()中体现。  
  335.                         //当然,经过实验,注释掉这个update()似乎不会影响结果噢。  
  336.             return;  
  337.         }  
  338.   
  339.         Resources res = getContext().getResources();  
  340.         CharSequence str = "";  
  341.         if (newMode == PAUSE) {  
  342.             str = res.getText(R.string.mode_pause);  
  343.         }  
  344.         if (newMode == READY) {  
  345.             str = res.getText(R.string.mode_ready);  
  346.         }  
  347.         if (newMode == LOSE) {  
  348.             str = res.getString(R.string.mode_lose_prefix) + mScore  
  349.                   + res.getString(R.string.mode_lose_suffix);  
  350.         }  
  351.   
  352.         mStatusText.setText(str);  
  353.         mStatusText.setVisibility(View.VISIBLE);  
  354.     }  
  355.   
  356.     /** 
  357.      * Selects a random location within the garden that is not currently covered 
  358.      * by the snake. Currently _could_ go into an infinite loop if the snake 
  359.      * currently fills the garden, but we'll leave discovery of this prize to a 
  360.      * truly excellent snake-player. 
  361.      * 在地图上随机的增加果子。注意苹果的位置不可以是蛇体所在噢~这里有个小bug,没有检测 
  362.      * 产生的果子位置 可能与 另一个果子位置重合。 
  363.      * 新产生的果子的坐标会增加到mApplist的数组上。 
  364.      */  
  365.     private void addRandomApple() {  
  366.         Coordinate newCoord = null;  
  367.         boolean found = false;  
  368.         while (!found) {  
  369.             // Choose a new location for our apple  
  370.             //注意别产生在边框上的果子  
  371.             int newX = 1 + RNG.nextInt(mXTileCount - 2);  
  372.             int newY = 1 + RNG.nextInt(mYTileCount - 2);  
  373.             newCoord = new Coordinate(newX, newY);  
  374.   
  375.             // Make sure it's not already under the snake  
  376.             boolean collision = false;  
  377.             int snakelength = mSnakeTrail.size();  
  378.             for (int index = 0; index < snakelength; index++) {  
  379.                 if (mSnakeTrail.get(index).equals(newCoord)) {  
  380.                     collision = true;  
  381.                 }  
  382.             }  
  383.             // if we're here and there's been no collision, then we have  
  384.             // a good location for an apple. Otherwise, we'll circle back  
  385.             // and try again  
  386.             found = !collision;  
  387.         }  
  388.         if (newCoord == null) {  
  389.             Log.e(TAG, "Somehow ended up with a null newCoord!");  
  390.         }  
  391.         mAppleList.add(newCoord);  
  392.     }  
  393.   
  394.   
  395.     /** 
  396.      * Handles the basic update loop, checking to see if we are in the running 
  397.      * state, determining if a move should be made, updating the snake's location. 
  398.      * 刷新游戏状态。每次游戏画面的更新、游戏数据的更新,都是依靠这个update()来完成的。 
  399.      */  
  400.     public void update() {  
  401.         if (mMode == RUNNING) {  
  402.             long now = System.currentTimeMillis();  
  403.   
  404.             if (now - mLastMove > mMoveDelay) {  //这里是对蛇体游戏刚开始时连续的两个移动速率的控制  
  405.                                     //主要作用应该是mMode变化时,对update()正确效果的保障。  
  406.                 clearTiles();       //清空 界面画布。        
  407.                 updateWalls();      //重新绘制墙壁  
  408.                 updateSnake();     //对蛇的 游戏逻辑 的处理 以及绘制  
  409.                 updateApples();   //对果子的 游戏逻辑 的处理 以及绘制  
  410.                 mLastMove = now;  
  411.             }  
  412.             mRedrawHandler.sleep(mMoveDelay);   //利用Handler进行 定时刷新的控制  
  413.         }  
  414.   
  415.     }  
  416.   
  417.     /** 
  418.      * Draws some walls. 
  419.      * 用setTile绘制墙壁 
  420.      */  
  421.     private void updateWalls() {  
  422.         for (int x = 0; x < mXTileCount; x++) {  
  423.             setTile(GREEN_STAR, x, 0);  
  424.             setTile(GREEN_STAR, x, mYTileCount - 1);  
  425.         }  
  426.         for (int y = 1; y < mYTileCount - 1; y++) {  
  427.             setTile(GREEN_STAR, 0, y);  
  428.             setTile(GREEN_STAR, mXTileCount - 1, y);  
  429.         }  
  430.     }  
  431.   
  432.     /** 
  433.      * Draws some apples. 
  434.      * 绘制果子 
  435.      */  
  436.     private void updateApples() {  
  437.         for (Coordinate c : mAppleList) {  
  438.             setTile(YELLOW_STAR, c.x, c.y);  
  439.         }  
  440.     }  
  441.   
  442.     /** 
  443.      * Figure out which way the snake is going, see if he's run into anything (the 
  444.      * walls, himself, or an apple). If he's not going to die, we then add to the 
  445.      * front and subtract from the rear in order to simulate motion. If we want to 
  446.      * grow him, we don't subtract from the rear. 
  447.      *  
  448.      */  
  449.     private void updateSnake() {  
  450.         boolean growSnake = false;  //吃过果子的蛇会长长。这个变量即为它的标记。  
  451.   
  452.         // grab the snake by the head  
  453.         Coordinate head = mSnakeTrail.get(0);  //头部很重要,只有头部可能碰到果子。  
  454.         Coordinate newHead = new Coordinate(11); //蛇下一步一定会前移,也就试newHead。长长只会从尾部增加。  
  455.                         //那么为啥不用Coordinate newHead 呢?反正肯定会给他赋值的。  
  456.                         //注意到之后咱们的程序是在switch语句中给newHead赋值的,这个是编译无法通过的~  
  457.         mDirection = mNextDirection;  
  458.   
  459.         switch (mDirection) {  
  460.         case EAST: {  
  461.             newHead = new Coordinate(head.x + 1, head.y);  
  462.             break;  
  463.         }  
  464.         case WEST: {  
  465.             newHead = new Coordinate(head.x - 1, head.y);  
  466.             break;  
  467.         }  
  468.         case NORTH: {  
  469.             newHead = new Coordinate(head.x, head.y - 1);  
  470.             break;  
  471.         }  
  472.         case SOUTH: {  
  473.             newHead = new Coordinate(head.x, head.y + 1);  
  474.             break;  
  475.         }  
  476.         }  
  477.   
  478.         // Collision detection  
  479.         // For now we have a 1-square wall around the entire arena  
  480.         //撞墙检测  
  481.         if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)  
  482.                 || (newHead.y > mYTileCount - 2)) {  
  483.             setMode(LOSE);  
  484.             return;  
  485.   
  486.         }  
  487.   
  488.         // Look for collisions with itself  
  489.         //撞自己检测  
  490.         int snakelength = mSnakeTrail.size();  
  491.         for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {  
  492.             Coordinate c = mSnakeTrail.get(snakeindex);  
  493.             if (c.equals(newHead)) {  
  494.                 setMode(LOSE);  
  495.                 return;  
  496.             }  
  497.         }  
  498.   
  499.         // Look for apples  
  500.         //吃果子检测  
  501.         int applecount = mAppleList.size();  
  502.         for (int appleindex = 0; appleindex < applecount; appleindex++) {  
  503.             Coordinate c = mAppleList.get(appleindex);  
  504.             if (c.equals(newHead)) {  
  505.                 mAppleList.remove(c);  
  506.                 addRandomApple();  
  507.                   
  508.                 mScore++;  
  509.                 mMoveDelay *= 0.9;  
  510.   
  511.                 growSnake = true;  
  512.             }  
  513.         }  
  514.   
  515.         // push a new head onto the ArrayList and pull off the tail  
  516.         //前进  
  517.         mSnakeTrail.add(0, newHead);  
  518.         // except if we want the snake to grow  
  519.         if (!growSnake) {  
  520.             mSnakeTrail.remove(mSnakeTrail.size() - 1);  
  521.         }  
  522.   
  523.         //绘制新的蛇体  
  524.         int index = 0;  
  525.         for (Coordinate c : mSnakeTrail) {  
  526.             if (index == 0) {  
  527.                 setTile(YELLOW_STAR, c.x, c.y);  
  528.             } else {  
  529.                 setTile(RED_STAR, c.x, c.y);  
  530.             }  
  531.             index++;  
  532.         }  
  533.   
  534.     }  
  535.   
  536.     /** 
  537.      * Simple class containing two integer values and a comparison function. 
  538.      * There's probably something I should use instead, but this was quick and 
  539.      * easy to build. 
  540.      * 这是坐标点的类。很简单的存储XY坐标。 
  541.      */  
  542.     private class Coordinate {  
  543.         public int x;  
  544.         public int y;  
  545.   
  546.         public Coordinate(int newX, int newY) {  
  547.             x = newX;  
  548.             y = newY;  
  549.         }  
  550.   
  551.         public boolean equals(Coordinate other) {  
  552.             if (x == other.x && y == other.y) {  
  553.                 return true;  
  554.             }  
  555.             return false;  
  556.         }  
  557.   
  558.         @Override  
  559.         public String toString() {  
  560.             return "Coordinate: [" + x + "," + y + "]";  
  561.         }  
  562.     }  
  563.       
  564. }  

 

 

Snake.java

 

  1. package com.example.android.snake;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.Window;  
  6. import android.widget.TextView;  
  7.   
  8. /** 
  9.  * Snake: a simple game that everyone can enjoy. 
  10.  * This is an implementation of the classic Game "Snake", in which you control a 
  11.  * serpent roaming around the garden looking for apples. Be careful, though, 
  12.  * because when you catch one, not only will you become longer, but you'll move 
  13.  * faster. Running into yourself or the walls will end the game. 
  14.  */  
  15.   
  16. public class Snake extends Activity {  
  17.   
  18.   
  19.     private SnakeView mSnakeView;  
  20.       
  21.     private static String ICICLE_KEY = "snake-view";  
  22.   
  23.     /** 
  24.      * Called when Activity is first created. Turns off the title bar, sets up 
  25.      * the content views, and fires up the SnakeView. 
  26.      *  
  27.      */  
  28.     @Override  
  29.     public void onCreate(Bundle savedInstanceState) {  
  30.         super.onCreate(savedInstanceState);  
  31.   
  32.         setContentView(R.layout.snake_layout);  
  33.   
  34.         mSnakeView = (SnakeView) findViewById(R.id.snake);  
  35.         mSnakeView.setTextView((TextView) findViewById(R.id.text));  
  36.   
  37.         if (savedInstanceState == null) {  
  38.             // We were just launched -- set up a new game  
  39.             mSnakeView.setMode(SnakeView.READY);  
  40.         } else {  
  41.             // We are being restored  
  42.             Bundle map = savedInstanceState.getBundle(ICICLE_KEY);  
  43.             if (map != null) {  
  44.                 mSnakeView.restoreState(map);  
  45.             } else {  
  46.                 mSnakeView.setMode(SnakeView.PAUSE);  
  47.             }  
  48.         }  
  49.     }  
  50.   
  51.     @Override  
  52.     protected void onPause() {  
  53.         super.onPause();  
  54.         // Pause the game along with the activity  
  55.         mSnakeView.setMode(SnakeView.PAUSE);  
  56.     }  
  57.   
  58.     @Override  
  59.     public void onSaveInstanceState(Bundle outState) {  
  60.         //Store the game state  
  61.         outState.putBundle(ICICLE_KEY, mSnakeView.saveState());  
  62.     }  
  63.   
  64. }  
posted on 2013-11-02 15:58  Love_in_paradise  阅读(3960)  评论(0编辑  收藏  举报