Snake游戏分析(排版)

  • 格式:doc
  • 大小:496.00 KB
  • 文档页数:25

Snake游戏分析Snake也是一个经典游戏了, Nokia蓝屏机的王牌游戏之一。

Android SDK 1.5就有了它的身影。

我们这里就来详细解析一下 Android SDK Sample中的 Snake 工程。

本工程基于 SDK 2.3.3版本中的工程,路径为: %Android_SDK_HOME% /samples/android-10/Snake一、 Eclipse 工程通过 File-New Project-Android-Android Project,选择“ Create pro ject from existing sample”创建自己的应用 SnakeAndroid,如下图:运行效果如下图:二、工程结构和类图其实 Snake的工程蛮简单的,源文件就三个: Snake.java SnakeView.java TileView.java。

Snake类是这个游戏的入口点, TitleView类进行游戏的绘画,SnakeView类则是对游戏控制操作的处理。

Coordinate, RefreshHandler是 2个辅助类,也是 SnakeView类中的内部类。

其中, Coordinate是一个点的坐标( x, y), RefreshHandler将 RefreshHandler对象绑定某个线程并给它发送消息。

如下图:任何游戏都需要有个引擎来推动游戏的运行,最简化的游戏引擎就是:在一个线程中 While循环,检测用户操作,对用户的操作作出反应,更新游戏的界面,直到用户退出游戏。

在 Snake这个游戏中,辅助类 RefreshHandler继承自 Handler,用来把RefreshHandler与当前线程进行绑定,从而可以直接给线程发送消息并处理消息。

注意一点: Handle对消息的处理都是异步。

RefreshHandler在 Handler 的基础上增加 sleep()接口,用来每隔一个时间段后给当前线程发送一个消息。

handleMessage()方法在接受消息后,根据当前的游戏状态重绘界面,运行机制如下:运行机制这比较类似定时器的概念,在特定的时刻发送消息,根据消息处理相应的事件。

update()与 sleep()间接的相互调用就构成了一个循环。

这里要注意:mRedrawHandle绑定的是 Avtivity所在的线程,也就是程序的主线程;另外由于 sleep()是个异步函数,所以 update()与 sleep()之间的相互调用才没有构成死循环。

最后分析下游戏数据的保存机制,如下:这里考虑了 Activity的生命周期:如果用户在游戏期间离开游戏界面,游戏暂停;或者由于内存比较紧张, Android关闭游戏释放内存,那么当用户返回游戏界面的时候恢复到上次离开时的界面。

三、源码解析详细解析下源代码,由于代码量不大,以注释的方式列出如下:1、 Snake.javaJava代码 /*** <p>Title: Snake</p>* <p>Copyright: (C) 2007 The Android Open Source Project. Licensed u nder the Apache License, Version 2.0 (the "License")</p>* @author Gavin 标注*/package com.deaboway.snake;import android.app.Activity;import android.os.Bundle;import android.widget.TextView;/*** Snake: a simple game that everyone can enjoy.** This is an implementation of the classic Game "Snake", in which yo u control a* serpent roaming around the garden looking for apples. Be careful, though,* because when you catch one, not only will you become longer, but y ou'll move* faster. Running into yourself or the walls will end the game. **/// 贪吃蛇:经典游戏,在一个花园中找苹果吃,吃了苹果会变长,速度变快。

碰到自己和墙就挂掉。

