android sdk Snake代码详解

1.先晒几张程序运行的图片

[img]http://dl.iteye.com/upload/attachment/434675/59005262-6bb5-3edc-a432-0370eb0c08a1.png[/img]
[img]http://dl.iteye.com/upload/attachment/434677/8ffb97f9-a21d-3624-8bf8-c9414dd4dfdf.png[/img]

[img]http://dl.iteye.com/upload/attachment/434679/5fd698ab-79e4-3635-b12e-5d9de739ba5d.png[/img]

2.下面看下代码的文件结构:

[img]http://dl.iteye.com/upload/attachment/434683/6ac78694-e3c7-3655-8a44-b829f04c3e9f.png[/img]

游戏代码只有3个类,Snake.java是Activity类;TileView.java继承View,是该例子的视图基类,可以把它当成一个小方格类;SnakeView继承TileView类,封装了数据的操作。

3.

下面先看看方格类TileView 是怎么编写的

//新建TileView继承View ,重写构造方法

protected int mTileSize;
public TileViewExm(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub

TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.TileView);

mTileSize=a.getInt(R.styleable.TileView_tileSize, 12);

a.recycle();

}

public TileViewExm(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();
}

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);

这句代码是获取res/value/attrs.xml下自定义的属性值,attrs.xml中如下定义:

<resources>
<declare-styleable name="TileView"> <!--属性列表名TileView 定义的属性名tileSize 值是Integer类型,定义好的属性名可以在layout配置文件中像正常属性那样使用-->
<attr name="tileSize" format="integer" />
</declare-styleable>
</resources>


mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);

这句是获取layout文件定义的UI参数中tileSize的值,没有定义则返回缺省值12。mTileSize代表小方格的尺寸。

接下来要把游戏的界面分成很多个小方格来绘画,首先我们要知道界面可以分成几个空格,可以在onSzieChanged中获取初始化数据,onSizeChanged在View第一次启动时加载

protected static int mXTileCount;
protected static int mYTileCount;

private static int mXOffset;
private static int mYOffset;

private int[][] mTileGrid;
@Override
protected void onSizeChanged(int w ,int h, int oldw,int oldh){
mXTileCount = (int) Math.floor(w/mTileSize);
mYTileCount = (int) Math.floor(h/mTileSize);

//不能分成整数个,得出剩余x y 的尺寸
mXOffset = ((w-(mTileSize * mXTileCount))/2);
mYOffset = ((h-(mTileSize * mYTileCount))/2);

mTileGrid = new int[mXTileCount][mYTileCount];
clearTiles();
}

public void clearTiles(){
for(int x = 0; x<mXTileCount; x++){
for(int y=0; y<mYTileCount; y++){
setTile(0, x, y);
}
}
}

/**
*
*
*/
public void setTile(int tileindex, int x, int y){
mTileGrid[x][y]=tileindex;
}




clearTiles()函数初始化mTileGrid,把mTileGrid所有值设为0,mTileGrid代表界面的所有方格



private Bitmap[] mTileArray;
public void resetTiles(int tilecount) {
mTileArray = new Bitmap[tilecount];
}

public void loadTile(int key, Drawable tile){
Bitmap bitmap =Bitmap.createBitmap(mTileSize,mTileSize,Bitmap.Config.ARGB_8888); //创建位图
Canvas canvas= new Canvas(bitmap);//获取位图的canvas
tile.setBounds(0, 0, mTileSize ,mTileSize);
tile.draw(canvas); //把tile所表示的图片画到canvas中

mTileArray[key]=bitmap;
}




mTileArray用来存方格所用到的图片resetTiles(int)函数初始化图片的数量,loadTile(int key,Drawable)加载图片存进mTileArray[key]中


//覆盖onDraw类,操作canvas实现绘图
Paint mPaint=new Paint();
@Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
for(int x=0; x<mXTileCount; x++){
for(int y=0; y<mYTileCount;y++){
if(mTileGrid[x][y]>0){
canvas.drawBitmap(mTileArray[mTileGrid[x][y]],
mXOffset+x * mTileSize,
mYOffset+y * mTileSize,
mPaint);
}
}
}
}



4.
接下来看看SnakeView类

//创建SnakeView,SnakeView继承TileView,重写构造函数
public SnakeViewExm(Context context , AttributeSet attrs){
super(context,attrs);
initSnakeView();
}

public SnakeViewExm(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
initSnakeView();
}


private void initSnakeView(){
setFocusable(true);//获得焦点

Resources r=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 static final int RED_STAR = 1;
private static final int YELLOW_STAR = 2;
private static final int GREEN_STAR = 3;


initSnakeView()函数中设置了mTileArray的长度,并加载了三张图片


//贪吃蛇当前的方向mDirection,下一刻方向mNextDirection
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;

private long mScore = 0; //得分
private long mMoveDelay = 600; //600毫秒后,自动行走
//贪吃蛇由几个方格组成,Coordiante 是表示一个方格的坐标类
//mAppleList 苹果列表,也就是给贪吃蛇吃的那个东西,
private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
private ArrayList<Coordinate> mAppleList=new ArrayList<Coordinate>();
private void initNewGame(){
mSnakeTrail.clear();
mAppleList.clear();

//贪吃蛇的坐标
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;

addRandomApple();
addRandomApple();

mMoveDelay = 600;
mScore = 0;


}
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 + "]";
}


}


addRandomApplet()随进产生苹果的坐标位置,代码如下
	
