Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Регистрация Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > Dukobpa3

Рейтинг: 5.00. Голосов: 2.

Про фпс, лаги и иже с ними. (с Хабра)

Запись от Dukobpa3 размещена 27.01.2012 в 23:43
Обновил(-а) Dukobpa3 31.01.2012 в 18:24

Оставлю это здесь.
Игровые циклы или ЭлектроКардиоГама
Перепост отсюда.
Кому понравилось и кто на хабре есть - плюсоните автору.
Еще в комментариях хотелось бы услышать от всех кто чего из этого применяет касательно флеша и каким образом.А может еще какие-то методы есть.

_______________________________________________________________________________________________________

Игровой цикл — это пульс каждой игры. Ни одна игра не будет работать без этого. Однако, к несчастью каждого нового разработчика игр, в сети нет хороших статей, в которых уделено достаточное внимание этой теме. Но не печальтесь, потому как только что вы получили возможность прочитать единственную в своем роде статью, уделяющую вопросу игровых циклов заслуженное внимание. По долгу службы мне часто приходится иметь дело с большим количеством кода мелких мобильных игр. И я каждый раз удивляюсь сколь много существует реализаций игрового цикла. Вы тоже можете удивиться как можно для такой, казалось бы простой, вещи можно придумать множество имплементаций. А ведь можно! И в статье я постараюсь рассказать о достоинствах и недостатках наиболее популярных вариантов игровых циклов. Также я постараюсь описать наилучший на мой взгляд вариант реализации игрового цикла.
(Thanks to Kao Cardoso Félix this article is also available in Brazilian Portuguese) (Thanks for me, in Russian also, прим. перев.)

Игровой цикл

Каждая игра содержит последовательность вызовов чтения пользовательского ввода, обновления состояния игры, обработки ИИ, проигрывания музыки и звуковых эффектов, отрисовки графики. Эта последовательность вызовов выполняется внутри игрового цикла. Т.е., как и было сказано в тизере, игровой цикл — это пульс каждой игры. В статье я не буду углубляться в детали реализации упомянутых выше тасков, а сконцентрируюсь исключительно на проблеме игрового цикла. По той же причине я упрощу список тасков до двух функций: обновление состояния и отрисовка. Ниже представлен пример кода для наиболее простой реализации игрового цикла.
Код:
 bool game_is_running = true;
    while( game_is_running ) {
        update_game();
        display_game();
    }
Проблема этой реализации в том, что она не обрабатывает время. Игра просто выполняется. На слабом железе игра работает медленно, на сильном — быстро. Давным давно, когда производительность компьютера была известной и примерно одинаковой на разных машинах, такая реализация не рождала проблем. Сегодня же, когда существует множество платформ с разной производительностью, появилась необходимость в обработке времени. Сделать это можно разными путями. О них я расскажу позже. А пока позвольте мне разъяснить пару моментов, которые дальше будут использоваться.

FPS
FPS — это аббревиатура от «Frames Per Second» (Кадров В Секунду, прим. перев.). В контексте представленной выше реализации игрового цикла это количество вызовов display_game() за одну секунду.

Скорость игры
Скорость игры — это количество обновлений состояния игры за одну секунду. Иными словами, количество вызовов update_game() в секунду времени.

FPS, зависящий от постоянной скорости игры

Реализация

Самое простое решение проблемы тайминга — просто выполнять вызовы с фиксированной частотой 25 раз/сек. Код, реализующий этот подход ниже.
Код:
const int FRAMES_PER_SECOND = 25;
    const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND;

    DWORD next_game_tick = GetTickCount();
    // GetTickCount() returns the current number of milliseconds
    // that have elapsed since the system was started

    int sleep_time = 0;

    bool game_is_running = true;

    while( game_is_running ) {
        update_game();
        display_game();

        next_game_tick += SKIP_TICKS;
        sleep_time = next_game_tick - GetTickCount();
        if( sleep_time >= 0 ) {
            Sleep( sleep_time );
        }
        else {
            // Shit, we are running behind!
        }
    }
Это реализация с одним большим плюсом: ПРОСТОТА! Коль скоро вы знаете, что update_game() вызывается 25 раз в секунду, написание остального кода становится проще пареной репы. К примеру, реализация функционала реплея становится тривиальной задачей. Если в игре не используются случайные величины, то вы просто можете логгировать пользовательский ввод и воспроизводить его позже. На своей тестовой машине вы можете подобрать некое компромиссное значение для FRAMES_PER_SECOND, но что произойдет на более быстром или более медленном железе? Давайте выясним это.

