开发者

Android game loop, how to control speed

I have created my game loop through Surfaceview. Animations in the game run faster on powerful devices with high configuration and slower on low configuration dev开发者_运维问答ices. So I tried to count FPS and if FPS is high then there should be delay, otherwise no delay for myThreadSurfaceView.onDraw(c);

Here is my code of game loop thread that I used for calculation:

private long mLastTime;     

/** Variables for the counter */
private int frameSamplesCollected = 0;
private int frameSampleTime = 0;
private int fps = 0;

@Override
public void run() {    
  while (myThreadRun) {
    Canvas c = null;
    try {
      c = myThreadSurfaceHolder.lockCanvas(null);
      synchronized (myThreadSurfaceHolder) 
      {                                                                                
        long now = System.currentTimeMillis();

        if (mLastTime != 0) {

          //Time difference between now and last time we were here
          int time = (int) (now - mLastTime);
          frameSampleTime += time;
          frameSamplesCollected++;

          //After 10 frames
          if (frameSamplesCollected == 10) 
          {
            //Update the fps variable
            fps = (int) (10000 / frameSampleTime);

            //Reset the sampletime + frames collected
            frameSampleTime = 0;
            frameSamplesCollected = 0;
          }
        }
        mLastTime = now;
      }

      if(fps>25)
      {
        try {
          myThreadSurfaceView.onDraw(c);
          TimeUnit.MILLISECONDS.sleep(35);
          //Thread.sleep(35);
        } 

        catch (InterruptedException e) 
        {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }

      else
      {
        myThreadSurfaceView.onDraw(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) {
        myThreadSurfaceHolder.unlockCanvasAndPost(c);
      }
    }
  }
}

Now this code is giving the desired output as I want sometimes FPS will be the same for high and low config. devices. What I want is that the game runs the same on both high and low config. devices.

So how can I achieve my goal? Is there any better way to do this?


First: (to answer your question) Use "clock time" to adjust frame rate rather than counting the number of frames.

Frame counting is always going to be CPU power dependent - faster the hardware the more times it can call your update loop.

So you want to adjust based on the actual time it takes to execute one pass through your loop and then adjust the sleep based on the result.

The easiest way is to track how long it takes for your update code to execute (say, 10 milliseconds).

private static final long ourTarget_millisPerFrame = 33; // ~30 FPS

public void MyUpdate() {

    long startTime = SystemClock.uptimeMillis();

    // ... Do your stuff here ...

    long stopTime = SystemClock.uptimeMillis();

    // How much time do *we* require to do what we do (including drawing to surface, etc)?
    long howLongItTakesForUsToDoOurWork = stopTime - startTime;

    // So, say we took 10 millis
    // ... that leaves (33 - 10 =) 23 millis of "time to spare"
    // ... so that's how long we'll sleep

    long timeToWait = ourTarget_millisPerFrame - howLongItTakesForUsToDoOurWork;

    // But apply a minimum wait time so we don't starve the rest of the system
    if ( timeToWait < 2 )
        timeToWait = 2;

    // Lullaby time    
    sleep(timeToWait);
}

Part II:

Also ... Perhaps consider using a Looper / Handler approach and posting delayed "Runnable" messages to execute your loop rather than sleep'ing the thread.

It's a bit more flexible ... you don't need to deal with thread sleep at all ... and you will probably want to post other messages to your thread anyway (like pause/resume game).

See the Android documentation for an code snippet example:

  • Looper: http://developer.android.com/reference/android/os/Looper.html
  • Handler: http://developer.android.com/reference/android/os/Handler.html

The Looper is simple - it just loops waiting for messages to array until you call myLooper.quit().

To process your timestep update loop call postDelayed(myRunnable, someDelay) where you create a class implementing runnable. Or, just put MyUpdate method in the class and call it when receive a message ID. (Runnable version is more efficient)

class MyUpdateThread extends Thread {
    public static final int MSGID_RUN_UPDATE = 1;
    public static final int MSGID_QUIT_RUNNING = 2;

    public MyMessageHandler mHandler;
    private MyUpdateRunnable myRunnable = new MyUpdateRunnable();

    private boolean mKeepRunning;

    // The *thread's* run method
    public run() {
        // Setup the Looper
        Looper.prepare();

        // Create the message handler
        // .. handler is auto-magically attached to this thread
        mHandler = new MyMessageHandler();

        // Prime the message queue with the first RUN_UPDATE message
        this.mKeepRunning = true;
        mHandler.post(myRunnable);

        // Start the Looper
        Looper.loop();
    } // end run fun (of MyThread class)    

    class MyMessageHandler extends Handler {
        @Override
        public void handleMessage(final Message msg) {
            try {
                // process incoming messages here
                switch (msg.what) {
                case MSGID_RUN_UPDATE:
                    // (optional)
                    // ... could call MyUpdate() from here instead of making it a runnable (do either/or but not both)
                    break;
                case MSGID_QUIT_RUNNING:
                    mKeepRunning = false; // flag so MyRunnable doesn't repost
                    Looper.myLooper().quit();
                    break;
                 }
             } catch ( Exception ex ) {}

    }


    class MyUpdateRunnable implements Runnable {
        public void run() {
            try {

                // ---- Your Update Processing ----
                // -- (same as above ... without the sleep() call) --

                // Instead of sleep() ... we post a message to "ourself" to run again in the future
                mHandler.postDelayed ( this, timeToWait );

            } catch ( Exception ex ) {}
        } // end run fun (my runnable)
    } // end class (my runnable)
}

The following article is a more elaborate solution with attempts to compensate for the occasional "this one timestep ran long". It's for Flash actionscript but easily applied to your Android application. (It's a bit more advanced but it helps smooth frame rate hiccups)

  • http://www.8bitrocket.com/2008/4/8/Tutorial-Creating-an-Optimized-AS3-Game-Timer-Loop/


I have seen this on tutorials, try to sleep() after every frame, or something like that, this will allow other processes on the phone to work and it should make ur game run the same speed whatever the hardware is, sorry that i cant give u a better answer, but try surface view tutorials

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