public class Snake extends Activity {private SnakeView mSnakeView;private static String ICICLE_KEY = "snake-view";/*** Called when Activity is first created. Turns off the title bar , sets up* the content views, and fires up the SnakeView.**/// 在 activity 第一次创建时被调用@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(yout.snake_layout);mSnakeView = (SnakeView) findViewById(R.id.snake);mSnakeView.setTextView((TextView) findViewById(R.id.text)); // 检查存贮状态以确定是重新开始还是恢复状态if (savedInstanceState == null) {// 存储状态为空,说明刚启动可以切换到准备状态mSnakeView.setMode(SnakeView.READY);} else {// 已经保存过,那么就去恢复原有状态Bundle map = savedInstanceState.getBundle(ICICLE_KEY); if (map != null) {// 恢复状态mSnakeView.restoreState(map);} else {// 设置状态为暂停mSnakeView.setMode(SnakeView.PAUSE);}}}// 暂停事件被触发时@Overrideprotected void onPause() {super.onPause();// Pause the game along with the activitymSnakeView.setMode(SnakeView.PAUSE);}// 状态保存@Overridepublic void onSaveInstanceState(Bundle outState) {// 存储游戏状态到View里outState.putBundle(ICICLE_KEY, mSnakeView.saveState()); }}2、 SnakeView.javaJava代码/*** <p>Title: Snake</p>* <p>Copyright: (C) 2007 The Android Open Source Project. Licensed u nder the Apache License, Version 2.0 (the "License")</p>* @author Gavin 标注*/package com.deaboway.snake;import java.util.ArrayList;import java.util.Random;import android.content.Context;import android.content.res.Resources;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.os.Bundle;import android.util.Log;import android.view.KeyEvent;import android.view.View;import android.widget.TextView;/*** SnakeView: implementation of a simple game of Snake***/public class SnakeView extends TileView {private static final String TAG = "Deaboway";/*** Current mode of application: READY to run, RUNNING, or you hav e already* lost. static final ints are used instead of an enum for perfor mance* reasons.*/// 游戏状态,默认值是准备状态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;// 游戏中蛇的前进方向,默认值北方private int mDirection = NORTH;// 下一步的移动方向,默认值北方private int mNextDirection = NORTH;// 游戏方向设定北南东西private static final int NORTH = 1;private static final int SOUTH = 2;private static final int EAST = 3;private static final int WEST = 4;/*** Labels for the drawables that will be loaded into the TileView class*/// 三种游戏元private static final int RED_STAR = 1;private static final int YELLOW_STAR = 2;private static final int GREEN_STAR = 3;/*** mScore: used to track the number of apples captured mMoveDelay : number of* milliseconds between snake movements. This will decrease as ap ples are* captured.*/// 游戏得分private long mScore = 0;// 移动延迟private long mMoveDelay = 600;/*** mLastMove: tracks the absolute time when the snake last moved, and is* used to determine if a move should be made based on mMoveDelay .*/// 最后一次移动时的毫秒时刻private long mLastMove;/*** mStatusText: text shows to the user in some run states*/// 显示游戏状态的文本组件private TextView mStatusText;/*** mSnakeTrail: a list of Coordinates that make up the snake's bo dy* mAppleList: the secret location of the juicy apples the snake craves.*/// 蛇身数组(数组以坐标对象为元素)private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordin ate>();// 苹果数组(数组以坐标对象为元素)private ArrayList<Coordinate> mAppleList = new ArrayList<Coordina te>();/*** Everyone needs a little randomness in their life*/// 随机数private static final Random RNG = new Random();/*** Create a simple handler that we can use to cause animation to happen. We* set ourselves as a target and we can use the sleep() function to cause an* update/invalidate to occur at a later date.*/// 创建一个Refresh Handler来产生动画:通过sleep()来实现private RefreshHandler mRedrawHandler = new RefreshHandler(); // 一个Handlerclass RefreshHandler extends Handler {// 处理消息队列@Overridepublic void handleMessage(Message msg) {// 更新View对象SnakeView.this.update();// 强制重绘SnakeView.this.invalidate();}// 延迟发送消息public void sleep(long delayMillis) {this.removeMessages(0);sendMessageDelayed(obtainMessage(0), delayMillis);}};/*** Constructs a SnakeView based on inflation from XML** @param context* @param attrs*/// 构造函数public SnakeView(Context context, AttributeSet attrs) {super(context, attrs);// 构造时初始化initSnakeView();}public SnakeView(Context context, AttributeSet attrs, int defStyl e) {super(context, attrs, defStyle);initSnakeView();}// 初始化private void initSnakeView() {// 可选焦点setFocusable(true);Resources r = this.getContext().getResources();// 设置贴片图片数组resetTiles(4);// 把三种图片存到Bitmap对象数组loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar)); loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar)); }// 开始新的游戏——初始化private void initNewGame() {// 清空ArrayList列表mSnakeTrail.clear();mAppleList.clear();// For now we're just going to load up a short default eastbo und 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;// 2个随机位置的苹果addRandomApple();addRandomApple();// 移动延迟mMoveDelay = 600;// 初始得分0mScore = 0;}/*** Given a ArrayList of coordinates, we need to flatten them into an array* of ints before we can stuff them into a map for flattening and storage.** @param cvec* : a ArrayList of Coordinate objects* @return : a simple array containing the x/y values of the coor dinates as* [x1,y1,x2,y2,x3,y3...]*/// 坐标数组转整数数组,把Coordinate对象的x y放到一个int数组中——用来保存状态private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) { int count = cvec.size();int[] rawArray = new int[count * 2];for (int index = 0; index < count; index++) {Coordinate c = cvec.get(index);rawArray[2 * index] = c.x;rawArray[2 * index + 1] = c.y;}return rawArray;}/*** Save game state so that the user does not lose anything if the game* process is killed while we are in the background.** @return a Bundle with this view's state*/// 保存状态public Bundle saveState() {Bundle map = new Bundle();map.putIntArray("mAppleList", coordArrayListToArray(mAppleLis t));map.putInt("mDirection", Integer.valueOf(mDirection));map.putInt("mNextDirection", Integer.valueOf(mNextDirection)) ;map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));map.putLong("mScore", Long.valueOf(mScore));map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTr ail));return map;}/*** Given a flattened array of ordinate pairs, we reconstitute the m into a* ArrayList of Coordinate objects** @param rawArray* : [x1,y1,x2,y2,...]* @return a ArrayList of Coordinates*/// 整数数组转坐标数组,把一个int数组中的x y放到Coordinate对象数组中——用来恢复状态private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArra y) {ArrayList<Coordinate> coordArrayList = new ArrayList<Coordina te>();int coordCount = rawArray.length;for (int index = 0; index < coordCount; index += 2) {Coordinate c = new Coordinate(rawArray[index], rawArray[i ndex + 1]);coordArrayList.add(c);}return coordArrayList;}/*** Restore game state if our process is being relaunched** @param icicle* a Bundle containing the game state*/// 恢复状态public void restoreState(Bundle icicle) {setMode(PAUSE);mAppleList = coordArrayToArrayList(icicle.getIntArray("mApple List"));mDirection = icicle.getInt("mDirection");mNextDirection = icicle.getInt("mNextDirection");mMoveDelay = icicle.getLong("mMoveDelay");mScore = icicle.getLong("mScore");mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnak eTrail"));}/** handles key events in the game. Update the direction our snake is* traveling based on the DPAD. Ignore events that would cause th e snake to* immediately turn back on itself.** (non-Javadoc)** @see android.view.View#onKeyDown(int, android.os.KeyEvent) */// 监听用户键盘操作,并处理这些操作// 按键事件处理,确保贪吃蛇只能90度转向,而不能180度转向@Overridepublic 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 prev ious 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 conti nue 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);}/*** Sets the TextView that will be used to give information (such as "Game* Over" to the user.** @param newView*/// 设置状态显示Viewpublic void setTextView(TextView newView) {mStatusText = newView;}/*** Updates the current mode of the application (RUNNING or PAUSED or the* like) as well as sets the visibility of textview for notificat ion** @param newMode*/// 设置游戏状态public void setMode(int newMode) {// 把当前游戏状态存入oldModeint oldMode = mMode;// 把游戏状态设置为新状态mMode = newMode;// 如果新状态是运行状态,且原有状态为不运行,那么就开始游戏if (newMode == RUNNING & oldMode != RUNNING) {// 设置mStatusTextView隐藏mStatusText.setVisibility(View.INVISIBLE);// 更新update();return;}Resources res = getContext().getResources();CharSequence str = "";// 如果新状态是暂停状态,那么设置文本内容为暂停内容if (newMode == PAUSE) {str = res.getText(R.string.mode_pause);}// 如果新状态是准备状态,那么设置文本内容为准备内容if (newMode == READY) {str = res.getText(R.string.mode_ready);}// 如果新状态时失败状态,那么设置文本内容为失败内容if (newMode == LOSE) {// 把上轮的得分显示出来str = res.getString(R.string.mode_lose_prefix) + mScore + res.getString(R.string.mode_lose_suffix); }// 设置文本mStatusText.setText(str);// 显示该ViewmStatusText.setVisibility(View.VISIBLE);}/*** Selects a random location within the garden that is not curren tly covered* by the snake. Currently _could_ go into an infinite loop if th e 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) {// 为苹果再找一个坐标,先随机一个X值int newX = 1 + RNG.nextInt(mXTileCount - 2);// 再随机一个Y值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 h ave// a good location for an apple. Otherwise, we'll circle back// and try again// 如果有冲突就继续循环,如果没冲突flag的值就是false,那么自然会退出循环,新坐标也就诞生了found = !collision;}if (newCoord == null) {Log.e(TAG, "Somehow ended up with a null newCoord!"); }// 生成一个新苹果放在苹果列表中(两个苹果有可能会重合——这时候虽然看到的是一个苹果,但是呢,分数就是两个分数。