Игровой цикл для Android, как контролировать скорость - PullRequest
1 голос
/ 22 сентября 2011

Я создал свой игровой цикл через 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 ]

2 голосов
/ 04 октября 2012

Первое: (чтобы ответить на ваш вопрос) Используйте «время часов» для регулировки частоты кадров, а не для подсчета количества кадров.

Подсчет кадров всегда будет зависеть от мощности процессора - чем быстрее аппаратное обеспечение, тем больше раз оно может вызвать ваш цикл обновления.

Таким образом, вы хотите отрегулировать на основе фактического времени, которое требуется для выполнения одного прохода в цикле, а затем отрегулировать режим ожидания на основе результата.

Самый простой способ - отследить, сколько времени потребуется вашему коду обновления для выполнения (скажем, 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);
}

Часть II:

Также ... Возможно, рассмотрите возможность использования подхода Looper / Handler и публикации отложенных "Runnable" сообщений для выполнения вашего цикла, а не для спящего потока.

Это немного более гибко ... вам вообще не нужно иметь дело со сном потока ... и вы, вероятно, захотите публиковать другие сообщения в любом случае (например, пауза / возобновление игры).

См. Документацию Android для примера фрагмента кода:

Looper прост - он просто зацикливается на ожидании сообщений для массива, пока вы не вызовете myLooper.quit ().

Чтобы обработать ваш цикл обновления временного шага, вызовите postDelayed (myRunnable, someDelay), где вы создадите класс, реализующий runnable. Или просто поместите метод MyUpdate в класс и вызовите его, когда получите идентификатор сообщения. (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 ActionScript, но легко применяется к вашему Android-приложению. (Это немного сложнее, но помогает сгладить частоту кадров)

0 голосов
/ 22 сентября 2011

Я видел это на уроках, стараюсь спать () после каждого кадра или что-то в этом роде, это позволит другим процессам на телефоне работать и должно заставить вашу игру работать с той же скоростью, какой бы ни была аппаратная часть, извинитечто я не могу дать вам лучший ответ, но попробуйте учебники по поверхностному виду

...