Слабое железо

Если железо способно выдерживать заданное FPS, то проблемы нет. Проблемы появятся, когда машина не сможет держать FPS на заданном уровне. Игра будет работать медленнее. В худшем случае игра будет лагать некоторые промежутки времени, а в другие работать нормально. Время будет течь с разной скоростью, что в итоге может сделать вашу игру неиграбельной.

Производительное железо

На мощном железе проблем не будет, но компьютер будет простаивать, тратя впустую «драгоценное» (видимо это ирония? — прим. перев.) процессорное время. Постыдились бы запускать игру с 25..30 FPS, когда она могла бы выдавать из за 300! Ваша игра потеряет в привлекательности по сравнению с тем, что она могла бы показать при использовании процессора на всю катушку. С другой стороны на мобильных платформах оно может быть и к лучшему — позволит сэкономить энергию.

Вывод

Завязывание FPS на фиксированную скорость игры — решение простое, позволяющее сохранить простоту кода. Но есть проблемы: задав слишком большое значение для FPS мы породим проблемы на слабом железе; задав слишком низкое значение мы неэффективно будем использовать мощное железо.

Скорость игры, зависящая от переменного FPS

Реализация

Другое решение проблемы — дать игре работать как можно быстрее и сделать скорость игры зависящей от текущего FPS. Игра будет обновляться с использованием промежутка времени, потраченного на отрисовку предыдущего кадра.
Код:
DWORD prev_frame_tick;
    DWORD curr_frame_tick = GetTickCount();

    bool game_is_running = true;
    while( game_is_running ) {
        prev_frame_tick = curr_frame_tick;
        curr_frame_tick = GetTickCount();

        update_game( curr_frame_tick - prev_frame_tick );
        display_game();
    }
Код усложняется, т.к. мы теперь должны обрабатывать дельту времени в update_game(). Но усложнился код несильно. Я видел много сообразительных разработчиков, которые реализовывали такой подход. Наверняка кто-то из них хотел бы иметь возможность прочитать этот пост, до того, как реализовали такой цикл самостоятельно. Ниже я покажу почему такой подход может иметь серьезные проблемы как на слабом железе, так и на мощном (да… и на мощном тоже).

Слабое железо

Слабое железо может иногда породить задержки в местах, где игра становится «тяжеловата». Это определенно будет иметь место в 3D играх, когда слишком много полигонов отрисовывается. В результате провал в FPS приведет к замедлению обработки пользовательского ввода. Обновление игры будет реагировать на провалы FPS, в результате состояние игры будет изменяться с заметными лагами. В результате время реакции игрока, ровно как и ИИ, замедлится, что может сделать даже простой маневр невозможным. К примеру, препятствие, которое можно преодолеть при нормальном FPS, будет невозможно преодолеть при низком FPS. Еще более серьезные проблемы на слабом железе будут при использовании физики. Симуляция физики может "взорваться".

Мощное железо

Вы можете удивиться тому, что представленная выше реализация игрового цикла может работать неправильно на быстром железе. К сожалению может. И прежде чем показать почему, позвольте немного разъяснить некоторые моменты математики на компьютере. В виду конечной разрядности представления числа в форме с плавающей точкой, некоторые значения не могут быть представлены. Так, значение 0.1 не может быть представлено в двоичном виде и будет округлено при хранении в переменной типа double. Продемонстрирую это с использованием консоли python:
Код:
>>> 0.1

0.10000000000000001
Само по себе это нестрашно, но в последовательных вычислениях приводит к проблемам. Пусть у вас есть машина, скорость которой равна 0.001 в попугаях (вольный перевод, прим. перев.). Через 10 секунд машина переместится на расстояние 10.0 попугаев. Если разобьем это вычисление по кадрам, то получим следующую функцию с FPS в качестве параметра:
Код:
>>> def get_distance( fps ):

... skip_ticks = 1000 / fps

... total_ticks = 0

... distance = 0.0

... speed_per_tick = 0.001

... while total_ticks < 10000:

... distance += speed_per_tick * skip_ticks

... total_ticks += skip_ticks