private static final Random RNG =new Random();
private void addRandomApple(){
Coordinate newCoord = null;
boolean found = false;
while(!found){

int newX =1 + RNG.nextInt(mXTileCount-2);
int newY =1 + RNG.nextInt(mYTileCount-2);
newCoord = new Coordinate(newX, newY);

boolean collision = false;
int snakelength = mSnakeTrail.size();
//生成的位置不能和贪吃蛇的坐标一样
for(int index =0; index< snakelength;index++){
if(mSnakeTrail.get(index).equals(newCoord)){
collision=true;
}
}

found=!collision;
}
if(newCoord == null){
Log.e(TAG,"Somehow ended up with a null newCoord");
}
mAppleList.add(newCoord);
}


游戏初始化的参数在上面已经完成了,接下来是要给SnakeView添加事件响应

//游戏状态的标识变量
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;
@Override
public boolean onKeyDown(int keyCode, KeyEvent msg){
//向上,如果是READY或LOST,则开始游戏
if(keyCode==KeyEvent.KEYCODE_DPAD_UP){
if(mMode ==READY | mMode ==LOSE){
initNewGame(); //初始化
setMode(RUNNING);//设置状态
return(true);
}
if(mMode == PAUSE){
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);
}



按下键盘键后,如果是刚开始游戏或者重新游戏时会调用update(),而其他状态则不会在这里调用更新,可以看出update()这个函数游戏中是一直在被调用的,下面我们看下代码这么实现update(),并且实现update()的循环调用

private long mLastMove;
public void update(){
//只有在运行中才会有数据更新
if(mMode==RUNNING){
long now =System.currentTimeMillis();

//等待mMoveDelay时间,游戏自动更新
if(now - mLastMove >mMoveDelay){
clearTiles();
updateWalls();
updateSnake();
updateApples();
mLastMove=now;
}
mRedrawHandler.sleep(mMoveDelay);
}
}

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

private void updateSnake(){
boolean growSnake =false;

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

//是否碰到“墙壁”
if((newHead.x<1)||(newHead.y<1)||(newHead.x>mXTileCount-2)
||(newHead.y>mYTileCount-2)){
setMode(LOSE);
return;
}

int snakelength =mSnakeTrail.size();
//碰到自己
for(int snakeindex=0;snakeindex<snakelength;snakeindex++){
Coordinate c=mSnakeTrail.get(snakeindex);
if(c.equals(newHead)){
setMode(LOSE);
return;
}
}


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

mSnakeTrail.add(0,newHead);

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++;
}
}
//设置苹果的图片
private void updateApples(){
for(Coordinate c:mAppleList){
setTile(YELLOW_STAR,c.x,c.y);
}
}

上面update()函数中调用了mRedrawHandler.sleep(mMoveDelay); mRedrawHandler对象是RedrawHandle的实例,mRedrawHandler继承Handler类

private RefreshHandler mRedrawHandler=new RefreshHandler();

class RefreshHandler extends Handler{

@Override
public void handleMessage(Message msg){
SnakeViewExm.this.update();
SnakeViewExm.this.invalidate(); //重新调用onDraw()
}

public void sleep(long delayMillis){
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0),delayMillis);//调用sendMessageDelayed函数,线程等待delayMillis时间后发送信息,发送信息会调用handleMessage(msg)函数
}
}



到这里键盘事件响应就完成了,之前还有一个setMode(int)设置状态的函数没说,接下了就说它

private TextView mStatusText; //textView用来显示不同状态的信息
//可以通过Activity调用setTextView来实例化,这样就可以通过layout来配置textView的位置
public void setTextView(TextView newView){
mStatusText=newView;
}
/*
*setMode(int );
*/
public void setMode(int newMode){
int oldMode = mMode;
mMode =newMode;

if(newMode==RUNNING & oldMode!=RUNNING){
mStatusText.setVisibility(View.INVISIBLE);//不显示TextView提示信息
update();
return;
}

Resources res=getContext().getResources();
CharSequence str="";
if(newMode ==PAUSE){
str=res.getText(R.string.mode_pause);//从资源文件string.xml中获取值
}
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);//显示TextView提示信息
}

string.xml的配置可以在google sdk 自带的sample/snake里面找到,这里就不贴出来了;现在SnakeView功能基本齐全了,下面来看看Activity类



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.
*
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// No Title bar
requestWindowFeature(Window.FEATURE_NO_TITLE);

setContentView(R.layout.snake_layout);//layout文件自己在sdk sample/snake中找

mSnakeView = (SnakeView) findViewById(R.id.snake);
mSnakeView.setTextView((TextView) findViewById(R.id.text));

if (savedInstanceState == null) {
// We were just launched -- set up a new game
mSnakeView.setMode(SnakeView.READY);
} else {
// We are being restored
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) {
//Store the game state
outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
}

}


Snake类很简单,只是重写了onCreate、onPause和onSaveInstanceState函数,
其中onSaveInstanceState函数会在activity被杀掉之前被调用。上面调用了SnakeView中的saveState()函数返回游戏数据,和restoreState()函数还原保持的数据状态。
下面来看看SnakeView里面这两个函数的代码
	
//保存数据

public Bundle saveState(){
Bundle map =new Bundle();

map.putIntArray("mAppleList",coordArrayListToArray(mAppleList)); //bundle只能存基本类型,所有要把ArrayList转换为int[],调用coordArrayListToArray进行转换
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;
}
private int[] coordArrayListToArray(ArrayList<Coordinate> cvec){
int count =cvec.size();
int[] array =new int[count*2];
for(int index=0; index<count;index++){
Coordinate c=cvec.get(index);
array[2*index]=c.x;
array[2*index+1]=c.y;
}
return array;
}


//还原数据

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

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


到这里所有的类都讲解完,AndroidManifest.xml文件我这就不说了,请大家自己去看。这个游戏其实还有很多不完善的,比如说这个游戏并不支持暂停,只能玩到死,还有其他很多可以改善的地方,(我很懒的~~这里就不写了)大家如果有兴趣可以自己动手改下
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值