开源项目是个学东西的好地方。http://code.google.com/p/apps-for-android/ ,我把代码上传一份http://download.csdn.net/detail/pipisky2006/3672322
Amazed是个重力感应的小球的游戏。
[效果图]
先说下代码结构,一个activity,把view和window关联起来,并且在onResume调用了view中的registerListener方法来重新注册重力感应事件 。public class AmazedActivity extends Activity { private AmazedView mView; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // remove title bar. requestWindowFeature(Window.FEATURE_NO_TITLE); // setup our view, give it focus and display. mView = new AmazedView(getApplicationContext(), this); mView.setFocusable(true); this.setContentView(mView); } @Override protected void onResume() { super.onResume(); mView.registerListener(); } @Override public void onSaveInstanceState(Bundle icicle) { super.onSaveInstanceState(icicle); mView.unregisterListener(); } }
AmazedView是个唯一的一个view,但是真正的界面的绘制工作在Marble和Maze。Maze是底图迷宫,Marble是上面的弹球。AmazedView更多的是逻辑的控制。控制游戏的状态。
/** * Updates the current game state with a new state. At the moment this is * very basic however if the game was to get more complicated the code * required for changing game states could grow quickly. * * @param newState * New game state */ public void switchGameState(int newState) { mCurState = newState; }
首先初始化为 switchGameState(GAME_INIT),随后就是根据touch事件或者onkeydown这些用户手动的事件来驱动更改状态,或者是游戏运行的状态到失败或者重新开始的状态。最终游戏是需要显示出来的,而且在运行状态下是需要实时刷新的,这样才能更有更流畅和细腻的用户体验。先看看draw方法
@Override public void onDraw(Canvas canvas) { Log.d("AmazedView", "onDraw>>"+mMarble.getX()+">>>"+mMarble.getY()); // update our canvas reference. mCanvas = canvas; // clear the screen. mPaint.setColor(Color.WHITE); mCanvas.drawRect(0, 0, mCanvasWidth, mCanvasHeight, mPaint); // simple state machine, draw screen depending on the current state. switch (mCurState) { case GAME_RUNNING: // draw our maze first since everything else appears "on top" of it. mMaze.draw(mCanvas, mPaint); // draw our marble and hud. mMarble.draw(mCanvas, mPaint); // draw hud drawHUD(); break; case GAME_OVER: drawGameOver(); break; case GAME_COMPLETE: drawGameComplete(); break; case GAME_LANDSCAPE: drawLandscapeMode(); break; } gameTick(); }
/** * Called every cycle, used to process current game state. */ public void gameTick() { // very basic state machine, makes a good foundation for a more complex // game. switch (mCurState) { case GAME_INIT: // prepare a new game for the user. initNewGame(); switchGameState(GAME_RUNNING); case GAME_RUNNING: // update our marble. if (!mWarning) updateMarble(); break; } // redraw the screen once our tick function is complete. invalidate(); }至于刷新的循环驱动,这里做的比较傻,一直在循环刷新,我觉得这里可以优化一下,在状态更改为运行时的时候进行一次invalidate() ;然后gameTick()中的invalidate()可以在mCurstate为GAME_RUNNING时调用,其他的状态绘制一次就可以了。
另外值得一说的是迷宫的地图的加载。是通过读取Assert下面的levelXX.txt中来获取的,1代表空,0代表可以运行的路径,2代表目标
下面的代码实现了从assets文件夹指定的文件中读取迷宫描述数据,保存至整型数组mMazeData中,在绘制的时候更加mMazeData的数值的不同加载不同的bitmap。
/** * Load specified maze level. * * @param activity * Activity controlled the maze, we use this load the level data * @param newLevel * Maze level to be loaded. */ void load(Activity activity, int newLevel) { // maze data is stored in the assets folder as level1.txt, level2.txt // etc.... String mLevel = "level" + newLevel + ".txt"; InputStream is = null; try { // construct our maze data array. mMazeData = new int[MAZE_ROWS * MAZE_COLS]; // attempt to load maze data. is = activity.getAssets().open(mLevel); // we need to loop through the input stream and load each tile for // the current maze. for (int i = 0; i < mMazeData.length; i++) { // data is stored in unicode so we need to convert it. mMazeData[i] = Character.getNumericValue(is.read()); // skip the "," and white space in our human readable file. is.read(); is.read(); } } catch (Exception e) { Log.i("Maze", "load exception: " + e); } finally { closeStream(is); } } /** * Draw the maze. * * @param canvas * Canvas object to draw too. * @param paint * Paint object used to draw with. */ public void draw(Canvas canvas, Paint paint) { // loop through our maze and draw each tile individually. for (int i = 0; i < mMazeData.length; i++) { // calculate the row and column of the current tile. mRow = i / MAZE_COLS; mCol = i % MAZE_COLS; // convert the row and column into actual x,y co-ordinates so we can // draw it on screen. mX = mCol * TILE_SIZE; mY = mRow * TILE_SIZE; // draw the actual tile based on type. if (mMazeData[i] == PATH_TILE) canvas.drawBitmap(mImgPath, mX, mY, paint); else if (mMazeData[i] == EXIT_TILE) canvas.drawBitmap(mImgExit, mX, mY, paint); else if (mMazeData[i] == VOID_TILE) { // since our "void" tile is purely black lets draw a rectangle // instead of using an image. // tile attributes we are going to paint. mRect.left = mX; mRect.top = mY; mRect.right = mX + TILE_SIZE; mRect.bottom = mY + TILE_SIZE; paint.setColor(VOID_COLOR); canvas.drawRect(mRect, paint); } } }
/** * Closes the specified stream. * * @param stream * The stream to close. */ private static void closeStream(Closeable stream) { if (stream != null) { try { stream.close(); } catch (IOException e) { // Ignore } } }
java.io.Closeable的定义如下,定义了一个接口用来关闭那些以后不再用到的类,通常包括InputStream和OutputStream等。调用close方法可以释放资源和所持有的对象。
Defines an interface for classes that can (or need to) be closed once they are not used any longer. This usually includes all sorts ofInputStream
s andOutputStream
s. Calling theclose
method releases resources that the object holds.
A common pattern for using a Closeable
resource:
Closable foo = new Foo();
try {
...;
finally {
foo.close();
}
}
这个小游戏不是很复杂,看完代码后觉得整体的面向对象做的非常好,各个类的分工都明确,代码看起来很舒服,值得学习。
参考 http://blog.csdn.net/stefzeus/article/details/6387462