我已经通过 Surfaceview 创建了我的游戏循环。
游戏中的动画在功能强大的高配置设备上运行得更快,而在低配置设备上运行得更慢。
所以我尝试计算 FPS,如果 FPS 高,则应该有延迟,否则 myThreadSurfaceView.onDraw(c);
这是我用于计算的游戏循环线程代码:
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);
}
}
}
}
现在这段代码给出了我想要的所需输出,有时 FPS 对于高配置和低配置是相同的。设备。我想要的是游戏在高配置和低配置上运行相同。设备。
那么我怎样才能实现我的目标呢?有没有更好的方法来做到这一点?
最佳答案
首先:(回答你的问题)使用“时钟时间”来调整帧率而不是计算帧数。
帧计数始终取决于 CPU 功率 - 硬件越快,它调用更新循环的次数就越多。
所以你想根据执行一次循环所需的实际时间进行调整,然后根据结果调整 sleep 。
最简单的方法是跟踪执行您的更新代码需要多长时间(例如,10 毫秒)。
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);
}
第二部分:
另外...也许考虑使用 Looper/Handler 方法并发布延迟的“Runnable”消息来执行循环而不是让线程休眠。
它更灵活一点......你根本不需要处理线程休眠......你可能想要发布其他消息 无论如何到你的线程(如暂停/恢复游戏)。
有关代码 fragment 示例,请参阅 Android 文档:
- 循环器:http://developer.android.com/reference/android/os/Looper.html
- 处理程序:http://developer.android.com/reference/android/os/Handler.html
Looper 很简单 - 它只是循环等待消息排列,直到您调用 myLooper.quit()。
要处理您的时间步长更新循环调用 postDelayed(myRunnable, someDelay),您可以在其中创建一个实现可运行的类。 或者,只需将 MyUpdate 方法放在类中并在收到消息 ID 时调用它。 (可运行版本效率更高)
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)
}
下面的文章是一个更详尽的解决方案,试图弥补偶尔出现的“这个时间步跑得太长”的情况。它适用于 Flash ActionScript ,但很容易应用于您的 Android 应用程序。 (它有点高级,但它有助于平滑帧率问题)
关于Android游戏循环,如何控制速度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7513493/