... return distance
А нука попробуем посчитать пройденный путь для 40 FPS.
Код:
>>> get_distance( 40 )

10.000000000000075
Постойте ка! Это не 10.0 попугаев! Что произошло? Все просто… Т.к. мы разбили вычисление пути на 400 кадров, то при суммировании накопилась значительная ошибка. Представляете что будет при 100 FPS?
Код:
>>> get_distance( 100 )

9.9999999999998312
Вот это да! Ошибка стала еще больше!!! Это потому, что мы делаем еще больше сложений при 100 FPS. Стало быть и ошибка накапливается больше. Таким образом, игра будет работать по-разному при 40 FPS и 100 FPS.
Код:
>>> get_distance( 40 ) - get_distance( 100 )

2.4336088699783431e-13
Вы можете подумать, что такая разница незначительна и ею можно пренебречь. Однако, если вы будете использовать это значение еще в каких-либо вычислениях, то проблемы возникнут более серьезные (как пример — интегрирование дифф. ур-ий, прим. перев.). Таким образом, ошибка может накопиться настолько большая, что зафакапит (чуть цензурнее чем в оригинале, прим. перев.) ваш продукт на больших FPS. Вы спросите насколько это вероятно? Достаточно вероятно, чтобы обратить на себя внимание. Я имел честь лицезреть игру с такой реализацией игрового цикла. И, действительно, в ней были проблемы при больших FPS. После того, как разработчик понял, что «собака зарыта» в самом ядре игрового кода, понадобилось отрефакторить тонну кода, чтобы починить багу.

Вывод

На первый взгляд этот тип игрового цикла кажется очень хорошим, но только на первый. Как слабое, так и мощное железо смогут породить проблемы. Кроме того, реализация функции обновления состояния усложнилось по сравнению с первой реализацией. Стало быть в топку ее?

Постоянная скорость игры и максимальное FPS

Реализация

Наша первая реализация, «FPS, зависящее от постоянной скорости игры», имеет проблемы на слабом железе. Она порождает лаги как FPS так и обновления состояния игры. Возможное решение этой проблемы — выполнять обновление состояния с фиксированной частотой, но снижать частоту отрисовки. Ниже код реализации такого подхода:
Код:
const int TICKS_PER_SECOND = 50;
    const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
    const int MAX_FRAMESKIP = 10;

    DWORD next_game_tick = GetTickCount();
    int loops;

    bool game_is_running = true;
    while( game_is_running ) {

        loops = 0;
        while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
            update_game();

            next_game_tick += SKIP_TICKS;
            loops++;
        }

        display_game();
    }

Игра будет обновляться с фиксированной частотой 50 раз в секунду, а отрисовка будет выполняться с максимально возможной частотой. Заметьте, если отрисовка будет выполняться чаще чем обновление состояние, то некоторые соседние кадрый будут одинаковыми, так что в действительности максимальное значение FPS будет ограничено частотой обновления состояния игры. На слабом железе FPS будет снижаться до тех пор, пока цикл обновления состояния не будет достигать значения MAX_FRAMESKIP. На практике это означает, что игра действительно начнет тормозить, только когда FPS отрисовки проседает ниже значения 5 (= FRAMES_PER_SECOND / MAX_FRAMESKIP).

Слабое железо

На слабом железе FPS просядет, но сама игра будет работать с большой вероятностью на нормальной скорости. Если же железо не сможет выдерживать даже минимальное FPS, то начнет тормозить и обновление состояния, а отрисовка потеряет даже намек на плавную анимацию.

Мощное железо

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

Вывод

Использование фиксированной скорости игры и максимально возможного FPS — решение, которое несложно реализовать и которое сохраняет код простым. Но все равно имеются некоторые проблемы: задание слишком большой частоты обновления состояния породит проблемы на слабом железе (пусть и не настолько серьезные, как в случае первой реализации), а задание малой частоты обновления состояния будет неэффективно использовать вычислительные мощности (ресурсы можно было бы использовать для увеличения плавности анимаций, но вместо этого они тратятся на частую отрисовку).

Постоянная скорость игры, независящая от переменного FPS

Реализация

