Snake作为Android源码中的游戏,是很经典的。通过对代码的阅读,我了解了Handler相关的处理功能、Drawable绘制、以及游戏的基本流程,游戏逻辑的处理。

=========================================================================================================================

以下代码是在参阅别人博客的,注释懒得写,看到别人注释好的直接拿来用了。自己写的很少。主要是作为学习用的,希望各位博主谅解。

TileView类作为SnakeView类的父类,负责基本的图片显示处理。将屏幕分成若干个图像点(即切片),然后根据 每个图像点的值(mTileGrid【x】【y】的值,x,y代表其物理位置,mTileGrid【x】【y】为相应的值,比如红、黄、蓝分别为1,2,3)对其进行显示。

/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.android.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;

//Offset这里做偏移量解释并不合适,应该是初始原点,以后的点都参考改点进行移动

private static int mXOffset;
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;

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第一次加载时会首先调用onSizeChanged,这里就是做这些事的最好时机

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {

//计算屏幕中可放置的方格的行数和列数

//注意模拟器屏幕默认的像素是320×400,而代码中默认的方格大小为12,
//因此屏幕上放置的方格数为26×40,把屏幕剖分成这么大后,再设置一个相应的二维int型数组来记录每一个方格的状态,
//根据方格的状态,可以从mTileArray保存的图标文件中读取对应的状态图标。

mXTileCount = (int) Math.floor(w / mTileSize);
mYTileCount = (int) Math.floor(h / mTileSize);

//mXOffset mYOffset是绘图的起点坐标。

mXOffset = ((w - (mTileSize * mXTileCount)) / 2);
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.
*
* 即将对应的砖块的图片 对应的加载到 mTileArray数组中
*
* @param key
* @param tile
*/
public void loadTile(int key, Drawable tile) {
//这里做了一个 Drawable 到 bitmap 的转换。由于外部程序使用的时候是直接读取资源文件中的图片,
//是drawable格式,而我们的数组是bitmap格式,方便最终的绘制。所以,需要进行一次到 bitmap的转换。
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[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++) {
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;
}


//第一次调用完onSizeChanged后,会紧跟着第一次来调用onDraw来绘制View自身,
//当然,此时由于所有方格的状态都是0,所以它在屏幕上等于什么也不会去绘制。

@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) {
canvas.drawBitmap(mTileArray[mTileGrid[x][y]],
mXOffset + x * mTileSize,
mYOffset + y * mTileSize,
mPaint);
}
}
}

}

}

SnakeView类主要负责游戏逻辑控制,利用RefreshHandler类(继承了Handler类)对游戏状态进行检测并不断刷新,从而驱动游戏运行。

SnakeView类代码如下:

/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.android.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 = "SnakeView";

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

/**
* Current direction the snake is headed.
*/
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.
*/
private RefreshHandler mRedrawHandler = new RefreshHandler();

class RefreshHandler extends Handler {

//响应消息,获取消息并处理
@Override
public void handleMessage(Message msg) {
SnakeView.this.update();
SnakeView.this.invalidate();
}

// 向外提供人工的调用消息的接口
//定时发送消息给UI线程,以此达到更新的效果。
public void sleep(long delayMillis) {
//注销消息
this.removeMessages(0);//清空消息队列,Handler进入对新消息的等待
//添加消息
sendMessageDelayed(obtainMessage(0), delayMillis);//定时发送新消息,激活handler

}
};


/**
* 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);//设置焦点,由于存在 文字界面 和 游戏界面的跳转。这个focus是不可或缺的。

Resources r = this.getContext().getResources();

resetTiles(4);
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() {
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;

// Two apples to start with
addRandomApple();
addRandomApple();

mMoveDelay = 600;
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...]
*
* 在游戏暂停时,需要通过Bundle方式保存数据。见saveState()。
* Bundle支持简单的数组。
* 所以需要将我们的部分数据结构,如蛇体和苹果位置的数组,转换成简单的序列化的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:A mapping from String values to various Parcelable types.
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
*/
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)
*/

@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
*/
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) {
int oldMode = mMode;
mMode = newMode;

if (newMode == RUNNING & oldMode != RUNNING) {//???这个地方&和&&作用应该是一样的,因为其只有一位
mStatusText.setVisibility(View.INVISIBLE);
update();//注意到,在initGame中也有update(),不过放心~ 多次重复 update不会影响效果的,
//蛇的移动有mLastMove 和 mMoveDelay 来校验。这会在Update()中体现。
//当然,经过实验,注释掉这个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);
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.
*
* 在地图上随机的增加果子。注意苹果的位置不可以是蛇体所在噢~这里有个小bug,没有检测
* 产生的果子位置 可能与 另一个果子位置重合。
* 新产生的果子的坐标会增加到mApplist的数组上。
*
*/
private void addRandomApple() {
Coordinate newCoord = null;
boolean found = false;
while (!found) {
// Choose a new location for our apple
int newX = 1 + RNG.nextInt(mXTileCount - 2);
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;
break;//经过测验,如果已经检测到冲突,这里是可以添加break提前结束循环的
}
}
// 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
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;
}
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);
}
}

/**
* * 设置当前蛇的方向位置:
* 从以上的键盘代码我们可以看得出,程序中是通过触发来改变坐标(+1,-1)的方式来改蛇头的方向,
* 可见坐标在游戏编程中的作用, 这个也是根据手机的屏幕是点阵的方式来显示, 所以坐标就是一个
* 定位器。 在这里大家可能还有一个疑问。 就是就这个蛇什么能够以“7”字形来移动行走, 其实我们
* 稍微仔细观察一下就知道了,在这里面, 他们也是通过坐标的传递来实现的, 只要把头部的坐标点
* 依次赋给下一个点, 后面的每一个点都走过了头部所走过的点,而蛇的头部就是负责去获取坐标,整
* 个蛇的行走起来就很自然和连贯。 坐标的方向变换又是通过判断那个方向按键的按下来改变的, 这
* 样一来, 键盘的作用就发挥出来了。蛇吃苹果又是怎样去实现?上面我所说到的坐标就起了作用。在蛇
* 所经过的每一个坐标, 他们都要在苹果所在的(ArrayList<Coordinate> mAppleList = new
* ArrayList<Coordinate>())坐标集里面集依次判断,若是坐标相同,那个这个苹果就被蛇吃了 。
*
* 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;

// grab the snake by the head
Coordinate head = mSnakeTrail.get(0);
Coordinate newHead = new Coordinate(1, 1);

mDirection = mNextDirection;

switch (mDirection) {
case EAST: {
newHead = new Coordinate(head.x + 1, head.y);
break;
}
case WEST: {
newHead = new Coordinate(head.x - 1, head.y);
break;
}
case NORTH: {
newHead = new Coordinate(head.x, head.y - 1);
break;
}
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)) {
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)) {
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++;
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;
}

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

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

}

参考文章:

http://blog.csdn.net/biaobiaoqi/article/details/6618313

http://www.jizhuomi.com/android/example/189.html

更多相关文章

  1. Android处理图片透明度并绘画图片
  2. Android获取arrays.xml里的数组字段值实例详解
  3. android studio module引用本地aar
  4. android listview的创建及行删除操作
  5. android对界面某一部分进行截图的方法
  6. android各种适配器的用法
  7. Android适用于IM通知音频的Vibrator
  8. android 通过百度地图定位获取坐标在导航地图上面显示,坐标发生偏
  9. 数组资源(arrays)的使用

随机推荐

  1. Android多版本兼容示例
  2. 关于Android全埋点方案
  3. S5PV210 Android(安卓)Overlay系统(视频输
  4. android:同时弹出顶部和底部菜单的做法
  5. [Tool] 取得APP的Store URL Scheme (Andr
  6. 第三部分:Android(安卓)应用程序接口指南-
  7. android初学者_面向初学者的Android(安卓
  8. Android(安卓)Boot: After kernel stage
  9. Android仿人人客户端(v5.7.1)——网络模块
  10. android apk如何入门