当前位置:首页 >> IT/计算机 >>

【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK Sample-Snake详解


no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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,如下图:

运行效果如下图:

1

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

二、工程结构和类图 其实 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 的基础上增

2

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

加 sleep()接口,用来每隔一个时间段后给当前线程发送一个消息。handleMessage() 方法在接受消息后,根据当前的游戏状态重绘界面,运行机制如下:

这比较类似定时器的概念,在特定的时刻发送消息,根据消息处理相应的事 件。update()与 sleep()间接的相互调用就构成了一个循环。这里要注意: mRedrawHandle 绑定的是 Avtivity 所在的线程,也就是程序的主线程;另外由于 sleep()是个异步函数,所以 update()与 sleep()之间的相互调用才没有构成死循环。 最后分析下游戏数据的保存机制,如下:

3

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

这里考虑了 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;
4

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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);

5

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

} 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;

6

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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

7

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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

8

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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

9

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

// 设置贴片图片数组 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

10

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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

11

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

// 整数数组转坐标数组,把一个 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

12

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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) {

13

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

// 原方向不是向上时,方向转向南 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

14

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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

15

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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

16

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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);

17

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

// 给下边线的每个贴片位置设置一个绿色索引标识 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

18

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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; }

19

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

} // 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++; } }

20

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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

21

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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 */ // 保存每个贴片的索引——二维数组

22

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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) {

23

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

// 定义 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

24

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

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://ishare.iask.sina.com.cn/f/14312223.html

25

no Pain no Gain no Gavin 博客同步更新至:http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway

http://download.csdn.net/source/3145349 五、小结及下期预告: 本次详细解析了 Android SDK 自带 Sample——Snake 的结构和功能。下次将 会把这个游戏移植到 J2ME 平台上,并且比较 Android 和 J2ME 的区别和相通之 处,让从事过 J2ME 开发的朋友对 Android 开发有个更加直观的认识。

更多相关文章,请访问: http://blog.sina.com.cn/deaboway http://blog.csdn.net/deaboway http://www.cnblogs.com/deaboway 以上三个 blog 同步更新。

26



相关文章:
贪吃蛇游戏报告毕业设计android
Android SDK中的贪吃蛇游... 5页 4下载券 android贪吃蛇游戏最高分... 6页 1下载券 基于android贪吃蛇游戏... 32页 7下载券 【贪吃蛇—Java程序员写......
Android SDK中的贪吃蛇游戏
【贪吃蛇—Java程序员写... 26页 免费 android贪吃蛇...Android SDK 中的贪吃蛇游戏贪吃蛇, 打开了手机游戏的...snakelength; index++) { // 检查下是存放的...
基于android的贪吃蛇游戏设计与开发
Android SDK中的贪吃蛇游... 5页 4下载券 android...10 SnakeView.java......16 1程序构思贪吃蛇游戏是一款非常经典的手机游戏,贪吃蛇游戏的设计比较复杂,它涉...
Android系列开发博客资源汇总
[8] Gavin: 贪吃蛇—Java 程序员写 Android 游戏 1-6 : 贪吃蛇本系列文章初步的计划是,由 Android SDK Sample贪吃蛇游戏(Snake)为切入点,通 过跟 J2ME ...
java编写的贪吃蛇游戏源码
15页 1下载券 【贪吃蛇—Java程序员写... 26页 免费喜欢此文档的还喜欢 用...三、结果分析运行程序出现游戏界面,蛇(Snake)头自动向前移动,用键盘的上(Up) ...
android小游戏——贪吃蛇
开始游戏 Js 游戏介绍 Shuoming 游戏说明 SnakeView 游戏的实体 TileView 方块图...【贪吃蛇—Java程序员写... 16页 免费 Android游戏引擎 17页 1下载券 android...
基于Android贪吃蛇游戏
基于Android贪吃蛇游戏_计算机软件及应用_IT/计算机_专业...○一二 年六月六日 摘 要 本课题是利用 java ...Android SDK 的下载地址为 http://development....
android贪食蛇
【贪吃蛇—Java程序员写... 26页 免费 贪吃蛇游戏报告毕业设计... 21页 1下载...<com.example.android.snake.SnakeView android:id="@+id/snake" android:layo...
android贪吃蛇
Android SDK 示例程序贪吃... 14页 5财富值 Android...【贪吃蛇—Java程序员写An... 26页 免费 Android五子...public SnakeGame(Context context) { super(contex...
基于Android的贪吃蛇游戏_图文
程序设计工作,包括: (1)贪吃蛇游戏主界面程序设计...Android 的中间层多以 Java 实现,并且采用特殊的 ...系统流程 交互界面 切换 Snake Activity Game ...
更多相关标签: