android sdk Snake代码详解

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

android sdk Snake代码详解
android sdk Snake代码详解

android sdk Snake代码详解

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

android sdk Snake代码详解

游戏代码只有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文件我这就不说了,请大家自己去看。这个游戏其实还有很多不完善的,比如说这个游戏并不支持暂停,只能玩到死,还有其他很多可以改善的地方,(我很懒的~~这里就不写了)大家如果有兴趣可以自己动手改下

你可能感兴趣的:(游戏,xml,android,UI,UP)