Framework: Fps

Есть свободная минутка, как раз чтобы рассказать про небольшой, но очень полезный, для моего фреймворка, утилитный класс.
(Утилитными классами я называю классы, которые реализуют только статические методы.)

Fps

elmortem.core.Fps
Frame Per Second — кадры в секунду. данный класс нужен для того, чтобы знать, сколько времени прошло за кадр (delta) и сколько кадров обрабатывается в секунду (fps). Все обновление объектов в фреймворке у меня привязано к секундам. Движение, анимация. Практически любой объект реализует метод update, который принимает в качестве параметра delta — время в секундах, пройденное с прошлой обработки кадра.

package elmortem.core {
        import flash.utils.getTimer;
        
        public class Fps {
                static private var last_time:int = 0;
                static private var last_fps_time:int = 0;
                // delta
                static public var delta:Number = 0;
                // fps
                static private var _fps:int = 0;
                static private var fps_num:int = 0;
                
                static public function start():void {
                        last_time = last_fps_time = getTimer();
                        delta = 0;
                        _fps = 0;
                        fps_num = 0;
                }
                static public function step():void {
                        // delta
                        delta = (getTimer() - last_time) / 1000;
                        
                        last_time = getTimer();
                        fps_num++;
                        if(last_time - last_fps_time >= 1000) {
                                _fps = fps_num;
                                fps_num = 0;
                                last_fps_time = last_time;
                        }
                }
                static public function get fps():int {
                        return _fps;
                }
        }
}


Используется этот класс следующим образом.
public class Gameplay extends State {
  private var ball:Ball;

  public function Gameplay(attrIn:Object = null) {
    super(attrIn);
  }
  override protected function init():void {
    addChild(ball = new Ball());
    addEventListener(Event.ENTER_FRAME, onFrame);
    Fps.start(); // начинаем считать
  }
  override public function free():void {
    removeChild(ball);
    ball = null;
  }
  private function onFrame(e:Event):void {
    Fps.step(); // считаем
    trace("fps: "+Fps.fps+", delta: "+Fps.delta);
    ball.x += 100 * Fps.delta; // скорость по x - 100 пикселей в секунду
    ball.y += 70 * Fps.delta; // скорость по y - 70 пикселей в секунду
  }
}

Комментарии (20)

