Цикл игры на Android, как контролировать скорость

Я создал свой игровой цикл через 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 был одинаковым для высокой и низкой конфигурации. устройства. Я хочу, чтобы игра работала одинаково как на высоких, так и на низких настройках. устройства.

Итак, как я могу достичь своей цели? Есть ли лучший способ сделать это?


person Devon Smith    schedule 22.09.2011    source источник
comment
source.android.com/devices/graphics/architecture.html#loops   -  person fadden    schedule 24.11.2014


Ответы (2)


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

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

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

Самый простой способ — отследить, сколько времени требуется для выполнения вашего кода обновления (скажем, 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 в класс и вызовите его при получении идентификатора сообщения. (Рабочая версия более эффективна)

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-приложению. (Это немного более продвинуто, но помогает сгладить икоту частоты кадров)

person DevByStarlight    schedule 04.10.2012

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

person M364M4N cro    schedule 22.09.2011