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
精彩评论