Возможно ли улучшить предыдущую реализацию, чтобы она работала быстрее на слабом железе и была бы визуально привлекательнее на мощном? Ну, к счастью для нас, да, это возможно! Состояние игры не нужно обновлять 60 раз в секунду. Пользовательский ввод, ИИ, а также обновление состояния игры, достаточно обновлять 25 раз в секунду (я с этим не согласен, не всегда, прим. перев.). Так что давайте вызывать update_game() 25 раз в секунду, не чаще, не реже. А вот отрисовка пусть выполняется так часто, как железо потянет. Но медленная отрисовка не должна сказываться на частоте обновления состояния. Как добиться этого показано в следующем коде.
Код:
const int TICKS_PER_SECOND = 25;
    const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
    const int MAX_FRAMESKIP = 5;

    DWORD next_game_tick = GetTickCount();
    int loops;
    float interpolation;

    bool game_is_running = true;
    while( game_is_running ) {

        loops = 0;
        while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
            update_game();

            next_game_tick += SKIP_TICKS;
            loops++;
        }

        interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick )
                        / float( SKIP_TICKS );
        display_game( interpolation );
    }
В результате реализация update_game() останется простой. Однако, к несчатью, функция display_game() становится более сложной. Вам понадобится реализовать интерполяцию и предсказание. Но не волнуйтесь, это не так сложно, как кажется. Позже я расскажу как работают интерполяция и предсказание, но сначала позвольте показать вам зачем они нужны.

Зачем нужна интерполяция

Состояние игры обновляется 25 раз в секунду. Поэтому, если не используется интерполяция, то и кадры будут отображаться с той же максимальной частотой. Тут нужно заметить, что 25 кадров в секунду это не так медленно, как кому-то может показаться. К примеру, в фильмах кадры сменяются с частотой 24 кадра в секунду. Так что 25 кадров в секунду кажется достаточным, но не для быстро движущихся объектов. Для таких объектов следует увеличить частоту обновления состояния, чтобы получить более плавную анимацию. Альтернативой увеличенной частоте обновления как раз и служит связка интерполяции и предсказания.
* Прим. перев.: в движке NeoAxis для физического объекта можно выставлять флаг Continuous Collision Detection; подозреваю, что по нему как раз и выполняется обработка, подобная описанной выше реализации игрового цикла.

Интерполяция и предсказание

Как было написано выше, состояние обновляется на своей, независимой, частоте. Поэтому возможна ситуация, когда отрисовка начинается между двумя последовательными тиками. Пусть вы обновили состояние в 10 раз. Затем вызывается отрисовка и выполняется она где-то между 10 и 11 тиками. Пусть это будет дискретное время 10.3. В результате «interpolation» будет иметь значение 0.3. В качестве примера, представьте машину, движущуююся следующим образом:
position = position + speed;
Если на 10 шаге цикла обновления состояния позиция будет 500, скорость будет 100, тогда на 11 шаге позиция будет 600. Так какова же будет позиция машины во время отрисовки? Можно просто взять позицию на последнем шаге, т.е. 500. Но куда лучше предсказать позицию на следующем шаге и произвести интерполяцию для времени 10.3. Получим код вида:
view_position = position + (speed * interpolation)
Таким образом машина будет отрисована в позиции 530. Переменная «interpolation» в общем случае содержит значение от 0 до 1 относительной позиции во времени между текущим и следующим кадрами (переделано для лучшего понимания, прим. перев.). Нет нужды делать предсказание слижком сложным, чтобы обеспечить плавность анимации. Конечно, возможна ситуация когда один объект частично пересечется с другим непосредственно перед детектированием коллизии. Но, как мы увидели ранее, состояние игры обновляется 25 раз в секунду, поэтому артефакт рендеринга будет виден лишь долю секунды (а что если плотность объектов велика и коллизий много? — прим. перев.) и с малой вероятностью он будет замечен пользователем.

Слабое железо

В большинстве случаев update_game() будет выполняться намного быстрее, чем display_game(). Фактически мы можем принять как данность, что даже на слабом железе функция update_game() вызывается 25 раз в секунду. Поэтому наша игра будет обрабатывать пользовательский ввод и обновление состояние без особых проблем даже в случае, когда отрисовка выполняется на частоту 15 кадров в секунду.

Мощное железо

