开源项目是个学东西的好地方。http://code.google.com/p/apps-for-android/ ,我把代码上传一份http://download.youkuaiyun.com/detail/pipisky2006/3672322
Amazed是个重力感应的小球的游戏。
[效果图]
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();
}
其他的状态比较简单,运行状态时,透传了当前view的Canvas,调用迷宫Maze来绘制迷宫的地板,调用子弹Marble绘制子弹的位置。
/**
* 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.youkuaiyun.com/stefzeus/article/details/6387462