Android游戏循环,如何控制速度

标签 android canvas surfaceview frame-rate

我已经通过 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 文档:

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/

相关文章:

javascript - iOS 上的全屏 <canvas> 元素 : glitch on scroll

javascript - 上传前用 Canvas 调整图像大小

java - 滚动屏幕位图,在 SurfaceView 上向下一行不起作用

android - GCM Activity 被迫关闭

android - 在 mac osx 上使用 Eclipse 检测不到小米 mi4

android - React Native Android 上的启动屏幕很快变成白色

android - 为什么 onCreate 方法在使用 Google Play 服务位置 API 获取位置后不执行下一行?

javascript - 将图像放入 HTML Canvas(适合宽度和高度)

Android 自定义相机 SurfaceView OnClick 在 sdcard 上保存图像?

java - 在自定义 surfaceView 中绘制时在主要 Activity 中创建按钮