这个一个每个人都喜欢的简单的小游戏
Snake是游戏的实现类,通过控制小蛇在花园中游走寻找苹果,注意,每吃掉一个苹果,小蛇身体不但会变的更长,还会移动的更敏捷,一旦撞上四周的墙或是碰到自己就会结束这次游戏。
代码结构分析:
Snake : 主游戏窗口
SnakeView : 游戏视图类,是实现游戏的主体类
TileView : 一个处理图片或其它
Coordinate :这是一个包括两个参数,用于记录X轴和Y轴简单类,其中包括一个比较函数.
RefshHandler :用于更新视图
Snake
这个类是游戏的主游戏窗口,是框架容器,
- 游戏的开始:oncreate此外的亮点是:setContentView(R.layout.snake_layout);设置窗口的布局文件,这里Android123给大家说明的是,这里 的snake_layout使用了自定义资源标签的方式,大家注意学习:这里我们可以看到来自SnakeView这个派生类的名称,由于Android内 部的R.资源不包含SnakeView类,所以我们必须写清楚Package,比如 com.exmple.android.snake.SnakeView 然后和其他控件使用一样,都是一个id然后宽度、高度、以及自定义的标签tileSize(尾巴长度),如下:
android:id="@+id/snake" android:layout_width="fill_parent" android:layout_height="fill_parent" tileSize="12" /> 在玩游戏过程中,如果有来电或是其它事件中断,这时应该把当前状态保存。以便返回时,还可以继续玩游戏。这就使用onSaveInstanceState实现保存当前状态。 注:此部分解析来自: Android示例程序Snake贪食蛇代码分析(三) TileView,从名称上不难看出这是一个方砖类,就是生成一个方块。 TileView使用了Android平台的显示基类View,View类是直接从java.lang.Object派生出来的,是各种控件比如 TextView、EditView的基类,当然包括我们的窗口Activity类,这些在SDK文档中都说的比较清楚。 在这个类中实现的游戏的实体,从游戏需求的角色,这个游戏包括了如下方面: 1. 随机产生小苹果,apples这里是复数,当然是是大于1个苹果,所以代码中产生了两个苹果。 2. 游戏状态管理 3. 画蛇,view的更新 4. 吃掉苹果后小蛇状态的变化 5. 画围墙 如果实现吃掉苹果小蛇速度变快? 关键是:mMoveDelay这个变量,以下是涉及到这个变量的函数, 每次吃掉苹果后,就会updateSnake一下,里面就把时间处理了:mMoveDelay *= 0.9; 小蛇其实就是一个数组,google的代码就是好注释写的清楚: /** * 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 private ArrayList mSnakeTrail:一个由Coordinates列表组织的蛇身. mAppleList:存放鲜美多汁的苹果列表 通过这个数组画出小蛇不难,问题是如何判断游戏是否结束? 问题是如何判断游戏的状态 所有以下的代码来自updateSnake 1. 吃了苹果 // 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; } } 2. 碰到了自己 // 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; } } 3.碰到墙了 // 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; } 在snakeView中定义了snake游戏的几种状态: private int mMode = READY; public static final int PAUSE = 0; //暂定 public static final int READY = 1; //准备好了 public static final int RUNNING = 2;//正在运行 public static final int LOSE = 3; //结束,输了游戏 各种游戏状态 rady running pausedlose 以上状态是通过:void setMode(int newMode)函数实现。 参看:http://yuefeng.javaeye.com/blog/206706 public class DrawView extends View { private final int mTileSize = 12; private final String TAG="DEMO"; private Paint pa = new Paint(); private Bitmap mTileArray; void loadImage(){ Resources r = this.getContext().getResources(); Drawable tile = r.getDrawable(R.drawable.redstar); Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); tile.setBounds(0, 0, mTileSize, mTileSize); tile.draw(canvas); mTileArray = bitmap; } public DrawView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub loadImage(); x = 10; y = 10; Log.i(TAG, "DrawView 2"); } //如果没有这段代码,大家可以试一下,改用上面的代码,程序能否通过。 public DrawView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub loadImage(); Log.i(TAG, "DrawView 3"); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.i(TAG, "onDraw 1"); canvas.drawBitmap(mTileArray, x, y, pa); } } 通过上面的文章可以画出小方块,但注意到SnakeView一共有两构造函数,那个函数才真正起作用呢? l public SnakeView(Context context, AttributeSet attrs) l public SnakeView(Context context, AttributeSet attrs, int defStyle) 通过加log的方式,判断是第一个构造函数起作用。 在第一个构造函数上方有一段注释:通过XML文件构造出SnakeView * Constructs a SnakeView based on inflation from XML 如果不使用这个构造函数,将会造成错误,可以试一下,看一下结果是怎样!本人得到如下的错误提示: 05-21 14:13:26.079: ERROR/AndroidRuntime(711): Caused by: java.lang.NoSuchMethodException: DrawView public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { Log.i(TAG, "KEYCODE_DPAD_UP"); } return super.onKeyDown(keyCode, event); } 实现小方块动起来的秘密在于view的public void invalidate () 大家可以参看SDK文档中关于View中Drawing中的一小段话 To force a view to draw, call invalidate().//为了让view重画,可以调用invalidate函数 方法: 在DrawView类中添加两个成员: private int x,y; 同时实现get,set方法, 在构造函数中添加他们的初始值, 修改onDraw @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.i(TAG, "onDraw 1"); canvas.drawBitmap(mTileArray, x, y, pa); } 4.修改onKeyDown函数 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { Log.i(TAG, "KEYCODE_DPAD_UP"); dv.setX(dv.getX()+10); dv.invalidate(); } return super.onKeyDown(keyCode, event); } 最后运行结果如下图: 附:网络上关于snake分析的三篇文章 上一次我们大概讲解了下Android SDK中的演示程序Snake游戏的主框架,今天我看来看下实现的基础类TileView,从名称上不难看出这是一个方砖类,就是生成一个方块。 TileView使用了Android平台的显示基类View,View类是直接从java.lang.Object派生出来的,是各种控件比如 TextView、EditView的基类,当然包括我们的窗口Activity类,这些在SDK文档中都说的比较清楚。 今天我们分析下最复杂的SnakeView的设计,它是派生于TileView方砖类,TileView构建是基于Android直接的显示类View,如果不明白的可以查看Android示例程序Snake贪食蛇代码分析(二)一文有关TileView类的实现, 首先我们看到整个游戏分 READY、PAUSE 、RUNNING 、LOSE四种mMode状态模式,分别对应准备、暂停、运行中、结束(死亡),毕竟贪食蛇没有胜利这个结果。 整个Snake的运行分4个方向,NORTH、SOUTH 、EAST、WEST分别对应了北、南、东、西四个方向,其中变量mDirection对应当 前的方向,而mNextDirection对应下个运行时的位置。这里星星分3种,使用的是一个Drawable图片,分RED_STAR、 YELLOW_STAR和GREEN_STAR三种颜色,游戏的星星出现位置由Random随机数生成器来决定,这里Random一般和Timer系统时 钟来随机生成更真实一些,通过一个Handler对象来控制画面的更新,使用了this.update();和this.invalidate();这两 个本地方法,Update和invaildate均为android.view.View类的本地方法。这里资源的使用通过Resources r = this.getContext().getResources();获取了r对象的实例,通过 r.getDrawable(R.drawable.redstar)获取资源名为redstar的资源,返回的是一个Drawable对象。 对于按键信息,直接重写View类的onKeyDown方法,这里KeyEvent传递的是按键的映射,比如KEYCODE_DPAD_UP向上,KeyEvent.KEYCODE_DPAD_DOWN向下等等,详细的查看SDK中的onKeyDown @Override if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {} } 整个游戏的控制流程就是上面这些,对于游戏的逻辑而言比较简单,这个贪食蛇并没有包含3D设计和类似Nokia的能量走廊、6边形轨迹,有空了我们一起来完善一个3D的贪食蛇游戏 下面这篇文章来自:http://www.jizhuomi.com/android/example/177.html 贪吃蛇剖析(一:暂停/继续、穿墙和全屏) 本文开始将为大家剖析Android示例程序-Snake贪吃蛇。贪吃蛇游戏大部分人都玩过,它是怎样实现的呢?Android示例程序给出了代码,下面进行详细分析。 游戏暂停/继续机制 由于原来的代码中在游戏运行时没有提供控制选项(比如暂停/继续),因此除非你死了,否则只能玩到底。我这里对代码进行一些修改,加入一个Option Menu来提供暂停/继续机制。 首先加入一个变量记录游戏当前状态: private int mState = SnakeView.READY; 然后重载onCreateOptionsMenu函数,创建一个控制菜单项,并对其进行处理,提供暂停/继续机制。 Java代码 修改后运行截图如下: 当然,这段代码还是有问题的,游戏刚开始时,必须先点击菜单确认,再按上方向键才能开始。(以后再来修改。。。) 穿墙贪食蛇 第二个修改是把这个普通的贪食蛇改成可以穿墙(呵呵,这样就可以不死了。。。)。想必要修改的就是撞墙检测那段代码?没错,就是下面这段! Java代码 原来的版本是发现撞墙时就直接判定为失败,我这里做个小小的修改,让它可以穿墙而去: Java代码 其实修改后的代码非常简单,就是把新节点的值做些处理,让它移动到对应的行/列的头部或尾部即可。下面就是修改后的“穿墙”贪食蛇的运行截图: 全屏机制 游戏一般都是全屏的,原始代码也考虑到标题栏太过难看了,于是使用下面这句代码就去掉了标题栏: Java代码 可还是没有达到全屏的效果,在Android1.5中实现全屏效果非常简单,只需要一句代码即可实现: Java代码 运行效果如下图所示: 贪吃蛇剖析(二:FrameLayout与RelativeLayout) 前一节中将了贪吃蛇Snake游戏的暂停/继续、穿墙和全屏功能的实现,本文继续分析此示例程序中体现的Android Layout机制。 1、FrameLayout 先来看官方文档的定义:FrameLayout是最简单的一个布局对象。它被定制为你屏幕上的一个空白备用区域,之后你可以在其中填充一个单一对象 — 比如,一张你要发布的图片。所有的子元素将会固定在屏幕的左上角;你不能为FrameLayout中的一个子元素指定一个位置。后一个子元素将会直接在前 一个子元素之上进行覆盖填充,把它们部份或全部挡住(除非后一个子元素是透明的)。 有点绕口而且难理解,下面还是通过一个实例来理解吧。我们仿照Snake项目中使用的界面一样,建立一个简单的FrameLayout,其中包含两个Views元素:ImageView和TextView,而后面的TextView还包含在一个RelativeLayout中。 XML/HTML代码 效果如下图所示: 2、UI优化 Android的tools目录下提供了许多实用工具,这里介绍其中一个用于查看当前UI结构视图的工具hierarchyviewer。打开tools/hierarchyviewer.bat后,查看上面这个示例的UI结构图可得: 我们可以很明显的看到由红色线框所包含的结构出现了两个framelayout节点,很明显这两个完全意义相同的节点造成了资源浪费(这里可以提醒大家在 开发工程中可以习惯性的通过hierarchyViewer查看当前UI资源的分配情况),那么如何才能解决这种问题呢(就当前例子是如何去掉多余的 frameLayout节点)?这时候就要用到 XML/HTML代码 运行程序后在Emulator中显示的效果是一样的,可是通过hierarchyviewer查看的UI结构是有变化的,当初多余的 FrameLayout节点被合并在一起了,或者可以理解为将merge标签中的子集直接加到Activity的FrameLayout跟节点下(这里需 要提醒大家注意:所有的Activity视图的根节点都是frameLayout)。如果你所创建的Layout并不是用framLayout作为根节点 (而是应用LinerLayout等定义root标签),就不能应用上边的例子通过merge来优化UI结构。 3、RelativeLayout RelativeLayout允许子元素指定他们相对于其它元素或父元素的位置(通过ID指定)。因此,你可以以右对齐,或上下,或置于屏幕中央的形式 来排列两个元素。元素按顺序排列,因此如果第一个元素在屏幕的中央,那么相对于这个元素的其它元素将以屏幕中央的相对位置来排列。如果使用XML来指定这个layout,在你定义它之前,被关联的元素必须定义。 解释起来也比较麻烦,不过我做个对比实验可以明白它的用处了,试着把上面例子里的RelativeLayout节点去掉看看,效果如下图所示,可以看到 由于FrameLayout的原因,都在左上角靠拢了,而使用了RelativeLayout,则可以让TextView相对于屏幕居中。 4、Snake的界面分析 有了上述Layout的基础知识,我们再来看Snake的布局文件就很好理解了,就是一个SnakeView和一个TextView,启动后,后者会覆盖在前者上面。 XML/HTML代码 也就是这样的效果: 那么相应的代码是如何实现这个效果的呢? SnakeView有一个私有变量存放覆盖其上的TextView: Java代码 在Snake这个Activity的onCreate方法中,首先将Layout文件中的SnakeView和TextView关联起来: Java代码 然后设置SnakeView的状态为Ready: Java代码 这一句代码会调用下述函数: Java代码 在mStatusText.setVisibility(View.VISIBLE);这一句后就显示出上面这个游戏起始画面了。 贪吃蛇剖析(三:界面UI、游戏逻辑和Handler) 往往我们在程序设计的时候喜欢将界面与处理分开,这样降低耦合性,易于维护扩展。在贪吃蛇Snake这个示例程序中同样将界面UI和游戏逻辑进行了分离, 它的实现方式就是,用父类TileView来实现比较基础的界面UI部分,而TileView类的子类SnakeView类完成了游戏控制逻辑部分,这样 就成功的将两者进行了分离,对后面的扩展和维护奠定了良好的基础。 界面UI 首先来看界面UI部分,基本思想大家都非常清楚:把整个屏幕看做一个二维数组,每一个元素可以视为一个方块,因此每个方格在游戏进行过程中可以处于不同的 状态,比如空闲,墙,苹果,贪食蛇(蛇身或蛇头)。我们在操作游戏的过程,其实就是不断修改相应方格的状态,然后再让整个View去重绘制自身(当然,还需要加入一些游戏当前所处状态(失败或成功)的判定机制)。TileView的数据成员如下: Java代码 那么在游戏还未正式开始前,首先要做一些初始化工作,在View第一次加载时会首先调用onSizeChanged,这里就是做这些事的最好时机。 Java代码 注意模拟器屏幕默认的像素是320×400,而代码中默认的方格大小为12,因此屏幕上放置的方格数为26×40,把屏幕剖分成这么大后,再设置一个相应 的二维int型数组来记录每一个方格的状态,根据方格的状态,可以从mTileArray保存的图标文件中读取对应的状态图标。 第一次调用完onSizeChanged后,会紧跟着第一次来调用onDraw来绘制View自身,当然,此时由于所有方格的状态都是0,所以它在屏幕上等于什么也不会去绘制。 Java代码 onDraw要做的工作非常简单,就是扫描每一个方格,根据方格当前状态,从图标文件中选择对应的图标绘制到这个方格上。当然这个onDraw在游戏进行过程中,会不断地被调用,从而界面不断被更新。 游戏逻辑 再来看子类SnakeView是如何在父类TileView的基础上,加入特定的游戏逻辑,从而完成Snake这个程序的。 Java代码 由于SnakeView从TileView继承而来,则可以说它已经拥有这个二维方格地图了(只是此时地图里的所有方格状态都是0)。那么它有了这么一个二维方格地图,如何去初始化这个地图呢?这在initNewGame函数中实现。 Java代码 想象下对整个游戏屏幕拍张照,然后对其下一个状态再拍张照,那么两张照片之间的区别是怎么产生的呢?对于系统来说,它只知道不断调用onDraw,后者负 责对整个屏幕进行绘制,那要产生两个屏幕之间的差异,肯定要通过一些手段对某些数据结构(比如这里的二维方格地图)进行调整(比如用户的控制指令,定时器 等),然后等到下一次onDraw时就会把这些更改在界面上反映出来。 这里要着重说明下private long mMoveDelay = 600;这个成员变量,虽然很不起眼,但仔细考虑它的作用就会发现很有趣,那么改变它的大小到底是如何让我们感觉到游戏变快或变慢呢? 可以打个简单的比方,在时刻0游戏启动,首先把蛇和苹果的位置都在方格地图上作好了标记,然后我们在update函数中修改蛇身让蛇向北前进一步,而这个 改变此时还只是停留在内部的核心数据结构上(即二维方格地图),还没有在界面上显示出来。当然,我们马上想到要想让这更改显示出来,让系统调用 onDraw去绘制不就完了吗?可是问题是我们不知道系统是隔多长时间去调用onDraw函数,于是mMoveDelay此时就发挥作用了,通过它就可以 设置休眠的时间,等时间一到,马上就会通知SnakeView去重绘制。你可以试试把mMoveDelay数值调大,就会看出我上面提到的“拍照“的效 果。 Handler的使用 写过JavaScript或者ActionScript的开发者,对于setInterval的用法会非常了解。那么在Android中如何实现 setInterval的方法呢?其中有两种方法可以实现类似的功能,其中一个是在线程中调用Handler方法,另外一个是应用Timer。Snake 中使用了前者。 Java代码 而实际调用的处理函数update就可以说是整个游戏的引擎,正是由于它的工作(修改蛇和苹果的状态到一个新的状态,然后休眠自己,然后等到苏醒后在 Handler中就会让系统区绘制上次修改过的二维方块地图,然后再次调用update,如此循环反复,生生不息),才使得游戏不断被推进,因此,比做 “引擎“不为过。 Java代码 既然update是游戏的动力,要让游戏停止下来只要不再调用update就可以了(因为此时其实是画面静止了),因此游戏进入暂停(这个状态还可以转为 “运行“,其实就是继续可以修改,再绘制),若进入失败(其实此时二维方块地图还停留在最后一个画面处,这也是为什么在开始时要首先清理掉整个地图)【这 一点,可以在游戏失败后,再次开始新游戏,此时通过设置的断点即可观察到上次游戏运行时的底层数据】。 一点困惑 可是个人认为Snake下面这段代码读起来有点怪,有点像一个“先有鸡,还是先有蛋?“的问题,导致我的思维逻辑上出现一个“怪圈“。 Java代码 按照这段代码的意思来看,当休眠的时间已经到了,首先去调用update,即为下一次绘制做准备工作,再让自己休眠起来,最后通知系统重绘制自己。 哎,这让我难以理解,还是回到时刻0的例子来说,在时刻0时让蛇身向北前进了一步(指的是底层的二维方格地图的修改,不是界面),然后让自己休眠0.6 毫秒,当时间到了,首先去调用update方法,那么就又会让蛇身做出修改,也就是把上一次还没绘制的覆盖掉了(那么上一次的修改岂不是白费,还没画上去 呢),更何况在update中又会让自己去休眠(还没调用invalidate,怎么又去休眠了?),又怎么还能去通知系统调用我的onDraw方法呢? 也就是说invalidate根本没有执行??? 按我的理解,应该把顺序颠倒一下,先通知系统去调用onDraw方法重绘,使得上一次 对底层二维方格地图的修改显示出来,然后再去为下一次修改做准备工作,最后让自己进入休眠,等待苏醒过来,如此循环反复。实验证明,颠倒过来也是正确的, 不过关于这一个迷惑我的地方,希望有朋友能指点我一下! 记得在javascript里使用setInterval时,也是先写处理逻辑,然后在末尾处写上一句setInterval(这也是我习惯的思维方式了),难道google上面这种写法有何深意? 此外,感觉每次绘制时都重新绘制墙壁,有点浪费时间,因为墙壁根本没有任何变化的。还有就是mLastMove这个变量设置的初衷是保证当前时间点距上一 次变化已经过去了mMoveDelay毫秒,可是既然已经用了sleep机制,再使用这个时间差看上去并无必要。 LunarLander游戏 前面有几篇文章写的是对Android示例程序贪吃蛇Snake程序的剖析,本文继续分析Android自带的另一个小游戏LunarLander的程序。在贪吃蛇Snake程序中采用了“定时器+系统调用onDraw”的架构,而LunarLander程序采用的是“多线程+强制自行绘制”的架构思路,比前者更为实用。 与贪吃蛇Snake程序的对比 就界面Layout来说,这个程序其实和Snake没有什么不同,同样是采用了FrameLayout,而且游戏的主界面由一个自定义的View来 实现,这里是LunarView。读过贪吃蛇程序剖析文章的朋友也许会发现,Snake的架构是“定时器+系统调用onDraw”来实现的,这里有一个最 大的缺陷就是onDraw是由Android系统来调用的,我们只能依赖它,却无法自行控制。这就好比一个黑盒,当然,总是能把我们要的东西给做出来,可 却无法控制其做事的细节,这对于游戏这样高效率的东西可是不利的,因此最好的解决之道当然是把绘制这部分工作自己”承包“过来,告别吃大锅饭的,进入”联 产承包制”时代。 此外,由于游戏的本质就是连续两帧图片之间发生些许差异,那么要不断催生这种差异的发生,只要有某种连续不 断发生的事件在进行就可以,例如Snake中使用的定时器,就是在不断地产生这种“差异源”,与此类似,一个线程也是不断在运行中,通过它也是可以不断产 生这种“差异源”的。 SurfaceView初探 如果说Snake中使用的Layout加自定义View是一把小型武器的话,那在SurfaceView对于android中游戏的开发来说就算是重型武 器了。我们使用前者时总是容易把游戏中某个对象(比如上文的每一个方格)当做一个小组件来处理,而后者则根本没有这种划分的概念,在它眼中,所有东西都是 在Canvas(画布)中自行绘制出来的(背景,人物等)。 SurfaceView提供直接访问一个可画图的界面,可以控制在界面顶部的子视图层。SurfaceView是提供给需要直接画像素而不是使用窗体部 件的应用使用的。Android图形系统中一个重要的概念和线索是surface。View及其子类(如TextView, Button)要画在surface上。每个surface创建一个Canvas对象(但属性时常改变),用来管理view在surface上的绘图操 作,如画点画线。还要注意的是,使用它的时候,一般都是出现在最顶层的:The view hierarchy will take care of correctly compositing with the Surface any siblings of the SurfaceView that would normally appear on top of it. 使用的SurfaceView的时候,一般情况下还要对其进行创建、销毁、改变时的情况进行监视,这就要用到 SurfaceHolder.Callback。 Java代码 surfaceCreated会首先被调用,然后是surfaceChanged,当程序结束时会调用surfaceDestroyed。下面来看看LunarView最重要的成员变量,也就是负责这个View所有处理的线程。 Java代码 这个线程由私有类LunarThread实现,它里面还有一个自己的消息队列处理器,用来接收游戏状态消息,并在屏幕上显示当前状态(而这个功能在 Snake中是通过View自己控制其包含的TextView是否显示来实现的,相比之下,LunarThread的消息处理机制更为高效)。由于有了 LunarThread这个负责具体工作的对象,所以LunarView的大部分工作都委托给后者去执行。 Java代码 工作线程LunarThread 由于SurfaceHolder是一个共享资源,因此在对其操作时都应该实行“互斥操作“,即需要使用synchronized进行”封锁“机制。 再来讨论下为什么要使用消息机制来更新界面的文字信息呢?其实原因是这样的,渲染文字的工作实际上是主线程(也就是LunarView类)的父类View 的工作,而并不属于工作线程LunarThread,因此在工作线程中式无法控制的。所以我们改为向主线程发送一个Message来代替,让主线程通过Handler对接收到的消息进行处理,从而更新界面文字信息。再回顾Android示例程序剖析之Snake贪吃蛇(三:界面UI、游戏逻辑和Handler)中SnakeView里的文字信息更新,由于是SnakeView自己(就这一个线程)对其包含的TextView做控制,当然没有这样的问题了。 Java代码 下面就是LunaThread这个工作线程的执行函数了,它一直不断在重复做一件事情:锁定待绘制区域(这里是整个屏幕),若游戏还在进行状态,则更新底层的数据,然后直接强制界面重新绘制。 Java代码 这里要注意的是最后要调用unlockCanvasAndPost来结束锁定画图,并提交改变。 强制自行绘制 doDraw这段代码就是在自己的Canvas上进行绘制,具体的绘制就不解释了,主要就是用drawBitmap,drawRect,drawLine。值得注意的一段代码是下面这个: Java代码 在绘制火箭的前后,调用了save()和restore(),它是先保存当前矩阵,将其复制到一个私有堆栈上。然后接下来对rotate的调用还是在原有 的矩阵上进行操作,但当restore调用后,以前保存的设置又重新恢复。不过,在这里还是看不出有什么用处。 暂停/继续机制 LunarLancher的暂停其实并没有不再强制重绘制,而是没有对底层的数据做任何修改,依然绘制同一帧画面,而继续则是把mLastTime设置为 当前时间+100毫秒的时间点,因为以前暂停时mLastTime就不再更新了,这样做事为了与当前时间同步起来。 Java代码 这样做的目的是为了制造“延迟“的效果,都是因为updatePhysics函数里这两句: Java代码 至于游戏的控制逻辑和判定部分就不介绍了,没有多大意思。
TileView
这里定义了 5个int型全局的变量,分别是方砖的数量mTileSize;方砖水平x防线的数量mXTileCount;以及竖直y方向上的方砖数量 mYTileCount,下面是一个相对偏移位置mXOffset和mYOffset;这里主让要大家了解如何自定义View在 Android开发中,在一个View类中主要是重写onSizeChanged方法来控制改变部分,以及onDraw实现画布的修改,实现的简写如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {}
@Override
public void onDraw(Canvas canvas) { super.onDraw(canvas);}
我们自定义的TileView类需要自己添加一个构造方法,根据需要,我们还重载了一种包含样式的方法,这里大家可以多看下Gallery控件的实现,就好理解了,下面是基本框架。
public TileView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}
public TileView(Context context, AttributeSet attrs) { super(context, attrs);}
在贪食蛇游戏中我们知道Snake是移动的,所以加入了一个清除显示的clearTiles方法,通过一个二维数组保存一个gird网格型的运动轨迹,下一次我们将会讲解android贪食蛇的游戏逻辑和完整的关联拼接实现。SnakeView
源代码分析
Snake状态分析:
如何实现画出小方块:
按键处理:
如何让我们的小方块动起来?
这里定义了 5个int型全局的变量,分别是方砖的数量mTileSize;方砖水平x防线的数量mXTileCount;以及竖直y方向上的方砖数量 mYTileCount,下面是一个相对偏移位置mXOffset和mYOffset;这里android123主让要大家了解如何自定义View在 Android开发中,在一个View类中主要是重写onSizeChanged方法来控制改变部分,以及onDraw实现画布的修改,实现的简写如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {}
@Override
public void onDraw(Canvas canvas) { super.onDraw(canvas);}
我们自定义的TileView类需要自己添加一个构造方法,根据需要,我们还重载了一种包含样式的方法,这里大家可以多看下Gallery控件的实现,就好理解了,下面是基本框架。
public TileView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}
public TileView(Context context, AttributeSet attrs) { super(context, attrs);}
在贪食蛇游戏中我们知道Snake是移动的,所以加入了一个清除显示的clearTiles方法,通过一个二维数组保存一个gird网格型的运动轨迹,下一次我们将会讲解android贪食蛇的游戏逻辑和完整的关联拼接实现。
public boolean onKeyDown(int keyCode, KeyEvent msg) {