На мощном железе, игра будет по-прежнему идти с фиксированной скоростью 25 тиков в секунду, но отрисовка будет выполняться быстрее. Интерполяция + предсказание добавят привлекательность анимации, т.к. фактически рендеринг будет выполняться на более высоком FPS. Прелесть в том, что таким образом вы мошенничаете с FPS. Вы не обновляете состояние игры с большой частотой, а лишь картинку. Но при этом ваша игра все равно будет иметь высокое FPS.

Вывод

Развязка обновления и отрисовки друг от друга кажется лучшим решением. Однако при этом необходимо реализовать интерполяцию и предсказание в display_game(). Правда задача эта не слишком сложная (лишь при использовании примитивной механики объекто, прим. перев.).

Заключение

Игровой цикл не такая уж и простая вещь, как вам может показаться. Мы рассмотрели 4 возможные реализации. И среди них есть по крайней мере одна (где обновление состояния жестко завязано на FPS), которую вы определенно должны избегать. Постоянная частота кадров может быть приемлемой на мобильных устройствах. Однако, если вы хотите портировать игру на разные платформы, то придется развязывать частоту обновления и частоту отрисовки, реализовывать интерполяцию и предсказание. Если не хотите заморачиваться с предсказанием и интерполяцией, то можете использовать большую частоту обновления состояния, но найти оптимальное ее значение для слабого и мощного железа может оказаться сложной задачей.

А теперь марш кодить вашу <%place_your_game_title_here%>!

Попутные статьи (от переводчика)

habrahabr.ru/blogs/silverlight/125037/ — Игровой цикл в SL
habrahabr.ru/blogs/gdev/112444/ — там есть упоминание о том, что в Unity3D нет игрового цикла как такового (видимо он запрятан?)
habrahabr.ru/blogs/gdev/102930/ — про создание игрового движка
habrahabr.ru/blogs/android_development/136968/ — пример игры на кокосе
gafferongames.com/game-physics/fix-your-timestep/ — еще одна статья про дискретное время в играх
Всего комментариев 24

Комментарии

Старый 28.01.2012 01:16 dimarik вне форума
dimarik
 
Аватар для dimarik
HomoLuden просто не оч далеко смотрит.

Я об этом думал… Перевод как первый шаг к имплементации через синглтоны и мультитред. По поводу синхоронизации — все зависит от используемого языка. Там есть ньюансы.

Его перевод мне не нравится. Его рассуждения мне не нравятся. Его познания русского языка мне не нравятся. Я делаю вывод что мне не о чем с ним разговаривать.
Обновил(-а) dimarik 28.01.2012 в 01:21
Старый 28.01.2012 01:16 -De- вне форума
-De-
 
Аватар для -De-
Со вторым способом не всё так страшно. Для слабого железа тормоза в рендере - потому они не уйдут ни для какого из способов. Физика вот требует часто постоянный "фпс", да, можно смиксовать с .третьим способом. Где-то ещё решениям дифуров перменный фпс не мешает на практике) Накапливающиеся ошибки посчитаны неправильно, погрешность может быть не только больше, но и меньше, фпс-то переменный (и меньше точности измерения времени между фреймами обычно). Ну и сильно большой фпс можно тупо замедлить)

Последний способ правда хорош, особенно если используется физика, но там всему, что рендерится надо уметь сохранять состояние и линейно интерполироваться.
PS: Continuous Collision Detection не имеет отношения к теме, св-во bullet в бокс2д про это, оно нужно как ни делай игровой цикл.
В юнити нет игрового цикла, как и во флэш, что не мешает реализовать любой из способов)
Старый 28.01.2012 01:50 dimarik вне форума
dimarik
 
Аватар для dimarik
Цитата:
Таким образом, ошибка может накопиться настолько большая, что зафакапит (чуть цензурнее чем в оригинале, прим. перев.) ваш продукт на больших FPS. Вы спросите насколько это вероятно? Достаточно вероятно, чтобы обратить на себя внимание. Я имел честь лицезреть игру с такой реализацией игрового цикла. И, действительно, в ней были проблемы при больших FPS. После того, как разработчик понял, что «собака зарыта» в самом ядре игрового кода, понадобилось отрефакторить тонну кода, чтобы починить багу.
А те, кто не пристегивался вылетали через лобовое стекло. Так мне рассказал брат тестя моего друга. От автора тоже несет штампом, что чукча не писатель, а читатель.
Он упоротый чтоле совсем. Какая разница в ошибке на слабой и мощной машине через 100 виртуальных кадров?

