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

Reply via email to