AS3: Насколько точны метод getTimer() и класс Timer?

Я нахожусь в процессе создания игры (shmup), и я начал подвергать сомнению точность таймеров в ActionScript. Конечно, они достаточно точны, когда вы хотите рассчитать время порядка нескольких секунд или десятых секунды, но, кажется, это работает довольно плохо, когда вы добираетесь до более мелких диапазонов. Это делает довольно трудным делать такие вещи, как космический корабль, который запускает сотню лазеров в секунду.

В следующем примере я проверил, как долго (в среднем) интервалы были между 1000 тиков таймера, предназначенными для 30 мс. Снова и снова результаты ~35-36мс. Уменьшая время, я обнаружил, что задержка таймера составляет ~16-17 мс. Это дает мне максимальный fps ~60, что замечательно визуально, но также означает, что я не могу запустить более 60 лазеров в секунду:-(. Я провел этот тест несколько раз на 100 и 1000 петлях, но для обоих 30 мс тест и 1 мс тест результаты не изменились. В конце я печатаю в textField, потому что использование trace() и запуск swf в режиме отладки, похоже, негативно влияют на тест. Так что мне интересно:

  • Является ли этот тест достойным показателем производительности класса Timer, или мои результаты сомнительны?
  • Будут ли эти результаты кардинально меняться на других машинах?

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

 пакет 
{
импорт flash.display.Sprite; импорт flash.events.TimerEvent; import flash.text.TextField; import flash.utils.getTimer; импорт flash.utils.Timer;

public class testTimerClass extends Sprite
{
    private var testTimer:Timer = new Timer(30, 1000);
    private var testTimes:Array = new Array();
    private var testSum:int = 0;
    private var testAvg:Number;
    private var lastTime:int;
    private var thisTime:int;

    public function testTimerClass()
    {
        testTimer.addEventListener(TimerEvent.TIMER, step);
        testTimer.addEventListener(TimerEvent.TIMER_COMPLETE, printResults);
        lastTime = getTimer();
        testTimer.start();
    }

    private function step(event:TimerEvent):void
    {
        thisTime = getTimer();
        testTimes.push(thisTime - lastTime);
        lastTime = thisTime;
    }

    private function printResults(event:TimerEvent):void
    {
        while (testTimes.length > 0)
        {
            testSum += testTimes.pop();
        }
        testAvg = testSum / Number(testTimer.repeatCount);              
        var txtTestResults:TextField = new TextField();
        txtTestResults.text = testAvg.toString();
        this.addChild(txtTestResults);  
    }       
}

}

Я полагаю, что лучший путь - просто нарисовать несколько лазеров в одном кадре с разными позициями и избегать использования более одного объекта Timer.

редактировать: я использовал stage.frameRate для изменения рендера frameRate и провел тест на нескольких частотах кадров, но изменений не было.

5 ответов

Решение

Тиник Уро (инженер Flash Player) написал интересное сообщение в блоге об этой проблеме некоторое время назад.

Вместо того, чтобы вообще использовать класс таймера, я бы добавил свой код обновления и обработчик событий enterframe.

Затем используйте getTimer(), чтобы узнать, сколько времени прошло с момента последнего обновления, а затем немного математики для расчета движения, лазерного "нереста" и т. Д. Таким образом, игра должна работать одинаково, независимо от того, получаете ли вы 60FPS или 10FPS.

Это также предотвращает странное поведение игры, если временное падение FPS или игра запущена на медленной машине.

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

В ответ на комментарий Джорелли ниже:

Проблема обнаружения столкновений может быть решена с помощью лучшего кода, который не только проверяет текущее состояние, но также и то, что произошло между предыдущим состоянием и текущим. У меня никогда не было проблем с клавиатурой. Может быть, вы должны попробовать этот классный маленький класс: http://www.bigroom.co.uk/blog/polling-the-keyboard-in-actionscript-3

BitmapData.scroll и BitmapData.copyPixels являются самым быстрым способом рендеринга во флэш-памяти прямо сейчас, хотя в некоторых системах использование мувиклипов с ускорением GPU немного быстрее. Каждый квад отображается в OpenGL как отдельная текстура, поэтому это не то, что вы ожидаете от обычного GPU-ускоренного приложения. Я знаю, потому что я профилировал оба пути в своей игре. Одна вещь, которую вы можете сделать, чтобы улучшить время отклика, - вызвать обновление игры из обработчиков входных событий. Таким образом, вы слышите правильно синхронизированные звуки, даже когда частота кадров в вашей игре ниже, и приложение будет чувствовать себя более отзывчивым.

Я только что попробовал запустить ваш пример кода, в точности как есть, кроме как в виде фрейм-скрипта, и там, где я сижу, таймер работает точно так, как вы ожидаете. При таймере 30 мс среднее значение составляет около 33-34, а при таймере 3 мс - около 3,4 или 3,5. С таймером 1 мс я получаю от 1,4 до 1,6 более тысячи испытаний. Так работает во Flash и в браузере.

Что касается точности, см. Сообщение в блоге Тиника в ответе Люка. Но для верхнего предела по частоте, если вы получаете события не быстрее, чем 16 мсек, используя только пример кода, который вы разместили, либо что-то странное, либо, возможно, это верхний предел того, насколько быстро ваш браузер выдает сообщения синхронизации Flash. Если вы получаете эти результаты в своей реальной игре, я думаю, что у вас просто есть синхронный код, который блокирует события таймера.

Еще одна вещь - я знаю, что это не то, что вы просили, но было бы разумнее обрабатывать вашу игровую логику в обработчике ENTER_FRAME. Неважно, запускается ли код для запуска лазеров каждые 30 мс или каждые 3 мс, экран перерисовывается только один раз за кадр (если только вы не заставляете его обновляться чаще, чего, вероятно, не следует). Таким образом, все, что происходит между обновлениями экрана, просто накладные расходы, которые снижают общую частоту кадров, так как с небольшой сообразительностью должно быть возможно достичь точно таких же результатов, которые вы получите от таймера, который выполняется чаще. Например, вместо создания лазера каждые 3 мс, вы можете создавать 10 лазеров каждые 30 мс и перемещать каждый на определенное расстояние по траектории полета, как если бы он был создан на 3, 6 или 9 мс ранее.

Другой естественный способ запустить вашу логику вне фреймовых событий - создать игровой цикл, который "теоретически" будет выполняться каждые T миллисекунд. Тогда в вашем ENTER_FRAME обработчик, просто повторяйте цикл F/T раз, где F - количество мс, прошедших с момента последнего события кадра. Таким образом, если вы хотите, чтобы игра теоретически обновлялась каждые 5 мс, и вы хотите, чтобы экран обновлялся со скоростью 30 кадров в секунду, вы просто публикуете со скоростью 30 кадров в секунду, и в своем ENTER_FRAME Обработчик, вы будете вызывать ваш основной игровой цикл 5-7 раз подряд, в зависимости от того, сколько времени прошло с последнего кадра. На экране это даст вам такие же результаты, как если бы вы использовали событие таймера 5 мс, и это избавит от значительной части накладных расходов.

Это дает мне максимальный fps ~60, что замечательно визуально, но также означает, что я не могу запустить более 60 лазеров в секунду:-(

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

Я бы согласился, что возможностей флеш-плеера, вероятно, недостаточно для того, чего вы пытаетесь достичь.

Другие вопросы по тегам