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 [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/android-developers?hl=en