Hello to all developers! First I want to say that I have read many articles on this subject in this forum and on some external resources(very helpful was Robert Green's diary at www.rbgrn.net and www.droidnova.com).
However, despite all of this I want to start topic regarding FPS, and ask for advice from experienced game developers on Android platform. My main question is "How to improve FPS when draw on Canvas?" I have implemented simple 2D arcade game skeleton for testing purpose. Now I have ~20 FPS and want to increase this value to 40-50 FPS, if this is possible of course. I know about Open GL ES, but so far I am interested in Canvas. In every frame I draw following stuff on the screen: - Canvas.drawColor(Color.BLACK) - to clear the screen - 1 spaceship PNG 24x24 image 1.25 kb - 5 asteroids PNG 64x64 image ~8 kb each - from 1 to 30 bullets PNG 8x8 image 299 b - 4 30x30 Rectangles - to control objects on the screen After running my app, in logcat I can see following output data: - Average FPS: 20 (Total frames drawn: 1945 in 97 seconds) - Average onDraw: 32 ms (clear canvas: 3, draw game stuff: 26, draw controls: 1) - Average updatePhysics: 1 ms >From this output I can assume, that my main problem here is "draw game stuff" wich includes: - draw 1 spaceship - draw 5 asteroids - draw from 1 to 30 bullets Thanks in advance Best Regards, Andre P.S.: sorry for my English Here is my code: --------------------------------------GAME VIEW-------------------------------------- package com.example.game.asteroids; import java.util.Random; import android.content.Context; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; public class GameView extends SurfaceView implements SurfaceHolder.Callback { private static final String TAG = "Asteroids"; private static final float MAX_VELOCITY = 3.0f; private static final int MAX_ASTEROIDS = 5; private static final int MAX_BULLETS = 30; private static final int[] ASTEROID_IMG_RES = {R.drawable.asteroid1, R.drawable.asteroid2, R.drawable.asteroid3, R.drawable.asteroid4, R.drawable.asteroid5}; //private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_4444; private GameLoop mGameLoop; private GameEntity mSpaceship; private GameEntity[] mAsteroids; private GameEntity[] mBullets; private int mCurrentBullet; private boolean mRight; private boolean mLeft; private boolean mUp; private boolean mFire; private long mFireTime; private Rect mVelocityControl; private Paint mVelocityControlPaint; private Rect mLeftControl; private Paint mLeftControlPaint; private Rect mRightControl; private Paint mRightControlPaint; private Rect mFireControl; private Paint mFireControlPaint; private Random mRandom; private long mStartMillis; private int mFrameCounter; private int mFps; private int mOverallSeconds; private int mOverallFrameCounter; private long mOnDrawTimer; private int mOnDrawCounter; private long mDrawColorTimer; private long mDrawStuffTimer; private long mDrawControlsTimer; private long mUpdateTimer; private int mUpdateCounter; public GameView(Context context) { super(context); SurfaceHolder holder = getHolder(); holder.addCallback(this); setFocusable(true); mGameLoop = new GameLoop(holder, this); mRandom = new Random(); mRight = false; mLeft = false; mUp = false; mFire = false; mVelocityControl = new Rect(0, 0, 30, 30); mVelocityControlPaint = new Paint(); mVelocityControlPaint.setColor(Color.GREEN); mLeftControl = new Rect(31, 0, 61, 30); mLeftControlPaint = new Paint(); mLeftControlPaint.setColor(Color.YELLOW); mRightControl = new Rect(62, 0, 92, 30); mRightControlPaint = new Paint(); mRightControlPaint.setColor(Color.BLUE); mFireControl = new Rect(93, 0, 123, 30); mFireControlPaint = new Paint(); mFireControlPaint.setColor(Color.CYAN); mStartMillis = System.currentTimeMillis(); } // private Bitmap loadBitmap(Drawable sprite, Bitmap.Config cfg) { // int width = sprite.getIntrinsicWidth(); // int height = sprite.getIntrinsicHeight(); // Bitmap bitmap = Bitmap.createBitmap(width, height, cfg); // Canvas canvas = new Canvas(bitmap); // sprite.setBounds(0, 0, width, height); // sprite.draw(canvas); // return bitmap; // } private void initSpaceship() { mSpaceship = new GameEntity(BitmapFactory.decodeResource(getResources (), R.drawable.spaceship)); //loadBitmap(getResources().getDrawable(R.drawable.spaceship), BITMAP_CONFIG)); //Log.d(TAG, "bitmap config: " + bitmap.getConfig().toString()); mSpaceship.setX(getWidth() / 2); mSpaceship.setY(getHeight() / 2); mSpaceship.setAlive(true); } private void initAsteroids() { GameEntity[] asteroids = new GameEntity[MAX_ASTEROIDS]; Random rnd = mRandom; float angle; for(int i = 0; i < MAX_ASTEROIDS; i++) { asteroids[i] = new GameEntity(BitmapFactory.decodeResource (getResources(), ASTEROID_IMG_RES[rnd.nextInt(5)])); //loadBitmap(getResources().getDrawable(ASTEROID_IMG_RES[rnd.nextInt (5)]), BITMAP_CONFIG)); asteroids[i].setRotationVelocity(rnd.nextInt(3) + 1); asteroids[i].setX(rnd.nextInt(getWidth()) - 20); asteroids[i].setY(rnd.nextInt(getHeight()) - 20); asteroids[i].setMoveAngle(rnd.nextInt(360)); angle = asteroids[i].getMoveAngle() - 90; asteroids[i].setVelocityX(calculateMoveAngleX(angle)); asteroids[i].setVelocityY(calculateMoveAngleY(angle)); asteroids[i].setAlive(true); } mAsteroids = asteroids; } private void initBullets() { GameEntity[] bullets = new GameEntity[MAX_BULLETS]; for(int i = 0; i < MAX_BULLETS; i++) { bullets[i] = new GameEntity(BitmapFactory.decodeResource (getResources(), R.drawable.plasmashot)); //loadBitmap(getResources().getDrawable(R.drawable.plasmashot), BITMAP_CONFIG)); } mBullets = bullets; } @Override protected void onDraw(Canvas canvas) { long mainStart = System.currentTimeMillis(); mFrameCounter++; if(System.currentTimeMillis() > mStartMillis + 1000) { mOverallSeconds++; mOverallFrameCounter += mFrameCounter; mStartMillis = System.currentTimeMillis(); mFps = mFrameCounter; mFrameCounter = 0; Log.d(TAG, "FPS: " + mFps); } long colorStart = System.currentTimeMillis(); canvas.drawColor(Color.BLACK); long colorFinish = System.currentTimeMillis(); long stuffStart = System.currentTimeMillis(); drawSpaceship(canvas); drawAsteroids(canvas); drawBullets(canvas); long stuffFinish = System.currentTimeMillis(); long controlsStart = System.currentTimeMillis(); canvas.setMatrix(null); canvas.drawRect(mVelocityControl, mVelocityControlPaint); canvas.drawRect(mLeftControl, mLeftControlPaint); canvas.drawRect(mRightControl, mRightControlPaint); canvas.drawRect(mFireControl, mFireControlPaint); long controlsFinish = System.currentTimeMillis(); long mainFinish = System.currentTimeMillis(); mOnDrawTimer += mainFinish - mainStart; mDrawColorTimer += colorFinish - colorStart; mDrawStuffTimer += stuffFinish - stuffStart; mDrawControlsTimer += controlsFinish - controlsStart; mOnDrawCounter++; } public void updatePhysics() { long start = System.currentTimeMillis(); updateSpaceshipPhysics(); updateAsteroidsPhysics(); updateBulletsPhysics(); checkForCollisions(); long finish = System.currentTimeMillis(); mUpdateTimer += finish - start; mUpdateCounter++; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder holder) { initSpaceship(); initAsteroids(); initBullets(); mGameLoop.setRunning(true); mGameLoop.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.d(TAG, "Average FPS: " + mOverallFrameCounter / mOverallSeconds + " (Total frames drawn: " + mOverallFrameCounter + " in " + mOverallSeconds + " seconds)"); Log.d(TAG, "Average onDraw: " + mOnDrawTimer / mOnDrawCounter + " ms" + " (clear canvas: " + mDrawColorTimer / mOnDrawCounter + ", draw game stuff: " + mDrawStuffTimer / mOnDrawCounter + ", draw controls: " + mDrawControlsTimer / mOnDrawCounter + ")"); Log.d(TAG, "Average updatePhysics: " + mUpdateTimer / mUpdateCounter + " ms"); boolean retry = true; mGameLoop.setRunning(false); while(retry) { try { mGameLoop.join(); retry = false; } catch(InterruptedException ex) { } } } private void drawSpaceship(Canvas canvas) { float x = mSpaceship.getX(); float y = mSpaceship.getY(); float angle = mSpaceship.getFaceAngle(); int width = mSpaceship.getWidth(); int height = mSpaceship.getHeight(); //canvas.translate(x, y); canvas.rotate(angle, x, y); canvas.drawBitmap(mSpaceship.getBitmap(), x - width / 2, y - height / 2, null); } private void drawAsteroids(Canvas canvas) { GameEntity[] asteroids = mAsteroids; for(int i = 0; i < MAX_ASTEROIDS; i++) { if(asteroids[i].isAlive()) { canvas.setMatrix(null); //canvas.translate(asteroids[i].getX(), asteroids[i].getY()); float x = asteroids[i].getX(); float y = asteroids[i].getY(); int width = asteroids[i].getWidth(); int height = asteroids[i].getHeight(); canvas.rotate(asteroids[i].getMoveAngle(), x, y); canvas.drawBitmap(asteroids[i].getBitmap(), x - width / 2, y - height / 2, null); } } } private void drawBullets(Canvas canvas) { GameEntity[] bullets = mBullets; for(int i = 0; i < MAX_BULLETS; i++) { if(bullets[i].isAlive()) { canvas.setMatrix(null); //canvas.translate(bullets[i].getX(), bullets[i].getY()); float x = bullets[i].getX(); float y = bullets[i].getY(); int width = bullets[i].getWidth(); int height = bullets[i].getHeight(); canvas.rotate(bullets[i].getMoveAngle() + 90, x, y); canvas.drawBitmap(bullets[i].getBitmap(), x - width / 2, y - height / 2, null); } } } private void updateSpaceshipPhysics() { GameEntity spaceship = mSpaceship; if(mLeft) { spaceship.incFaceAngle(-5); if(spaceship.getFaceAngle() < 0) { spaceship.setFaceAngle(360 - 5); } } if(mRight) { spaceship.incFaceAngle(5); if(spaceship.getFaceAngle() > 360) { spaceship.setFaceAngle(5); } } if(mUp) { spaceship.setMoveAngle(spaceship.getFaceAngle() - 90); if(Math.abs(spaceship.getVelocityX()) < MAX_VELOCITY) { spaceship.incVelocityX(calculateMoveAngleX(spaceship.getMoveAngle ()) * 0.1f); } if(Math.abs(spaceship.getVelocityY()) < MAX_VELOCITY) { spaceship.incVelocityY(calculateMoveAngleY(spaceship.getMoveAngle ()) * 0.1f); } } spaceship.incX(spaceship.getVelocityX()); if(spaceship.getX() < -10) { spaceship.setX(getWidth() + 10); } else if(spaceship.getX() > getWidth() + 10) { spaceship.setX(-10); } spaceship.incY(spaceship.getVelocityY()); if(spaceship.getY() < -10) { spaceship.setY(getHeight() + 10); } else if(spaceship.getY() > getHeight() + 10) { spaceship.setY(-10); } mSpaceship = spaceship; } private void updateAsteroidsPhysics() { GameEntity[] asteroids = mAsteroids; for(int i = 0; i < MAX_ASTEROIDS; i++) { if(asteroids[i].isAlive()) { asteroids[i].incX(asteroids[i].getVelocityX()); if(asteroids[i].getX() < -20) { asteroids[i].setX(getWidth() + 20); } else if(asteroids[i].getX() > getWidth() + 20) { asteroids[i].setX(-20); } asteroids[i].incY(asteroids[i].getVelocityY()); if(asteroids[i].getY() < -20) { asteroids[i].setY(getHeight() + 20); } else if(asteroids[i].getY() > getHeight() + 20) { asteroids[i].setY(-20); } asteroids[i].incMoveAngle(asteroids[i].getRotationVelocity()); if(asteroids[i].getMoveAngle() < 0) { asteroids[i].setMoveAngle(360 - asteroids[i].getRotationVelocity ()); } else if(asteroids[i].getMoveAngle() > 360) { asteroids[i].setMoveAngle(asteroids[i].getRotationVelocity()); } } } mAsteroids = asteroids; } private void updateBulletsPhysics() { GameEntity[] bullets = mBullets; int currentBullet = mCurrentBullet; if(mFire && (System.currentTimeMillis() > mFireTime + 250)) { mFireTime = System.currentTimeMillis(); currentBullet++; if(currentBullet > MAX_BULLETS - 1) { currentBullet = 0; } bullets[currentBullet].setAlive(true); bullets[currentBullet].setX(mSpaceship.getX()); bullets[currentBullet].setY(mSpaceship.getY()); bullets[currentBullet].setMoveAngle(mSpaceship.getFaceAngle() - 90); float angle = bullets[currentBullet].getMoveAngle(); float spaceshipVelocityX = mSpaceship.getVelocityX(); float spaceshipVelocityY = mSpaceship.getVelocityY(); bullets[currentBullet].setVelocityX(spaceshipVelocityX + calculateMoveAngleX(angle) * 2); bullets[currentBullet].setVelocityY(spaceshipVelocityY + calculateMoveAngleY(angle) * 2); } for(int i = 0; i < MAX_BULLETS; i++) { if(bullets[i].isAlive()) { bullets[i].incX(bullets[i].getVelocityX()); if(bullets[i].getX() < 0 || bullets[i].getX() > getWidth()) { bullets[i].setAlive(false); } bullets[i].incY(bullets[i].getVelocityY()); if(bullets[i].getY() < 0 || bullets[i].getY() > getHeight()) { bullets[i].setAlive(false); } } } mBullets = bullets; mCurrentBullet = currentBullet; } private void checkForCollisions() { GameEntity[] asteroids = mAsteroids; GameEntity[] bullets = mBullets; GameEntity spaceship = mSpaceship; for(int i = 0; i < MAX_ASTEROIDS; i++) { if(asteroids[i].isAlive()) { for(int j = 0; j < MAX_BULLETS; j++) { if(bullets[j].isAlive()) { if(asteroids[i].getBounds().intersect(bullets[j].getBounds())) { bullets[j].setAlive(false); asteroids[i].setAlive(false); continue; } } } if(asteroids[i].getBounds().intersect(spaceship.getBounds())) { asteroids[i].setAlive(false); spaceship.setX(getWidth() / 2); spaceship.setY(getHeight() / 2); spaceship.setFaceAngle(0); spaceship.setVelocityX(0); spaceship.setVelocityY(0); continue; } } } mAsteroids = asteroids; mBullets = bullets; mSpaceship = spaceship; } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); int x = (int)event.getX(); int y = (int)event.getY(); if(action == MotionEvent.ACTION_DOWN) { if(mVelocityControl.contains(x, y)) { mUp = true; } else if(mLeftControl.contains(x, y)) { mLeft = true; } else if(mRightControl.contains(x, y)) { mRight = true; } else if(mFireControl.contains(x, y)) { mFire = true; } } else if(action == MotionEvent.ACTION_UP) { mUp = false; mLeft = false; mRight = false; mFire = false; mSpaceship.setVelocityX(0.0f); mSpaceship.setVelocityY(0.0f); } return true; } private float calculateMoveAngleX(float angle) { return (float)Math.cos(angle * Math.PI / 180); } private float calculateMoveAngleY(float angle) { return (float)Math.sin(angle * Math.PI / 180); } } --------------------------------------GAME LOOP-------------------------------------- package com.example.game.asteroids; import android.graphics.Canvas; import android.view.SurfaceHolder; public class GameLoop extends Thread { private SurfaceHolder mHolder; private GameView mGameView; private boolean mRunning; public GameLoop(SurfaceHolder holder, GameView gameView) { mHolder = holder; mGameView = gameView; mRunning = true; } public void setRunning(boolean running) { mRunning = running; } public SurfaceHolder getSurfaceHolder() { return mHolder; } @Override public void run() { Canvas c; while(mRunning) { c = null; try { Thread.sleep(0); c = mHolder.lockCanvas(); synchronized(mHolder) { mGameView.updatePhysics(); mGameView.onDraw(c); } } catch(InterruptedException ex) { } finally { if(c != null) { mHolder.unlockCanvasAndPost(c); } } } } }
-- You received this message because you are subscribed to the Google Groups "Android Developers" group. To post to this group, send email to android-developers@googlegroups.com To unsubscribe from this group, send email to android-developers+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/android-developers?hl=en