P/S/ Dukobpa3, дай, пожалуйста, мне инвайт на хабр. И на дюти, если есть )
Обновил(-а) dimarik 28.01.2012 в 02:09
Старый 28.01.2012 02:06 HardCoder вне форума
HardCoder
 
Аватар для HardCoder
Эта статья только для тех кто пишет на JAVA, или что там за язык (я в этом не очень)? Если нет - то вопрос по флеш: как четвертый способ реализовать? Разве можно узнать время, через которое отрисуется следующий кадр? Чтобы, если отрисовка произойдет до следующего "тика", в предыдущем "тике" изменить позицию какого-то элемента не на 1, а на 0.3?
Старый 28.01.2012 02:13 dimarik вне форума
dimarik
 
Аватар для dimarik
Расслабься, за нас Адоби уже написали пятый вариант и ты работаешь в его парадигме.
Старый 28.01.2012 02:26 -De- вне форума
-De-
 
Аватар для -De-
На флэш делать тупо по их коду. Только вместо while( game_is_running ) - обработчик enter frame. В display_game - выставлять в нужные позиции display objectы, ну и рисовать что надо, если что-то рисуется.
GetTickCount() это getTimer() или (new Date()).valueOf().
Старый 28.01.2012 02:45 HardCoder вне форума
HardCoder
 
Аватар для HardCoder
Спасибо -De-, код я как-то расшифровал. Просто думал, что тема актуальна и для флешеров - вот и спросил как управлять частотой перерисовки.
Старый 28.01.2012 02:49 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
@dimarik - ну переводчик немного как непрофессионал пишет, размышления тоже, хотя сама статья мне интересной показалась, вроде очевидные весчи, но задумываешься не часто, тем более во флеше где вроде как изначально к кадрам привязали. Инвайта к сожалению не дам, там инвайты дают за 50+ кармы, у меня тока 15, так что сорри Напиши статью, тебя пригласят из песочницы Самый честный вариант. Думаю тебе есть о чем поведать, там есть флеш-блог.
Цитата:
Какая разница в ошибке на слабой и мощной машине через 100 виртуальных кадров?
Разница в том что на сильной и слабой тачках вычисления будут производиться в разное кол-во этапов. И на слабой мы одну цифру получим за 10 кадров, как 0.01 * 10, а на сильной эту же получим за счет 100 кадров как 0.001 * 100. Вишка в том что полученные цифры должны совпадать, ноони не совпадут из-за разницы в кол-вах итераций.

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

@-De- Ну наверное примерно так. Я бы поглядел на работающую флешовую реализацию такого. А может сам напишу. Сейчас как раз проект такой есть, мож быстренько перепилю на это.
Старый 28.01.2012 02:52 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
@HardCoder - тема актуальна ля флешеров)) Иначе с чего бы я ее на флешере репостил)
Старый 28.01.2012 03:31 -De- вне форума
-De-
 
Аватар для -De-
На тачке с 100 фпс апдейт за секунду выполнится 100 раз, на тачке с 40 фпс - может выполнится от 98 до 102 =) До 2%. Where's your 2.4336088699783431e-13 now? =)
Синхронизация требует дополнительного и весьма большого гемора, который сомнительно нужен.
Старый 28.01.2012 03:41 HardCoder вне форума
HardCoder
 
Аватар для HardCoder
Перечитал еще раз и понял что мне это не понять Если обьекту нужно пройти из точки А в точку Б с определенной постоянной скоростью. Запускаем ентерфрем или таймер (первый грешен потому что зависит от фактического FPS, а второй - потому что он таймер). Но это не важно. Запускаем, например, таймер. Код мгновенно выполнился: х и у поменялись. Пришла очередь визуализации. Обьект настолько большой что визуализация занимает 0.5 секунды. Я так понимаю: пока будет проходить визуализация (0.5 сек), таймер снова выполнится и координаты обьекта изменятся. Поэтому при следующей визуализации обьект "перескочит" на несколько отрезков (рывок).
Чтобы это не случилось - вычисляем время визуализации с помощью getTimer(). И при следующей итерации таймера изменяем координату не на один отрезок, а на меньший кусок. То есть делаем так, чтб при визуализации обьект не скакал а шел плавно. Анимация плавная, но скорость то обьекта изменяется, что противоречит тз. И как нам эти дельты помогли?
PS: Я могу ошибаться и понимаю что нужно читать книжки, а вопросы задавать на форуме а не здесь.
Старый 28.01.2012 04:10 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
Ну касательно флеша я встречался с такими реализациями:

