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

时间:2011-09-22 10:44:36

标签: 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对于高配置和低配置都是相同的。设备。我想要的是游戏在高配置和低配置上运行相同。设备

那么我怎样才能实现目标呢?有没有更好的方法呢?

2 个答案:

答案 0 :(得分:2)

首先:(回答你的问题)使用“时钟时间”调整帧速率而不是计算帧数。

帧计数总是依赖于CPU功率 - 硬件越快,它可以调用更新循环的次数越多。

因此,您希望根据执行一次循环所需的实际时间进行调整,然后根据结果调整睡眠。

最简单的方法是跟踪执行 更新代码所需的时间(例如,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”消息来执行你的循环而不是睡眠线程。

它更灵活......你根本不需要处理线程睡眠......你可能想发布其他消息 无论如何你的线程(如暂停/恢复游戏)。

有关代码段示例,请参阅Android文档:

Looper很简单 - 它只是循环等待消息到数组,直到你调用myLooper.quit()。

要处理你的时间步更新循环调用postDelayed(myRunnable,someDelay),你在其中创建一个实现runnable的类。 或者,只需将MyUpdate方法放入类中,并在接收消息ID时调用它。 (Runnable版本效率更高)

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动作脚本,但很容易应用于您的Android应用程序。 (它有点先进,但它有助于平滑帧速率打嗝)

答案 1 :(得分:0)

我在教程中看过这个,在每一帧之后尝试sleep(),或类似的东西,这将允许手机上的其他进程工作,它应该让你的游戏运行速度与硬件无关,抱歉我不能给你一个更好的答案,但尝试表面视图教程