一、游戏介绍: 贪吃蛇是一个古老而经典的游戏,讲的是在一个美丽的花园里,有一只爱吃苹果的小蛇,它每吃一个苹果都会变得更大更快,只是它有个致命的弱点,如果它想逃出花园或者一不小心咬到自身就会立刻死亡。作为玩家,你的目标是操纵小蛇吃掉更多的苹果而不死掉。
二、游戏截图:
a、开始画面
b、游戏暂停
c、游戏结束
三、代码总体分析
1、布局方面:我们使用一个FrameLayout绝对定位,在里面放两个和父元素一样大的子元素,一个是我们自定义的View:snake,这个元素就是游戏界面,我们通过不停的操纵和重绘该View来完成游戏交互,一个是在中间有蓝色文本的相对定位布局框架,它用来显示游戏状态。
2、类设计方面:贪吃蛇游戏使用了三个主类和两个内部类。
TitleView :一个游戏贴片(Tile)类,是我们的自定义View。是它实现游戏画面的贴片计算、贴片的种类定义、贴片的绘制等和Tile相关的方法。
SnakeView :是TileView的子类,是游戏的主体类。定义游戏状态、操作方式、游戏规则、初始化游戏、刷新视图、处理打电话导致游戏暂停时保存状态,接收焦点时恢复状态等等工作都在这个类中进行,注意它还是个View。
MainActivity :游戏窗口类,负责载入SnakeView,为SnakeView服务。
Snake.RefreshHandler : Handler类,刷新View
Snake.Coordinate : 坐标类,简化问题
四、代码阅读:
为了你阅读方便,我对原有代码做了简化、汉化和增加注释这三件事。你读起来应该会轻松许多。废话不说了,还是多给你一些时间消化代码。我提几个问题你看代码时不妨思考思考。
1、AndroidManifest.xml
1 | <? xml version = "1.0" encoding = "utf-8" ?> |
请留意android:configChanges=”keyboardHidden|orientation”这句话,这里有一个onConfigurationChanged()方法的用法问题,你了解吗?是不是自己先研究一下?当然,你也可以等我下一讲讲解这个问题。
2、res/values/strings.xml
1 | <? xml version = "1.0" encoding = "utf-8" ?> |
我们在以往很少用Strings.xml来管理字符串资源,都是自己写死进去了,这次为什么单独写了?
3、res/layout/main.xml
1 | <? xml version = "1.0" encoding = "utf-8" ?> |
4、TileView
001 | package android.basic.lesson48; |
003 | import android.content.Context; |
004 | import android.graphics.Bitmap; |
005 | import android.graphics.Canvas; |
006 | import android.graphics.Paint; |
007 | import android.graphics.drawable.Drawable; |
008 | import android.util.AttributeSet; |
009 | import android.util.Log; |
010 | import android.view.View; |
012 | public class TileView extends View { |
014 | private static final String tag = "yao" ; |
017 | protected static int mTileSize = 20 ; |
020 | protected static int mXTileCount; |
022 | protected static int mYTileCount; |
025 | private static int mXOffset; |
027 | private static int mYOffset; |
030 | private Bitmap[] mTileArray; |
033 | private int [][] mTileGrid; |
036 | private final Paint mPaint = new Paint(); |
039 | public TileView(Context context, AttributeSet attrs) { |
040 | super (context, attrs); |
041 | Log.i(tag, "TileView Constructor" ); |
042 | Log.i(tag, "mTileSize=" + mTileSize); |
046 | public void resetTiles( int tilecount) { |
047 | mTileArray = new Bitmap[tilecount]; |
052 | protected void onSizeChanged( int w, int h, int oldw, int oldh) { |
054 | Log.i(tag, "onSizeChanged," + "w=" + w + " h=" + h + " oldw=" + oldw + " oldh=" + oldh); |
057 | mXTileCount = ( int ) Math.floor(w / mTileSize); |
058 | mYTileCount = ( int ) Math.floor(h / mTileSize); |
059 | Log.i(tag, "mXTileCount=" + mXTileCount); |
060 | Log.i(tag, "mYTileCount=" + mYTileCount); |
063 | mXOffset = ((w - (mTileSize * mXTileCount)) / 2 ); |
066 | mYOffset = ((h - (mTileSize * mYTileCount)) / 2 ); |
068 | Log.i(tag, "mXOffset=" + mXOffset); |
069 | Log.i(tag, "mYOffset=" + mYOffset); |
072 | mTileGrid = new int [mXTileCount][mYTileCount]; |
079 | public void loadTile( int key, Drawable tile) { |
080 | Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888); |
081 | Canvas canvas = new Canvas(bitmap); |
082 | tile.setBounds( 0 , 0 , mTileSize, mTileSize); |
086 | mTileArray[key] = bitmap; |
090 | public void clearTiles() { |
091 | Log.i(tag, "TileView.clearTiles" ); |
092 | for ( int x = 0 ; x < mXTileCount; x++) { |
093 | for ( int y = 0 ; y < mYTileCount; y++) { |
101 | public void setTile( int tileindex, int x, int y) { |
102 | mTileGrid[x][y] = tileindex; |
107 | public void onDraw(Canvas canvas) { |
109 | Log.i(tag, "onDraw" ); |
110 | super .onDraw(canvas); |
116 | for ( int x = 0 ; x < mXTileCount; x++) { |
117 | for ( int y = 0 ; y < mYTileCount; y++) { |
119 | if (mTileGrid[x][y] > 0 ) { |
120 | bmp = mTileArray[mTileGrid[x][y]]; |
121 | left = x * mTileSize + mXOffset; |
122 | top = y * mTileSize + mYOffset; |
124 | canvas.drawBitmap(bmp, left, top, mPaint); |