Для начала примем что математика считается не по кадрам а по времени. Это главное. Далее:
подписываемся на ентерфрейм, так как это самое надежное событие флеша которе нативное на котором тупо всё построено на уровне движка. Даже таймер как ни странно. Таймер не может отсчитывать с периодичностью меньше чем время на один кадр.

Где-то глобально храним последнее время старта рендера.
Внутри обработчика ентерфейрма берем текущее время, сравниваем с сохраненным, получаем разницу милисекунд. Исходя из этой дельты просчитываем анимации и прочие смещения.

Собственно всё. Но это только рендер. А как сюда всунуть большие объемы обработки данных тиа физики и прочего, я пока не придумал. Надо еще раз внимательно примеры из статьи изучить.
Старый 28.01.2012 04:27 -De- вне форума
-De-
 
Аватар для -De-
HardCoder, мне вот не понять вышенаписанного %) Может, если это таки понятно написать, то будет лучше)
Вот на примере равномерного движения из А в Б:
1) Пытаемся сделать фпс для всех одинаковым, искусственно его ограничивая, смещаем всегда на одну и ту же величину. Иногда нельзя и sleep - не точен. Это флэш плеер =)
2) Ставим в нужное в данный момент положение. Смещение не всегда на одну и ту же величину и неточность чисел с плавающей запятой мешает.
3) Смещая постоянно на одну и ту же величину (иногда несколько раз за "фрейм") ставим максимально близко к нужному в данный момент положению. Получается не так точно установить, как в прошлом варианте, т.к. смещение на постоянную величину может перепрыгнуть нужное положение.
4) Делаем как в 3, плюс хоть "физическое" (в модели в смысле MVC) положение там же, где и в 3, видимое положение ставим, линейно интерполируя между физическими. Приходится сохранять физические положения и линейно интерполировать между ними (линейная интерполяция между матрицами может изредка давать забавные эффекты).
Обновил(-а) -De- 28.01.2012 в 04:29
Старый 28.01.2012 05:09 TanaTiX вне форума
TanaTiX
 
Аватар для TanaTiX
Цитата:
Далее:
подписываемся на ентерфрейм
Цитата:
Собственно всё. Но это только рендер. А как сюда всунуть большие объемы обработки данных тиа физики и прочего, я пока не придумал.
Возможно сочетание с EXIT_FRAME поможет.
Старый 28.01.2012 16:32 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Цитата:
бьект настолько большой что визуализация занимает 0.5 секунды. Я так понимаю: пока будет проходить визуализация (0.5 сек), таймер снова выполнится и координаты обьекта изменятся.
Флеш однопоточен. Пока не будет выполнен текущий стек вызовов - что-то новое в программу не вклинится.
На пальцах:
Код AS3:
function a1(){
a2();
trace("Finish");
}
function a2(){
a3();
}
function a3(){
}
Запустился метод a1, он вызвал метод a2 и тут произошел триггер таймера. Сперва выполнится полностью a2, в том числе и вызов метода a3, а обработчик TimerEvent.TIMER вызовется в самом конце, после trace("Finish");


А если использовать метод в дельтой, но управлять целочисленной математикой или рассматривать и отсекать любые части числа, меньшие 0.0001 как ошибку? Идеально.
Старый 28.01.2012 20:45 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
Цитата:
А если использовать метод в дельтой, но управлять целочисленной математикой или рассматривать и отсекать любые части числа, меньшие 0.0001 как ошибку? Идеально.
Как-то так, да. Принять допустим за основу 25 фпс, чтоб секунда нацело на 4 делилась. И дальше уж плясать. Но излишние округления тоже могут помешать.

Например 25 фпс, т.е. один кадр - 40 мс.
Тут произошла задержка и наш кадр занял 41.00003 мс

Если тупо отсекать то получится опять же накопление в минус. Правда что-то меня дробная часть миллисекунды не очень вдохновляет Кажись их может быть только целое число.
Обновил(-а) Dukobpa3 28.01.2012 в 21:41
Старый 28.01.2012 20:54 TanaTiX вне форума
TanaTiX
 