+2
Еще имеет смысл ограничить максимальное значение дельта. Если фпс сильно просаживается — дельта становится большой и возникает туннельный эффект: объекты телепортируются не срабатывают коллизии и игра становится полностью неиграбельной. Лучше уж, замедлить скорости движения объектов, но игрок сможет пройти проблемный участок. Один минус максимальное значение дельты надо искать экспериментальным путем.
0
На самом деле не имеет, т.к. туннельный эффект правильно разруливать на тех участках кода, где он возникает. Например isBullet в Box2D решает проблему. А если происходят сильные проседания, то играть всё равно становится невозможно, по крайней мере нормальный игрок просто забьёт на дико тормозящую игру.
0
Проседание фпс не всегда можно пердугодать, а написание продвинутого колижна или использование громоздких физ. движков не всегда оправдано. Например, падение замедление на 30% процентов еще терпимо, даже иногда помогает в играх на реакцию. А тоннельный эффект при эквивалентом увеличении скорости полностью может сделать уровень неиграбельным. Да и слоумо визуально смотрися лучше чем дерганья. ОК, со своим уставом в чужой монастырь не лезу))
0
Не, ты всё правильно лезешь, мы же обсуждаем. Истину, так сказать, ищем. (:
Я к тому, что ограничение fps если и делать, то не в этом классе. Тут должны быть чистые данные. Если на моём примере смотреть, то ограничение можно сделать в методе onFrame стейта Gameplay.
0
ограничение fps
Я то как раз предлагаю не фпс ограничить а дельте задать правую границу, это будет при небольших скачках фпс сглаживать кинематику, а при больших интервалах она будет урезаться, и не будет давать объектам скакать на пол экрана(тут работает эквивалентно снижению фпс). Насчет размещения: если delta свойство класса fps, то все методы и другие махинации должны быть членами этого класса, вот такое мое ИМО
0
Fps — класс, считающий fps и delta. И только. Не нужно его перегружать дополнительной обработкой. Под ограничением фпс я подразумевал что если fps не хватает — делать 2 и более прохода обновления игры, т.к. сверху fps и так ограничен настройками на уровне плеера. А просто обрезать дельту при падении fps — не круто. Лучше игру оптимизировать. (:
0
Да, если нужна стабильная дельта, то лучше использовать 1 / stage.frameRate — меньше вычислений при том же результате.
0
Я обычно исользую дельту вычисленную по формуле delta = 1.0 / stage.frameRate.
И каков бы ни был FPS, все будет менятся на заданный шаг по фреймрейту.
По мне так очень надежно и просто к тому же.
0
У тебя мелкие проседания fps не влияют на перемещение, что не очень хорошё. Хотя по большому счёту пофиг, я и такой способ иногда использую, чтобы лишних классов не тянуть…
0
Ну у меня вот за 2 года ни разу не возникло необходиомти FPS мерять. :)
Всегда использовал глобальное дельта от фреймрейта.
И никогда не испытывал дискомфорта или хотя бы намеков на него от того, что у меня что-то там не влияет на перемещение. :)
Как ты уже сказал выше, если оно тормозит — то играть в любом случае невозможо, а если все пучком, то все равно что у тебя:
класс или глобальная перменная — играть одинаково комфортно. :)
0
Я ж и говорю, что по большому счёту всё равно. Это скорее мои привычки нативного программирования сказываются. (:
0
Нативное это хорошо. Но разве в нативном вы отталкивались от рендера или все таки от таймера (игрового цикла), а рендер сколько вытянет столько вытянет, ну можно было ограничить максимум?
По моему вешать обработку (игровой update) на Event.ENTER_FRAME не очень хорошо.
0
Не совсем понимаю, в чём имменно вопрос. Нативно у нас fps ограничен либо искусственно, либо конфигурацией компа. Поэтому приходится так или иначе отталкиваться от дульты между кадрами. Максимум ограничивать имеет смысл лишь для разгрузки процессора. А рендерить чаще обновления без соответствующей интерполяции вообще смысла не имеет.

А во флеше разве есть другие варианты, кроме того или иного события? Делать обновление реже 30 раз в секунду бессмысленно, задолбаешься синхронизировать отрисовку и обновление — отсюда дрожание, рывки.
0
Я к тому что дельту мы можем считать не время между кадрами а время между тиками таймера.
Вместо вешать обновление на Event.ENTER_FRAME и нагружать флешку в момент отрисовки (флеш плееру там хватает чего считать и без нашего апдейта) мы вешаем его на таймер TimerEvent.TIMER.
0
Я как то повесил апдейт мира на тики таймера, в итоге когда флешка начинала терять в FPS-е несколько кадров, то игрок просто не успевал реагировать на происходящее на экране…
А тайсеры вешать по несколько секунд на Timer так вообще невозможно.
Если работа с чатсицами, то они даже отрисоваться не успевают, как уже уничтожаются.
Timer хорош, например, чтобы пинги слать на сервер, но для всего остального я лично, закрыл ему дорогу.
0
Я не знаю что такое — тайсеры.
Но если ваша игра тормозит то она тормозит и на ENTER_FRAME.
Если делать зависимость от дельты (разницы во времени между обновлениями) то тут всеравно к какому событию привязываться, просто при ENTER_FRAME идет отрисовка.
А проблемы с частицами у вас были скорей всего из за неправильного применения.
0
Извинте, таймеры. Не нашел кнопки редактировать пост. :(
Проблемы с частицами были из-за того что таймера, потому что как только повесил все на Enter_Frame — все сразу стало корректно работать.
Вот в том то идело. Если игра тормозит, то она тормозит и на Enter_Frame.
Сколько вешал все апдейты на Enter_Frame — все всегда рабтало «ок» (и как-то тормозов из-за скрипта и даже работы бокс2Д не наблюдалось).
Но как только пытался сделать по «человечески» и повесить апдейт на Timer, то сразу проявлялась какая-то рассинхронизация.
Конечно может я глупый и что-то не так делал, но лично мой опыт показал, что Enter_Frame стабильней и надежней.
0
Когда вызвается ENTER_FRAME плеер ничего не рисует. Это событие вызывается как раз между отрисовками. А с учётом того, что отрисовка идёт согласно установленному frameRate (или стремится к нему), то вешать обновление как раз нужно именно на это событие. На таймер имеет смысл вешать обновление, если считать нужно реже, чем frameRate, но в этом случае всё равно имеет смысл как-то интерполировать рендер между фреймами, а это лишний гемор, который нафиг никому не нужен.
0
Да согласен, не правильно выразился, после ENTER_FRAME и перед EXIT_FRAME идет отрисовка, а мы в этот момент ему еще пересчет данных подсовываем.
Если у нас стоит frameRate 60 это не означает что нам нужно провести все нужные расчеты 60 раз за секунду.
Да конечно есть момент проседания когда интрервал таймера меньше интервалов ENTER_FRAME, но визуально мы не заметим этого (в разумных приделах).
Неоднократно сталкивался с ситуациями когда перенос расчетов из ENTER_FRAME спасал ситуацию, и взял для себя за правило использовать таймер.
+1
Пересчёт мы подсовываем не в момент отрисовки, а до неё. Между отрисовками.
И зачем ставить frameRate 60? 30 вполне достаточно.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.