`
deaboway
  • 浏览: 53783 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK Sample-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 project 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.java

    /** 
     * <p>Title: Snake</p> 
     * <p>Copyright: (C) 2007 The Android Open Source Project. Licensed under 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 you 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 you'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 第一次创建时被调用  
        @Override  
        public void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.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);  
                }  
            }  
        }  
        // 暂停事件被触发时  
        @Override  
        protected void onPause() {  
            super.onPause();  
            // Pause the game along with the activity  
            mSnakeView.setMode(SnakeView.PAUSE);  
        }  
        // 状态保存  
        @Override  
        public void onSaveInstanceState(Bundle outState) {  
            // 存储游戏状态到View里  
            outState.putBundle(ICICLE_KEY, mSnakeView.saveState());  
        }  
    }  




2、 SnakeView.java

/**
 * <p>Title: Snake</p>
 * <p>Copyright: (C) 2007 The Android Open Source Project. Licensed under 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 have already
	 * lost. static final ints are used instead of an enum for performance
	 * 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 apples 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 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>();

	/**
	 * 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();

	// 一个Handler
	class RefreshHandler extends Handler {

		// 处理消息队列
		@Override
		public 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 defStyle) {
		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 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;

		// 2个随机位置的苹果
		addRandomApple();
		addRandomApple();

		// 移动延迟
		mMoveDelay = 600;
		// 初始得分0
		mScore = 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 coordinates 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(mAppleList));
		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(mSnakeTrail));

		return map;
	}

	/**
	 * Given a flattened array of ordinate pairs, we reconstitute them 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[] rawArray) {
		ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();

		int coordCount = rawArray.length;
		for (int index = 0; index < coordCount; index += 2) {
			Coordinate c = new Coordinate(rawArray[index], rawArray[index + 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("mAppleList"));
		mDirection = icicle.getInt("mDirection");
		mNextDirection = icicle.getInt("mNextDirection");
		mMoveDelay = icicle.getLong("mMoveDelay");
		mScore = icicle.getLong("mScore");
		mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
	}

	/*
	 * handles key events in the game. Update the direction our snake is
	 * traveling based on the DPAD. Ignore events that would cause the snake to
	 * immediately turn back on itself.
	 * 
	 * (non-Javadoc)
	 * 
	 * @see android.view.View#onKeyDown(int, android.os.KeyEvent)
	 */
	// 监听用户键盘操作,并处理这些操作
	// 按键事件处理,确保贪吃蛇只能90度转向,而不能180度转向
	@Override
	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);
	}

	/**
	 * Sets the TextView that will be used to give information (such as "Game
	 * Over" to the user.
	 * 
	 * @param newView
	 */
	// 设置状态显示View
	public 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 notification
	 * 
	 * @param newMode
	 */
	// 设置游戏状态
	public void setMode(int newMode) {

		// 把当前游戏状态存入oldMode
		int 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);
		// 显示该View
		mStatusText.setVisibility(View.VISIBLE);
	}

	/**
	 * 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) {
			// 为苹果再找一个坐标,先随机一个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 have
			// 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!");
		}
		// 生成一个新苹果放在苹果列表中(两个苹果有可能会重合——这时候虽然看到的是一个苹果,但是呢,分数就是两个分数。)
		mAppleList.add(newCoord);
	}

	/**
	 * 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;
			}
			// Handler 会话进程sleep一个延迟时间单位
			mRedrawHandler.sleep(mMoveDelay);
		}

	}

	/**
	 * 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);
		}
	}

	/**
	 * 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;

		// 得到蛇头坐标
		Coordinate head = mSnakeTrail.get(0);
		// 初始化一个新的蛇头坐标
		Coordinate newHead = new Coordinate(1, 1);

		// 当前方向改成新的方向
		mDirection = mNextDirection;

		// 根据方向确定蛇头新坐标
		switch (mDirection) {
		// 如果方向向东(右),那么X加1
		case EAST: {
			newHead = new Coordinate(head.x + 1, head.y);
			break;
		}
			// 如果方向向西(左),那么X减1
		case WEST: {
			newHead = new Coordinate(head.x - 1, head.y);
			break;
		}
			// 如果方向向北(上),那么Y减1
		case NORTH: {
			newHead = new Coordinate(head.x, head.y - 1);
			break;
		}
			// 如果方向向南(下),那么Y加1
		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)) {
			// 设置游戏状态为Lose
			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)) {
				// 设置游戏状态为Lose
				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++;
				// 延迟是以前的90%
				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++;
		}

	}

	/**
	 * Simple class containing two integer values and a comparison function.
	 * There's probably something I should use instead, but this was quick and
	 * easy to build.
	 * 
	 */
	// 坐标内部类——原作者说这是临时做法
	private class Coordinate {
		public int x;
		public int y;

		// 构造函数
		public Coordinate(int newX, int newY) {
			x = newX;
			y = newY;
		}

		// 重写equals
		public boolean equals(Coordinate other) {
			if (x == other.x && y == other.y) {
				return true;
			}
			return false;
		}

		// 重写toString
		@Override
		public String toString() {
			return "Coordinate: [" + x + "," + y + "]";
		}
	}

}


3、 TileView.java

    /** 
     * <p>Title: Snake</p> 
     * <p>Copyright: (C) 2007 The Android Open Source Project. Licensed under the Apache License, Version 2.0 (the "License")</p> 
     * @author Gavin 标注 
     */  
    package com.deaboway.snake;  
    import android.content.Context;  
    import android.content.res.TypedArray;  
    import android.graphics.Bitmap;  
    import android.graphics.Canvas;  
    import android.graphics.Paint;  
    import android.graphics.drawable.Drawable;  
    import android.util.AttributeSet;  
    import android.view.View;  
    /** 
     * TileView: a View-variant designed for handling arrays of "icons" or other 
     * drawables. 
     *  
     */  
    // View 变种,用来处理 一组 贴片—— “icons”或其它可绘制的对象  
    public class TileView extends View {  
        /** 
         * Parameters controlling the size of the tiles and their range within view. 
         * Width/Height are in pixels, and Drawables will be scaled to fit to these 
         * dimensions. X/Y Tile Counts are the number of tiles that will be drawn. 
         */  
        protected static int mTileSize;  
        // X轴的贴片数量  
        protected static int mXTileCount;  
        // Y轴的贴片数量  
        protected static int mYTileCount;  
        // X偏移量  
        private static int mXOffset;  
        // Y偏移量  
        private static int mYOffset;  
        /** 
         * A hash that maps integer handles specified by the subclasser to the 
         * drawable that will be used for that reference 
         */  
        // 贴片图像的图像数组  
        private Bitmap[] mTileArray;  
        /** 
         * 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;  
        // Paint对象(画笔、颜料)  
        private final Paint mPaint = new Paint();  
        // 构造函数  
        public TileView(Context context, AttributeSet attrs, int defStyle) {  
            super(context, attrs, defStyle);  
            TypedArray a = context.obtainStyledAttributes(attrs,  
                    R.styleable.TileView);  
            mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);  
            a.recycle();  
        }  
        public TileView(Context context, AttributeSet attrs) {  
            super(context, attrs);  
            TypedArray a = context.obtainStyledAttributes(attrs,  
                    R.styleable.TileView);  
            mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);  
            a.recycle();  
        }  
        /** 
         * Rests the internal array of Bitmaps used for drawing tiles, and sets the 
         * maximum index of tiles to be inserted 
         *  
         * @param tilecount 
         */  
        // 设置贴片图片数组  
        public void resetTiles(int tilecount) {  
            mTileArray = new Bitmap[tilecount];  
        }  
        // 回调:当该View的尺寸改变时调用,在onDraw()方法调用之前就会被调用,所以用来设置一些变量的初始值  
        // 在视图大小改变的时候调用,比如说手机由垂直旋转为水平  
        @Override  
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
            // 定义X轴贴片数量  
            mXTileCount = (int) Math.floor(w / mTileSize);  
            mYTileCount = (int) Math.floor(h / mTileSize);  
            // X轴偏移量  
            mXOffset = ((w - (mTileSize * mXTileCount)) / 2);  
            // Y轴偏移量  
            mYOffset = ((h - (mTileSize * mYTileCount)) / 2);  
            // 定义贴片的二维数组  
            mTileGrid = new int[mXTileCount][mYTileCount];  
            // 清空所有贴片  
            clearTiles();  
        }  
        /** 
         * Function to set the specified Drawable as the tile for a particular 
         * integer key. 
         *  
         * @param key 
         * @param tile 
         */  
        // 给mTileArray这个Bitmap图片数组设置值  
        public void loadTile(int key, Drawable tile) {  
            Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize,  
                    Bitmap.Config.ARGB_8888);  
            Canvas canvas = new Canvas(bitmap);  
            tile.setBounds(0, 0, mTileSize, mTileSize);  
            // 把一个drawable转成一个Bitmap  
            tile.draw(canvas);  
            // 在数组里存入该Bitmap  
            mTileArray[key] = bitmap;  
        }  
        /** 
         * Resets all tiles to 0 (empty) 
         *  
         */  
        // 清空所有贴片  
        public void clearTiles() {  
            for (int x = 0; x < mXTileCount; x++) {  
                for (int y = 0; y < mYTileCount; y++) {  
                    // 全部设置为0  
                    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;  
        }  
        // onDraw 在视图需要重画的时候调用,比如说使用invalidate刷新界面上的某个矩形区域  
        @Override  
        public void onDraw(Canvas canvas) {  
            super.onDraw(canvas);  
            for (int x = 0; x < mXTileCount; x += 1) {  
                for (int y = 0; y < mYTileCount; y += 1) {  
                    // 当索引大于零,也就是不空时  
                    if (mTileGrid[x][y] > 0) {  
                        // mTileGrid中不为零时画此贴片  
                        canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x  
                                * mTileSize, mYOffset + y * mTileSize, mPaint);  
                    }  
                }  
            }  
        }  
    }  




四、工程文件下载

为了方便大家阅读,可以到如下地址下载工程源代码:

http://download.csdn.net/source/3145349

五、小结及下期预告:

本次详细解析了 Android SDK 自带 Sample—— Snake的结构和功能。下次将会把这个游戏移植到 J2ME平台上,并且比较 Android和 J2ME的区别和相通之处,让从事过 J2ME开发的朋友对 Android开发有个更加直观的认识。
  • 大小: 8.3 KB
  • 大小: 6.3 KB
  • 大小: 11.5 KB
  • 大小: 86.4 KB
  • 大小: 26.5 KB
  • 大小: 26.1 KB
分享到:
评论
24 楼 lrh_java 2011-05-16  
是个很好的例子,马上入门!
23 楼 deaboway 2011-05-12  
追梦人21 写道
是因为我的问题在第三页,没有被楼主看到吗?谁来帮忙解决一下这个问题啊!!!


你很认真哦,最近有点小忙,刚看到。

请注意:

    // 更新蛇 
    private void updateSnake() { 
        // 生长标志 
        boolean growSnake = false; 
 
        // 得到蛇头坐标 
        Coordinate head = mSnakeTrail.get(0); 
        // 初始化一个新的蛇头坐标 
        Coordinate newHead = new Coordinate(1, 1); 
 
        // 当前方向改成新的方向 
        mDirection = mNextDirection; 

当你按向上键的时候,其实新的蛇头已经产生了,所以,你会看到新的蛇头不是原来的蛇头。
22 楼 追梦人21 2011-05-06  
是因为我的问题在第三页,没有被楼主看到吗?谁来帮忙解决一下这个问题啊!!!
21 楼 追梦人21 2011-05-02  
楼主你好,看了你对于贪吃蛇的代码的讲述,我受益匪浅,非常感谢楼主可以奉献出来帮助新人。不过我有一个疑问,希望可以得到楼主的解答。

楼主的运行效果图的第一张应该是点击“上”之后开始初始化的界面,我的疑问就在这里。我也做了一个测试,这是创建蛇身的第一个代码,也就是蛇头,mSnakeTrail.add(new Coordinate(7, 7)); 默认的方向是北方,而 运行出来是黄色的蛇头的坐标明显不是(7,7)而是(7,5)。

为了进一步验证,我将创建蛇身的代码就只保留了这一句,也就是只创建一个蛇头,当当next方向为北时,蛇头坐标为(7,5),next方向为南时,蛇头方向为(7,9),next方向为东时,蛇头方向为(9,7),next方向为西时,蛇头坐标为(5,7)。很明显,在游戏开始时,蛇头已经朝next方向前进了两个单位。

请问楼主这是为什么呢?该怎么解释这一现象呢?希望可以得到楼主的回复,祝您工作愉快!

20 楼 zhdkn 2011-04-29  
android刚入门,值得看看
19 楼 tinren 2011-04-20  
受益匪浅,入门的经典,谢谢
18 楼 水的哥哥 2011-04-19  
public class Dingyige{
 Public static void main(String [] arg){
 System.out.println("楼主辛苦了");
 }
}
17 楼 gaoyibin 2011-04-17  
en ,感觉还不错
16 楼 石头的日记 2011-04-16  
呵呵,很好的帖子,我没有做j2me,不知android下的这个游戏和j2me下相同的游戏,那个性能更好些,期待与你交流
15 楼 zhangcs053 2011-04-15  
谢谢分享!
14 楼 ejacky 2011-04-15  
zan yige

13 楼 deaboway 2011-04-15  
wjb_forward 写道
一直就想着能有个人给讲解一下这个例子,结果楼主你就做了,感谢啊


呵呵,缘分啊。。。
12 楼 helong0904 2011-04-15  
楼主,多谢了,正是我需要的
11 楼 javasunnyboy 2011-04-14  
感谢楼主,正在学习中。
10 楼 wjb_forward 2011-04-14  
一直就想着能有个人给讲解一下这个例子,结果楼主你就做了,感谢啊
9 楼 sky0014 2011-04-14  
感谢LZ,好贴
8 楼 无为1055 2011-04-14  
谢谢 楼主  正在学习这个
7 楼 Mr.Cheney 2011-04-14  
不顶不行啊! 让咱们初学者有个更加直观的了解!
6 楼 huzhenyu 2011-04-14  
greyfox4488 写道
kill_all 写道
从设计思路,到建模,可以当作一个典型的andriod入门教学实例

Google 的 API Demo...本来就是让你当作一个典型的andriod入门教学实例的...


的确是android入门教学实例,但是楼主能做如此细致的分析,并分享出来.精神可嘉.
5 楼 yuanzhifei89 2011-04-14  
楼主很细心啊,对贪食蛇游戏进行了详细的分析...居然不能打shou,cang,了

相关推荐

    贪吃蛇—Java程序员写Android游戏

    贪吃蛇—Java程序员写Android游戏

    java-小游戏贪吃蛇实验报告.zip

    java-小游戏贪吃蛇实验报告.zipjava-小游戏贪吃蛇实验报告.zip java-小游戏贪吃蛇实验报告.zipjava-小游戏贪吃蛇实验报告.zip java-小游戏贪吃蛇实验报告.zipjava-小游戏贪吃蛇实验报告.zip java-小游戏贪吃蛇实验...

    java 贪吃蛇游戏.zip

    java 贪吃蛇游戏.zipjava 贪吃蛇游戏.zipjava 贪吃蛇游戏.zipjava 贪吃蛇游戏.zip java 贪吃蛇游戏.zipjava 贪吃蛇游戏.zipjava 贪吃蛇游戏.zipjava 贪吃蛇游戏.zip java 贪吃蛇游戏.zipjava 贪吃蛇游戏.zipjava ...

    java贪吃蛇游戏.zip

    java贪吃蛇游戏.zipjava贪吃蛇游戏.zipjava贪吃蛇游戏.zipjava贪吃蛇游戏.zip java贪吃蛇游戏.zipjava贪吃蛇游戏.zipjava贪吃蛇游戏.zipjava贪吃蛇游戏.zip java贪吃蛇游戏.zipjava贪吃蛇游戏.zipjava贪吃蛇游戏.zip...

    Java版贪吃蛇游戏.zip

    Java版贪吃蛇游戏.zipJava版贪吃蛇游戏.zipJava版贪吃蛇游戏.zip Java版贪吃蛇游戏.zipJava版贪吃蛇游戏.zipJava版贪吃蛇游戏.zip Java版贪吃蛇游戏.zipJava版贪吃蛇游戏.zipJava版贪吃蛇游戏.zip Java版贪吃蛇游戏....

    java swing 贪吃蛇游戏.zip

    java swing 贪吃蛇游戏.zipjava swing 贪吃蛇游戏.zipjava swing 贪吃蛇游戏.zip java swing 贪吃蛇游戏.zipjava swing 贪吃蛇游戏.zipjava swing 贪吃蛇游戏.zip java swing 贪吃蛇游戏.zipjava swing 贪吃蛇游戏....

    rust-snake使用rust编写的贪吃蛇游戏

    rust-snake使用rust编写的贪吃蛇游戏 仅供学习参考。 rust-snake使用rust编写的贪吃蛇游戏 仅供学习参考。 rust-snake使用rust编写的贪吃蛇游戏 仅供学习参考。 rust-snake使用rust编写的贪吃蛇游戏 仅供学习参考。...

    JAVA贪吃蛇游戏毕业设计(源代码).zip

    JAVA贪吃蛇游戏毕业设计(源代码).zipJAVA贪吃蛇游戏毕业设计(源代码).zipJAVA贪吃蛇游戏毕业设计(源代码).zipJAVA贪吃蛇游戏毕业设计(源代码).zipJAVA贪吃蛇游戏毕业设计(源代码).zipJAVA贪吃蛇游戏毕业设计(源代码)...

    小游戏-首页ASP教程.NET教程PHP教程JSP教程Mssql专栏DreamWeaver教程Mysql教程当前位置:首页>>JSP教程>>JSP开发实例>>java小游戏-贪吃蛇 java小游戏-贪吃蛇 SnakeGame.java

    首页ASP教程.NET教程PHP教程JSP教程Mssql专栏DreamWeaver教程Mysql教程当前位置:首页&gt;&gt;JSP教程&gt;&gt;JSP开发实例&gt;&gt;java小游戏-贪吃蛇 java小游戏-贪吃蛇 SnakeGame.java

    java图形界面,贪吃蛇游戏练习.zip

    java图形界面,贪吃蛇游戏练习.zipjava图形界面,贪吃蛇游戏练习.zip java图形界面,贪吃蛇游戏练习.zipjava图形界面,贪吃蛇游戏练习.zip java图形界面,贪吃蛇游戏练习.zipjava图形界面,贪吃蛇游戏练习.zip java图形...

    Android示例程序Snake贪吃蛇代码

    Android示例程序Snake贪吃蛇代码 http://blog.csdn.net/manp1212/article/details/7692020

    java编写一个贪吃蛇小游戏.zip

    java编写一个贪吃蛇小游戏.zipjava编写一个贪吃蛇小游戏.zip java编写一个贪吃蛇小游戏.zipjava编写一个贪吃蛇小游戏.zip java编写一个贪吃蛇小游戏.zipjava编写一个贪吃蛇小游戏.zip java编写一个贪吃蛇小游戏.zip...

    java小游戏 贪吃蛇 java小游戏 贪吃蛇

    java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 贪吃蛇java小游戏 ...

    Java Swing编写的贪吃蛇小游戏.zip

    Java Swing编写的贪吃蛇小游戏.zipJava Swing编写的贪吃蛇小游戏.zip Java Swing编写的贪吃蛇小游戏.zipJava Swing编写的贪吃蛇小游戏.zip Java Swing编写的贪吃蛇小游戏.zipJava Swing编写的贪吃蛇小游戏.zip Java ...

Global site tag (gtag.js) - Google Analytics