Аватар для TanaTiX
При 25 фпс один кадр 40 мс.
Старый 28.01.2012 21:40 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
да, внатуре, косяк)
Поправил, спасибо.
Старый 29.01.2012 03:13 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Я про другое: ошибка на первых итерациях незначительная, скажем, 0.00000001. А мы возьмем, и скажем, что всё, меньшее 0.0001 - ошибка, проще говоря, число 2.1230000001 мы будем рассматривать как 2.123, и 2.122999999999 мы тоже будем рассматривать как 2.123. Тогда ошибка не пройдёт, хоть мы и потеряем миллиардную долю точности.
Ещё лучше - руководствоваться целочисленной математикой, в ней не бывает ошибок. Например, принять что целочисленная переменная х хранит значение в 100 раз большее, чем реальное, т.е. если х = 1234, то на самом деле подразумевалось 12.34.
Лично я считаю вариант с дельтой самый правильный, но честное слово, никогда не задумывался о том, что там может быть какая-то ошибка. Возможно, зря.
Старый 29.01.2012 03:55 -De- вне форума
-De-
 
Аватар для -De-
Там проблемы связаны не с тем, что дельта не целая(можно целое число миллисекунд передавать), а с тем, что она РАЗНАЯ. Физику это может убить. В бокс2д поэтому просто фиксирован шаг апдейта мира - так что как хотите, но придется что-то думать, если его используете. Целое значение не спасает ещё и потому, что внутри оно рано или поздно где-то сконвертится в не целое. В случае с кадрами и фиксированным временем апдейта тоже присутствует такая конвертация, но она будет одинаковая для всех машин, с одинаковыми последствиями.
Кроме физики ничего особо не убивает (хотя иногда приходится думать, например, чтоб одинаковое число частиц в секунду рождалось). Это я как использующий переменное время апдейта намного чаще фиксированного говорю.
Обновил(-а) -De- 29.01.2012 в 04:05
Старый 29.01.2012 04:25 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Физику бокс2д, может. Инструмент такой. Как взять безалкогольное пиво и пытаться им напиться.
Цитата:
Целое значение не спасает ещё и потому, что внутри оно рано или поздно где-то сконвертится в не целое.
Целое значение - для просчета. Нецелое - для визуализации. Там погрешность в одну сотую не важна.
Старый 29.01.2012 04:42 -De- вне форума
-De-
 
Аватар для -De-
Дык в просчётах-то не целые используются. Множишь целое это на нецелую скорость, например, и привет. Только в целых рассчёты не вести же.
Но именно погрешность - всем пофиг, вообще-то.
Старый 29.01.2012 04:59 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
Та вот как бы не везде и не всегда пофиг.

Меня это интересует в перспективе риалтайм мультиплей.

Допустим сервер считает модель, передает данные с определенным периодом, клиенты рисуют. Тут вроде как всё ок, кто как успел так и отрисовал, глобально всем пофиг.

Но как только мы подключаем некие инструменты лагокомпенсации типа там дублирование на клиентах математики, или же сервер присылает посекундные расчеты, для экономии трафика, та и задача позволяет, а клиенты просчитывают ежекадрово ситуацию. Тут уже жди беды если этой самой математики много. Но страшна даже не математика которая во флеше вроде как шустрая (хотя не сказал бы про тот же бокс2д), а вот то что из-за долго рендера на слабой тачке может похериться пару циклом математики это да. Рндерили мы допустим вместо положеных 40мс, целых 45. А с покадровой структурой флеша вцелом получится что математика просчитается на 5мс позже. Ибо низзя сделать забив на таймлайн или свой цикл какой-то бесконечный или в два потока пустить с разными приоритетами.
Старый 29.01.2012 05:35 -De- вне форума
-De-
 
Аватар для -De-
Дык а при чем тут погрешность нецелых чисел? Если бы точно считало, то что бы изменилось?

Основные варианты своего бесконечного цикла - вот есть как раз. Они все реализуемы на флэш без проблем. И как раз показано, что делать с математикой. Синхронность - отдельная сказка. Если есть лагокомпенсация, то всё равно есть поправки от сервера. Главное до получения поправки не сильно далеко от неё уехать (и не вижу, почему вариант "Скорость игры, зависящая от переменного FPS" отъедет далеко).
 

 


Часовой пояс GMT +4, время: 01:31.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2021, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.