Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android 2d canvas game: FPS Jitter problem

I based my game off of the lunar lander demo, although heavily modified, and I can get around 40-50fps but the problem is it fluctuates between 40-50fps so much that it causes the moving graphics to jitter! Its very annoying and makes my game look really shitty when in fact its running at a good frame rate.

I tried setting the thread priority higher but that just made it worse... now it will fluctuate between 40-60fps...

I was thinking of limiting the FPS to about 30 so that it will be constant. Is this a good idea and does anyone else have experience or a different solution?

Thanks!

This is my run loop

@Override
    public void run() {
        while (mRun) {
            Canvas c = null;
            try {
                c = mSurfaceHolder.lockCanvas(null);
                synchronized (mSurfaceHolder) {
                    if(mMode == STATE_RUNNING){

                        updatePhysics();
                    }
                    doDraw(c);
                }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (c != null) {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
        }

private void updatePhysics() {

        now = android.os.SystemClock.uptimeMillis();

        elapsed = (now - mLastTime) / 1000.0;

        posistionY += elapsed * speed;
        mLastTime = now;
}
like image 545
Cameron Avatar asked Nov 29 '22 04:11

Cameron


2 Answers

Don't base your game's logic (object movement, etc.) updating rate on the framerate. In other words, put your drawing and logic updating code in two separate components/threads. This way your game logic is completely independent from your framerate.

Logic updating should be based on how much time has passed since the last update (let's call it delta). Therefore, if you have an object moving at 1px/millisecond, then during each update your object should do something like this:

public void update(int delta) {
    this.x += this.speed * delta;
}

So now even if your FPS lags, it won't affect your object's movement speed, since the delta will just be larger, making the object move farther to compensate (there are complications in some cases, but that's the gist of it).

And this is one way of calculating delta within your logic updating object (running in some thread loop):

private long lastUpdateTime;
private long currentTime;

public void update() {
    currentTime = System.currentTimeMillis();
    int delta = (int) (currentTime - lastUpdateTime);
    lastUpdateTime = currentTime;
    myGameObject.update(delta); // This would call something like the update method above.
}

Hope that helps! Please ask if you have any other questions; I've been making Android games myself. :)


Sample code:

Copy these two snippets (1 activity and 1 view) and run the code. The result should be a white dot smoothly falling down your screen, no matter what your FPS is. The code looks kinda complicated and long, but it's actually quite simple; the comments should explain everything.

This activity class isn't too important. You can ignore most of the code in it.

public class TestActivity extends Activity {

    private TestView view;

    public void onCreate(Bundle savedInstanceState) {
        // These lines just add the view we're using.
        super.onCreate(savedInstanceState);
        setContentView(R.layout.randomimage);
        RelativeLayout rl = (RelativeLayout) findViewById(R.id.relative_layout);
        view = new TestView(this);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                10000, 10000);
        rl.addView(view, params);

        // This starts our view's logic thread
        view.startMyLogicThread();
    }

    public void onPause() {
        super.onPause();
        // When our activity pauses, we want our view to stop updating its logic.
        // This prevents your application from running in the background, which eats up the battery.
        view.setActive(false);
    }
}

This class is where the exciting stuff is!

public class TestView extends View {

    // Of course, this stuff should be in its own object, but just for this example..
    private float position; // Where our dot is
    private float velocity; // How fast the dot's moving

    private Paint p; // Used during onDraw()
    private boolean active; // If our logic is still active

    public TestView(Context context) {
        super(context);
        // Set some initial arbitrary values
        position = 10f;
        velocity = .05f;
        p = new Paint();
        p.setColor(Color.WHITE);
        active = true;
    }

    // We draw everything here. This is by default in its own thread (the UI thread).
    // Let's just call this thread THREAD_A.
    public void onDraw(Canvas c) {
        c.drawCircle(150, position, 1, p);
    }

    // This just updates our position based on a delta that's given.
    public void update(int delta) {
        position += delta * velocity;
        postInvalidate(); // Tells our view to redraw itself, since our position changed.
    }

    // The important part!
    // This starts another thread (let's call this THREAD_B). THREAD_B will run completely
    // independent from THREAD_A (above); therefore, FPS changes will not affect how
    // our velocity increases our position.
    public void startMyLogicThread() {
        new Thread() {
            public void run() {
                // Store the current time values.
                long time1 = System.currentTimeMillis();
                long time2;

                // Once active is false, this loop (and thread) terminates.
                while (active) {
                    try {
                        // This is your target delta. 25ms = 40fps
                        Thread.sleep(25);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }

                    time2 = System.currentTimeMillis(); // Get current time
                    int delta = (int) (time2 - time1); // Calculate how long it's been since last update
                    update(delta); // Call update with our delta
                    time1 = time2; // Update our time variables.
                }
            }
        }.start(); // Start THREAD_B
    }

    // Method that's called by the activity
    public void setActive(boolean active) {
        this.active = active;
    }
}
like image 104
Andy Zhang Avatar answered Dec 23 '22 07:12

Andy Zhang


I am thinking there might be, not really something wrong with some of the above code, but rather an inefficiency. I am talking about this code...

   // The important part!
// This starts another thread (let's call this THREAD_B). THREAD_B will run completely
// independent from THREAD_A (above); therefore, FPS changes will not affect how
// our velocity increases our position.
public void startMyLogicThread() {
    new Thread() {
        public void run() {
            // Store the current time values.
            long time1 = System.currentTimeMillis();
            long time2;

            // Once active is false, this loop (and thread) terminates.
            while (active) {
                try {
                    // This is your target delta. 25ms = 40fps
                    Thread.sleep(25);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }

                time2 = System.currentTimeMillis(); // Get current time
                int delta = (int) (time2 - time1); // Calculate how long it's been since last update
                update(delta); // Call update with our delta
                time1 = time2; // Update our time variables.
            }
        }
    }.start(); // Start THREAD_B
}

Specifically, I am thinking about the following lines...

// This is your target delta. 25ms = 40fps
Thread.sleep(25);

It seems to me that just having the thread hang out doing nothing is a waste of valuable processing time, when in fact what you want to be doing is performing the updates, then, if the updates have taken less time than the 25 millis, then sleep the thread for the difference of what was used during the update and 25 millis (or whatever your chosen frame rate is). In this way the update will happen while the current frame is being rendered, and will be completed so the next frame update uses the updated values.

The only problem I can think of here is that some kind of syncronization will need to occur so that the current frame render does not use partially updated values. Perhaps update into a new instance of your set of values, and then make the new instance the current instance just before rendering.

I think I remember reading something in a graphics book about the goal being to perform as many updates as you can while staying within your desired frame rate, then, and only them, perform a screen update.

This of course will require one thread to drive the updates - if you use a SurfaceView, the render is controlled by this thread when you lock the canvas (in theory, according to my understanding anyway).

So, in code, it would be more like...

// Calculate next render time
nextRender = System.currentTimeInMillis() + 25;

while (System.currentTimeInMillis() < nextRender)
{
    // All objects must be updated here
    update();

    // I could see maintaining a pointer to the next object to be updated,
    // such that you update as many objects as you can before the next render, and 
    // then continue the update from where you left off in the next render...
}

// Perform a render (if using a surface view)
c = lockCanvas() blah, blah...
// Paint and unlock

// If using a standard view
postInvalidate();

Good luck and any feedback from anyone using this would surely help us all learn something...

rpbarbati

like image 24
Rodney Barbati Avatar answered Dec 23 '22 09:12

Rodney Barbati