Комментарии 62
Спасибо за перевод. Очень познавательная статья.
А есть что-то про игровой цикл, но только с распараллеливанием? Например физику обновлять в одном потоке, ИИ в другом, отрисовка в третьем. Как тут быть с синхронизацией и взаимодействием разных компонентов движка между собой?
А есть что-то про игровой цикл, но только с распараллеливанием? Например физику обновлять в одном потоке, ИИ в другом, отрисовка в третьем. Как тут быть с синхронизацией и взаимодействием разных компонентов движка между собой?
Я об этом думал… Перевод как первый шаг к имплементации через синглтоны и мультитред. По поводу синхоронизации — все зависит от используемого языка. Там есть ньюансы.
Кажется все-таки синглетоны вообще не причем к возможности распараллеливания. Или я что-то пропустил?
Синглтон + Мультитред (локи, мьютексы и пр.) = доступный отовсюду в аппликейшене тредсейфти сервис. Как-то так.
Я это себе так представляю:
1) Есть поток рендеринга — Low Priority
2) Есть поток обновления состояния — High Priority
3) Есть синглтон, хранящий GameState
Воркфлоу:
А) Поток обновления стейта работает на выбранной частоте и периодически апдейтит закэшированный стейт в синглтоне
Б) Поток рендеринга считывает текущий стейт из синглтона.
В) В синглтоне реализовывается синхронизация. Можно разбить веси стейт на блоки, сделав несколько критических секций. Может еще какие оптимизации.
Концепт такой. Надо попробовать что-то типа танчиков запилить.
1) Есть поток рендеринга — Low Priority
2) Есть поток обновления состояния — High Priority
3) Есть синглтон, хранящий GameState
Воркфлоу:
А) Поток обновления стейта работает на выбранной частоте и периодически апдейтит закэшированный стейт в синглтоне
Б) Поток рендеринга считывает текущий стейт из синглтона.
В) В синглтоне реализовывается синхронизация. Можно разбить веси стейт на блоки, сделав несколько критических секций. Может еще какие оптимизации.
Концепт такой. Надо попробовать что-то типа танчиков запилить.
как мне кажется GameState не должен быть в единственном экземпляре. иначе получится как при отключенной вертикальной синхронизации верхняя часть экрана одна картинка, нижняя — вторая.
только по объектам разделение, одни по предыдущему отсчету, другие по следущему. результат будет такой, что танчики будут дергаться. и двигаться неравномерно.
его можно разбить на статические данные и динамические. динамические должны иметь по объекту на каждый временной отсчет. которые и будет передаваться от одного треда к другому.
и не должно быть у потока игросостояния более высокого приоритета. получится, что 1 раз в 40мс он просыпается, что то делает, на это время фпс критически падает. потом он завершается, и фпс взлетает до небес. игра будет пульсирующей.
имхо правильнее сделать, чтоб поток игросостояния так же не был привязан к тому, что его будут вызывать ровно 25 раз в секунду. лучше если он будет просчитывать следующее состояние из рассчета на то, сколько прошло времени. естественно можно выставить некий таргет, и если выполняется быстрее, чем нужно, то можно стравливать время, а если медленнее, то упрощать вычисления, насколько игромеханика это позволяет.
только по объектам разделение, одни по предыдущему отсчету, другие по следущему. результат будет такой, что танчики будут дергаться. и двигаться неравномерно.
его можно разбить на статические данные и динамические. динамические должны иметь по объекту на каждый временной отсчет. которые и будет передаваться от одного треда к другому.
и не должно быть у потока игросостояния более высокого приоритета. получится, что 1 раз в 40мс он просыпается, что то делает, на это время фпс критически падает. потом он завершается, и фпс взлетает до небес. игра будет пульсирующей.
имхо правильнее сделать, чтоб поток игросостояния так же не был привязан к тому, что его будут вызывать ровно 25 раз в секунду. лучше если он будет просчитывать следующее состояние из рассчета на то, сколько прошло времени. естественно можно выставить некий таргет, и если выполняется быстрее, чем нужно, то можно стравливать время, а если медленнее, то упрощать вычисления, насколько игромеханика это позволяет.
> лучше если он будет просчитывать следующее состояние из рассчета на то, сколько прошло времени
Может я чего то не понял правильно, но в статье про это вроде написано.
> как мне кажется GameState не должен быть в единственном экземпляре
«НЕ должен быть» — это опечатка? В синглтоне всегда хранится последний стейт. Там я нехорошо написал про несколько критических секций. Все таки секция должна быть одна.
Про приоритеты… Мы делаем обновление с фиксированной частотой (10, 25 или сколько то еще раз в сек.), и эта частота должна иметь наивысший приоритет. Фактическая частота рендеринга неважна.
Может я чего то не понял правильно, но в статье про это вроде написано.
> как мне кажется GameState не должен быть в единственном экземпляре
«НЕ должен быть» — это опечатка? В синглтоне всегда хранится последний стейт. Там я нехорошо написал про несколько критических секций. Все таки секция должна быть одна.
Про приоритеты… Мы делаем обновление с фиксированной частотой (10, 25 или сколько то еще раз в сек.), и эта частота должна иметь наивысший приоритет. Фактическая частота рендеринга неважна.
> Фактическая частота рендеринга неважна.
Точнее «сколько получится».
Точнее «сколько получится».
1)у автора про отвязку вывода кадров от статических временных промежутков. я имел ввиду, что и просчет игросостояний тоже можно и нужно от них отвязывать.
2) не опечатка. следует читать как неприменимость синглтона. как я это вижу. вместо этого некий таймлайн игровых состояний. в виде очереди. стейт(первый) на -5мс, дальше стейт на +35мс(второй), и дальше еще стейт на +75мс. и отображатель рисует интерполированное состояние между первым и вторым. когда второй стейт достигает нулевой отметки времени, первый освобождается, а позади третьего от другого потока должен приехать четвертый стейт еще на 40мс позже.
3) частота фпс условно не важна. но важна его стабильность. если брать из рассчета 10 стейтов в секунду, то если обработчик состояния каждые 100мс проснется и с высоким приоритетом будет 50мс что то делать, то за это время не будет ни одного кадра. а в следующие 50 мс он уснет, и отображатель нарисует скажем 5-6 кадров. фпс будет достаточно высокий, 50-60, но он будет дерганый, вплоть до неиграбельности. равномерный фпс 25 кадров лучше выглядит, чем настолько неравномерный 60.
2) не опечатка. следует читать как неприменимость синглтона. как я это вижу. вместо этого некий таймлайн игровых состояний. в виде очереди. стейт(первый) на -5мс, дальше стейт на +35мс(второй), и дальше еще стейт на +75мс. и отображатель рисует интерполированное состояние между первым и вторым. когда второй стейт достигает нулевой отметки времени, первый освобождается, а позади третьего от другого потока должен приехать четвертый стейт еще на 40мс позже.
3) частота фпс условно не важна. но важна его стабильность. если брать из рассчета 10 стейтов в секунду, то если обработчик состояния каждые 100мс проснется и с высоким приоритетом будет 50мс что то делать, то за это время не будет ни одного кадра. а в следующие 50 мс он уснет, и отображатель нарисует скажем 5-6 кадров. фпс будет достаточно высокий, 50-60, но он будет дерганый, вплоть до неиграбельности. равномерный фпс 25 кадров лучше выглядит, чем настолько неравномерный 60.
Если рендеринг будет тяжел, то он просто не будет поспевать за обновлением стейта всего навсего. Отрисовывать рендерер всегда будет последнее состояние.
При малой производительности просто будут пропускаться некоторые промежуточные состояния: к примеру сначала рендеринг начнет отрисовывать состояние между первым тиком и вторым. Будет делать это два-три тика и в следующий раз отрисует состояние между 3 и 4 тиками или между 4 и 5 тиками.
Динамика персонажа не изменится, хоть картинка и будет дерганной. А вот если тяжелый рендеринг будет аффектать обновление стейта, то уже перс начнет вести себя непредсказуемо. Про это в статье и написано.
У вас была ситуация в шутерах на слабом железе, когда инерционность поворота камеры увеличивалась из-за тормозов. Теперь представьте, что вместо инерции будет лишь дрожание картинки, а реакция на мышь останется приличной.
Про очередь стейтов… Нельзя заранее просчитать несколько стейтов наперед, т.к. они зависят от ввода от юзера.
При малой производительности просто будут пропускаться некоторые промежуточные состояния: к примеру сначала рендеринг начнет отрисовывать состояние между первым тиком и вторым. Будет делать это два-три тика и в следующий раз отрисует состояние между 3 и 4 тиками или между 4 и 5 тиками.
Динамика персонажа не изменится, хоть картинка и будет дерганной. А вот если тяжелый рендеринг будет аффектать обновление стейта, то уже перс начнет вести себя непредсказуемо. Про это в статье и написано.
У вас была ситуация в шутерах на слабом железе, когда инерционность поворота камеры увеличивалась из-за тормозов. Теперь представьте, что вместо инерции будет лишь дрожание картинки, а реакция на мышь останется приличной.
Про очередь стейтов… Нельзя заранее просчитать несколько стейтов наперед, т.к. они зависят от ввода от юзера.
теряется возможность интерполяции и собственно смысл развязки. чтобы иметь 60фпс нужно и стейт просчитывать 60 раз в секунду и кадр отрисовывать 60 раз в секунду. если стейт просчитывается 10 раз в секунду, то и на экране будет 10. формально счетчик покажет 60, но кажые 6 кадров будут одинаковы и пользователь будет видеть 10фпс.
и наоборот, если рендер «тяжелый» и выводит всего 10 фпс, то зачем просчитывать 60 состояний, отображаться будет лишь каждое 6-ое, остальные уйдут в корзину.
очередь стейтов решает вот эти проблемы. если рендер не отобразил стейт, то можно увеличивать время между отсчетами. и наоборот, если рендер быстр, то он и будет определять плавность анимации, а не обработчик состояния.
А несколько стейтов наперед просчитать можно. вопрос в инпут-лаге.
его кстати надо еще уметь просчитывать. это тоже в тему задач гейм-дева.
если взять опять же supreme commander. то 10 стейтов в секунду он просчитывает. 100мс между отсчетами. если ввод от игрока придет на 101-ой мс, то отклик на его действие попадет в стейт, который начнет просчитываться на 200-ой мс.
допустим стейт просчитывается за 40мс, рендер идет каждые 20мс(50фпс), то получится минимальный инпут лаг в 160мс. если ставить задачу просчитать на один стейт вперед, то инпут лаг становится 260мс. что для реал-тайм стратегий вполне приемлемо.
кстати лобби в этой игре красным отмечает пинг выше 500мс, все что ниже считается приемлемым.
в случае же шутеров сцена существенно проще, объектов на 2 порядка меньше в такой модели просто нет необходимости. стейт можно просчитывать очень быстро, и не париться интерполяцией, а задавать шаг анимации сразу в стейте.
и наоборот, если рендер «тяжелый» и выводит всего 10 фпс, то зачем просчитывать 60 состояний, отображаться будет лишь каждое 6-ое, остальные уйдут в корзину.
очередь стейтов решает вот эти проблемы. если рендер не отобразил стейт, то можно увеличивать время между отсчетами. и наоборот, если рендер быстр, то он и будет определять плавность анимации, а не обработчик состояния.
А несколько стейтов наперед просчитать можно. вопрос в инпут-лаге.
его кстати надо еще уметь просчитывать. это тоже в тему задач гейм-дева.
если взять опять же supreme commander. то 10 стейтов в секунду он просчитывает. 100мс между отсчетами. если ввод от игрока придет на 101-ой мс, то отклик на его действие попадет в стейт, который начнет просчитываться на 200-ой мс.
допустим стейт просчитывается за 40мс, рендер идет каждые 20мс(50фпс), то получится минимальный инпут лаг в 160мс. если ставить задачу просчитать на один стейт вперед, то инпут лаг становится 260мс. что для реал-тайм стратегий вполне приемлемо.
кстати лобби в этой игре красным отмечает пинг выше 500мс, все что ниже считается приемлемым.
в случае же шутеров сцена существенно проще, объектов на 2 порядка меньше в такой модели просто нет необходимости. стейт можно просчитывать очень быстро, и не париться интерполяцией, а задавать шаг анимации сразу в стейте.
> теряется возможность интерполяции и собственно смысл развязки
По какой причине? Если интерполяцию сделать невозможно даже приближенно (Хмм а почему, собственно? тоже задумывался, но пока в голову не пришел пример), тогда да… лесом идет эта имплементация с интерполяцией и мы откатываемся обратно к двупоточной системе, где нам уже нужно давать достаточный приоритет обоим.
> если рендер «тяжелый» и выводит всего 10 фпс, то зачем просчитывать 60 состояний
В случае физики, как раз нужно будет. В статье упомянут «взрыв» симуляции.
По какой причине? Если интерполяцию сделать невозможно даже приближенно (Хмм а почему, собственно? тоже задумывался, но пока в голову не пришел пример), тогда да… лесом идет эта имплементация с интерполяцией и мы откатываемся обратно к двупоточной системе, где нам уже нужно давать достаточный приоритет обоим.
> если рендер «тяжелый» и выводит всего 10 фпс, то зачем просчитывать 60 состояний
В случае физики, как раз нужно будет. В статье упомянут «взрыв» симуляции.
НЛО прилетело и опубликовало эту надпись здесь
Сейчас активно CUDA используют в гейм деве (посмотрите шоукейсы на сайте NVidia). Но это для серьезных разработок. Но уже стираются границы между ЦП и ГП.
А в казуалках 2D акселерация железная, вроде, не так распространена. И тот же XNA ЦП нагружает значительно. Впрочем надо профилировать, чтоб точно сказать что в XNA игрушках (например та же Террария) загружает ЦП.
А в казуалках 2D акселерация железная, вроде, не так распространена. И тот же XNA ЦП нагружает значительно. Впрочем надо профилировать, чтоб точно сказать что в XNA игрушках (например та же Террария) загружает ЦП.
Это реальный способ ускорить игру, другие способы, когда разные подсистемы в отдельные потоки складывают просто не работают из-за тесной связи между ними. Анимация связана с физикой, которая связана с триггерами гейплея, которые вообще в скриптах обрабатываются и тесно связаны с ИИ.
> Фактически мы можем принять как данность, что даже на слабом железе функция update_game() вызывается 25 раз в секунду. Поэтому наша игра будет обрабатывать пользовательский ввод и обновление состояние без особых проблем даже в случае, когда отрисовка выполняется на частоту 15 кадров в секунду.
Если говорить простыми словами, то я вижу алгоритм таким:
Ну не могу я представить ситуацию, когда при последовательном вызове функций одна будет выполняться чаще, чем другая, при том, что ее запуск «прореживают». В статье ведь не говорится о многопоточности etc. Что я недопонял?
Если говорить простыми словами, то я вижу алгоритм таким:
бесконечный цикл {
если (update_game() выполняется не чаще 25 раз в секунду) {
update_game();
}
display_game();
}
Ну не могу я представить ситуацию, когда при последовательном вызове функций одна будет выполняться чаще, чем другая, при том, что ее запуск «прореживают». В статье ведь не говорится о многопоточности etc. Что я недопонял?
Запуск update_game не прореживают, а «стабилизируют»: она может запускаться как реже (если, к примеру, display_game уложится в 0.01с), так и чаще (0.1с на отображение). Ваш алгоритм на это не способен, увы.
Ъ. Пока писал самый первый комментарий «где же отличие?», до меня дошло, но я случайно нажал на Alt-Home, и простыня комментария с объяснением стерлась (>_<)
Отличие оказалось в том, как алгоритм будет реагировать на задержку. В моем случае — никак (^_^) В случае алгоритма из статьи оный при выявлении задержки будет обновлять мир столько раз, на сколько этот мир «опаздывает».
Моя ошибка была в том, что я сделал неверные выводы. Да, update_game() не будет выполняться каждую 1/25 секунды. Обновление может вызываться себе приблизительно с таким интервалом, а потом display_game() напорется на сложную сцену, отрисовка которой займет 1/20 секунды, и следующий update_game() будет вызван уже через 1/5 секунды (5/25 — три интервала, короче), но благодаря такому построению игрового цикла, как в статье, он будет вызван не один, а пять раз, чтобы компенсировать задержку (таким образом, в это время между вызовами update_game() будет проходить 1-2 мс). НО. В среднем (по больнице, есессено, и тем больше по больнице, чем круче скачки FPS) update_game() будет вызываться именно 25 раз в секунду (неважно, с какими промежутками между вызовами), а значит, что можно считать, что между вызовами update_game() в среднем проходит 1/25 секунды.
И, наконец, описание своими словами алгоритма из статьи:
Есессено, что значение времи между вызовами с учетом единиц измерения (секунд) будет дробным, а значит, что после деления на 1/25 (умножения на 25) нужно взять целое число, но это уже нюансы (равно как и переменные, точки отсчета etc).
Отличие оказалось в том, как алгоритм будет реагировать на задержку. В моем случае — никак (^_^) В случае алгоритма из статьи оный при выявлении задержки будет обновлять мир столько раз, на сколько этот мир «опаздывает».
Моя ошибка была в том, что я сделал неверные выводы. Да, update_game() не будет выполняться каждую 1/25 секунды. Обновление может вызываться себе приблизительно с таким интервалом, а потом display_game() напорется на сложную сцену, отрисовка которой займет 1/20 секунды, и следующий update_game() будет вызван уже через 1/5 секунды (5/25 — три интервала, короче), но благодаря такому построению игрового цикла, как в статье, он будет вызван не один, а пять раз, чтобы компенсировать задержку (таким образом, в это время между вызовами update_game() будет проходить 1-2 мс). НО. В среднем (по больнице, есессено, и тем больше по больнице, чем круче скачки FPS) update_game() будет вызываться именно 25 раз в секунду (неважно, с какими промежутками между вызовами), а значит, что можно считать, что между вызовами update_game() в среднем проходит 1/25 секунды.
И, наконец, описание своими словами алгоритма из статьи:
бесконечный цикл {
если (время между вызовами update_game() больше 1/25 секунды) {
[время_между_вызовами_update_game_в_секундах / (1/25)] раз сделать { update_game(); }
}
display_game();
}
Есессено, что значение времи между вызовами с учетом единиц измерения (секунд) будет дробным, а значит, что после деления на 1/25 (умножения на 25) нужно взять целое число, но это уже нюансы (равно как и переменные, точки отсчета etc).
Вы предлагаете игнорировать изредка возникающую проблему, автор предлагает её решение. Кроме того, в среднем 25 будет как раз у автора; у вас получится меньше (совсем немного), но это может легко привести, к примеру, к рассинхронизации в мультиплеере.
Ваш новый алгоритм уже стал сложнее предложенного, и вы забыли про время выполнения самого update_game и MAX_FRAMESKIP. И то и то может иметь значение. А с учётом нюансов, код усложнится ещё сильнее.
Ваш новый алгоритм уже стал сложнее предложенного, и вы забыли про время выполнения самого update_game и MAX_FRAMESKIP. И то и то может иметь значение. А с учётом нюансов, код усложнится ещё сильнее.
Как же хорошо было с контроллерами прерываний… Нажали кнопку или двинули мышкой и сразу прерывание и его обработка, а пока игрок ничего не вводит, только обсчитываем и отрисовываем. :)
Довольно часто этот баг встречается у начинающих разработчиков — начинаешь двигать мышью и игра резко начинает жрать много процессора. Поэтому, если по правильному, положение мыши сохраняют и обрабатывают только в update(). Тогда независимо от кол-ва движений мышью игра потребляет одно и то же кол-во ресурсов.
Если обработка движения мыши начинает жрать много процессора, выкиньте этот процессор.
Вы плохо себе представляете логику работы игры.
Если при каждом событии мыши вы начинаете выполнять относительно непростые действия, то легко получается, что у вас эта обработка фактически вызывается более чем 100 раз в секунду. Для игры это бессмысленно, а нагрузку дает (особенно если это Flash, а если мобильное приложение, то вы бессмысленно «выжигаете» батарею).
Если при каждом событии мыши вы начинаете выполнять относительно непростые действия, то легко получается, что у вас эта обработка фактически вызывается более чем 100 раз в секунду. Для игры это бессмысленно, а нагрузку дает (особенно если это Flash, а если мобильное приложение, то вы бессмысленно «выжигаете» батарею).
Не понимаю, в чем разница? Биос все равно вызывает прерывания по изменению состояния датчиков. Если боитесь слишком часто обрабатывать ввод, то обрабатывайте не каждое изменение, а время проверяйте или в стек записывайте и обрабатывайте уже в самой игре.
Теперь перечитайте мое первое сообщение :)
Вы ответили как противопоставление обработке прерывания при его возникновении. Объявили обработку прерывания багом. И я объяснил, что обработка прерывания это не баг, а вполне рабочий вариант. Возможно, фраза «обработка прерывания» вызвала у Вас ассоциации с работой логической модели игры в зависимости от введенных данных. Это не так. Я про логику игры говорил только, что обсчитываем и отрисовываем, когда игрок ничего не вводит. А Вы?
Если вы при событии MOUSE_MOVE сохраняете координаты мыши, а обрабатываете после, то у вас по-любом вариант с update/draw, а не обработка по событию.
«двинули мышкой и сразу прерывание и его обработка» — это событийная модель, и она для игр не подходит.
«обрабатывайте не каждое изменение, а время проверяйте или в стек записывайте и обрабатывайте уже в самой игре» — это update/draw, и ее все используют.
«двинули мышкой и сразу прерывание и его обработка» — это событийная модель, и она для игр не подходит.
«обрабатывайте не каждое изменение, а время проверяйте или в стек записывайте и обрабатывайте уже в самой игре» — это update/draw, и ее все используют.
Вы издеваетесь (это больше к автору и приверженцам этой тактики), какие циклы, что за 'прошлый век'!
Событийный механизм — когда обновление экрана делается по времени по алгоритму XX кадров в YY интервал, когда события в игре выполняются строго когда нужно, когда не объекты сдвигаются на пиксел на game_update(), а их координаты вычисляются в момент их запроса.
И не будет тогда ни проблем с тачпадом (когда при меленном выводе невозможно нарисовать гладкую кривую, хотя проблемы могут вытекать еще глубже — из железа и драйверов), ни глюков с разными скоростями, набегающих из-за ошибок вычислений (того же float — ошибки с ним заметнее) и многого другого можно просто не заметить/не встретить, если правильно изначально НЕ ОРГАНИЗОВЫВТЬ цикл.
p.s. в конечном счете цикл будет, но не игровой а событийный, и его может реализовывать уже сама библиотека, используемая для организации событий.
p.p.s. FPS и 30 более чем достаточно, проблема чаще возникает не из-за низкого FPS а из-за его неравномерностей, и самое простое — сначала считаем изображение, затем ждем до момента вывода, затем выводим — т.е. должен быть небольшой лаг в 1/30 секунды (бывают алгоритмы, выдающие лаг в 2-3 кадра).
Еще неплохим решением со слабым железом может быть вывод недоконца построенного изображения (особенно если есть возможность ухудшить качество без уменьшения информативности) это воспринимается гораздо лучше чем пропуск кадров.
Событийный механизм — когда обновление экрана делается по времени по алгоритму XX кадров в YY интервал, когда события в игре выполняются строго когда нужно, когда не объекты сдвигаются на пиксел на game_update(), а их координаты вычисляются в момент их запроса.
И не будет тогда ни проблем с тачпадом (когда при меленном выводе невозможно нарисовать гладкую кривую, хотя проблемы могут вытекать еще глубже — из железа и драйверов), ни глюков с разными скоростями, набегающих из-за ошибок вычислений (того же float — ошибки с ним заметнее) и многого другого можно просто не заметить/не встретить, если правильно изначально НЕ ОРГАНИЗОВЫВТЬ цикл.
p.s. в конечном счете цикл будет, но не игровой а событийный, и его может реализовывать уже сама библиотека, используемая для организации событий.
p.p.s. FPS и 30 более чем достаточно, проблема чаще возникает не из-за низкого FPS а из-за его неравномерностей, и самое простое — сначала считаем изображение, затем ждем до момента вывода, затем выводим — т.е. должен быть небольшой лаг в 1/30 секунды (бывают алгоритмы, выдающие лаг в 2-3 кадра).
Еще неплохим решением со слабым железом может быть вывод недоконца построенного изображения (особенно если есть возможность ухудшить качество без уменьшения информативности) это воспринимается гораздо лучше чем пропуск кадров.
Напишите похожую статью про событийный механизм в играх, заинтересовался очень даже… Раньше нигде не встречал подобного подхода.
И мне интересно… Я видимо неправильно понял. Перечитаю еще несколько разков. Но пока сдается мне, что это подобие первого решения в данной статье, но с событиями. Наверняка я ошибаюсь.
Одно время уже обсуждали на Хабре события и событийный подход к программированию (кажется, для экономии энергии (читайте — аккумулятора)). Лично я сделал для себя такой вывод: пока не появится удобного (в плане строк кода, подключаемых библиотек, кроссплатформенности и тому подобных шняг) API для аппаратной поддержки событий (прерывания-то есть, но многим работать с ними не очень удобно, да и абстракции вроде API для универсального доступа к устройствам накладывают свои ограничения), события и их обработка в итоге сведутся к тому же бесконечному циклу.
habrahabr.ru/blogs/development/134559/ собственно
НЛО прилетело и опубликовало эту надпись здесь
наш рабочий проект создавался еще когда не было операционных систем, точнее они были, но их использование было неоправдано. а «многозадачность» нужна. я то тут не так давно, но вот с опухшими глазами смотрю на это.
так вот, там реализован sched вот как раз по этому принципу, только более комплексно. очереди сообщений 2х-милисекундные, 20ти, 100. сам считает таймслайсы, и т.п.
сейчас это все, причем существенно эффективнее делает операционная система. хоть линух, хоть винда дает возможности многозадачности другого порядка.
собственно тут именно эта задача и стоит. два отдельных процесса — обработки состояния игры и ее отображения должны быть полностью независимы, чего собственно и добивается автор пытаясь абстрагировать механику игры от фпс.
как мне кажется проще и правильнее сделать два треда. в одном updateGame, в другом displayGame. и соответственно первый второму через пайп скармливает данные для отображения.
чем колхозить вот такую вот «двухзадачность», которая еще и при этом не сможет двухядерник даже напрячь.
так вот, там реализован sched вот как раз по этому принципу, только более комплексно. очереди сообщений 2х-милисекундные, 20ти, 100. сам считает таймслайсы, и т.п.
сейчас это все, причем существенно эффективнее делает операционная система. хоть линух, хоть винда дает возможности многозадачности другого порядка.
собственно тут именно эта задача и стоит. два отдельных процесса — обработки состояния игры и ее отображения должны быть полностью независимы, чего собственно и добивается автор пытаясь абстрагировать механику игры от фпс.
как мне кажется проще и правильнее сделать два треда. в одном updateGame, в другом displayGame. и соответственно первый второму через пайп скармливает данные для отображения.
чем колхозить вот такую вот «двухзадачность», которая еще и при этом не сможет двухядерник даже напрячь.
кстати про аппроксимацию и интерполяцию. читал про игру Supreme Commander. там обсчет игрового состояния идет 10 раз в секунду всего. при этом фпс 60 рисует абсолютно плавное, и при этом анимированное движение. анимация вся явно в треде отображения. тред игросостояния
я так полагаю, что тред отображения при этом всегда должен запаздывать. ибо ракета должна попасть в цель, что может произойти по времени между этими двумя игровыми отсчетами. и тред игросостояния собственно не просто следующее состояние" должен выдавать, а всю достаточную для аппроксимации информацию.
и кстати игра утилизирует 2 треда.
я так полагаю, что тред отображения при этом всегда должен запаздывать. ибо ракета должна попасть в цель, что может произойти по времени между этими двумя игровыми отсчетами. и тред игросостояния собственно не просто следующее состояние" должен выдавать, а всю достаточную для аппроксимации информацию.
и кстати игра утилизирует 2 треда.
хмм… редактирование съело кучу текста… ну ладно…
Я думал о границах применения интерполяции. Если прогнозировать движение объекта, на которое оказывает влияние пользовательский ввод или другие в общем случае случайные факторы, то прогноз и интерполяция с использованием текущих параметров движения объекта будет давать пренебрежимую погрешность лишь на коротких промежутках времени. Видимо 1/10 секунды — достаточно короткий таймсэмпл.
Про физику и дифф. ур-ия тут умолчим.
Про физику и дифф. ур-ия тут умолчим.
про Unity3d не совсем верно описано.
Да, там нет единого цикла, который обновляет игру. Зато каждый объект может обновить свое(ну или если надо, то и другого объекта) состояние.
В юнитях в классах, наследующиесях от MonoBehaviour, есть такие методы как Update, FixedUpdate, LateUpdate.
Update вызывается при обновлении каждого фрейма. LateUpdate вызывается после выполнения Update(к примеру, на апдейте мы пересчитали данных, а на LateUpdate мы отрисовали с использованием уже обновленных данных).
А FixedUpdate — это такой же апдейт, но вызывается раз в определенное время, которое задается в настройках проекта (то есть не зависит от быстродействия девайса).
Да, там нет единого цикла, который обновляет игру. Зато каждый объект может обновить свое(ну или если надо, то и другого объекта) состояние.
В юнитях в классах, наследующиесях от MonoBehaviour, есть такие методы как Update, FixedUpdate, LateUpdate.
Update вызывается при обновлении каждого фрейма. LateUpdate вызывается после выполнения Update(к примеру, на апдейте мы пересчитали данных, а на LateUpdate мы отрисовали с использованием уже обновленных данных).
А FixedUpdate — это такой же апдейт, но вызывается раз в определенное время, которое задается в настройках проекта (то есть не зависит от быстродействия девайса).
А кем все эти эвенты вызываются? В фоне петля должна иметься какая-то. Либо все компоненты — отдельные треды в некотором пуле. Так или иначе петля есть?
Достаточно много работаю в геймдеве и согласен, новички очень часто не понимают этой темы. Теперь будет ссылка, куда их можно послать :)
Реквестиирую флешеров в тред))
Поделитесь своими размышлениями по сабжу.
Поделитесь своими размышлениями по сабжу.
А собственно что вы хотите услышать? Все приведенные принципы работают для любого языка программирования. Или вы думаете что пульс игры на Actionscript выглядит по другому? :)
Ну так то оно так, только даже таймер во флеше от фпс зависит и его обновление не возможно чаще чем раз в кадр. Т.е. 1/25-30 секунды или 1/60 секунды.
Насколько я знаю там эту систему привязывают к Enter Frame-у и работает она аналогично — проверяет время, которое было между кадрами, вызывает необходимое количество update_game() после этого вызывает уже display_game()
Но переменного FPS на флеше не получится, потому что там в любом случае ФПС ограничен настройкой флеш плеера.
Но переменного FPS на флеше не получится, потому что там в любом случае ФПС ограничен настройкой флеш плеера.
В Box2d вроде как единственный верный путь — использовать постоянный шаг физики с переменным шагом отрисовки. К сожалению, я не программист, поэтому не могу сказать точно. Там даже в комментариях к коду ссылка на статью есть. ;)
Не копался в Box2D, но читал/смотрел туториал про то как создается легковесный игровой физический движок. Как раз «проповедовался» подход на основе переменных промежутков времени. Применительно к линейному перемещению — просто вычислялось приращение позиции в зависимости от скорости и пройденного времени. От интегрирования дифф. уравнений настоятельно рекомендуют отказаться.
Поэтому подход с постоянным шагом не является и не заявляется как единственно верный. Но на мой взгляд — это способ получить более реалистичную физику, и такой подход в чем-то сильно упрощает реализацию физики. Конечно, появляются сложности выдерживания этого постоянного шага. Потому я и писал, что тред с обновлением состояния (или только тред физики) должен иметь наивысший приоритет.
Поэтому подход с постоянным шагом не является и не заявляется как единственно верный. Но на мой взгляд — это способ получить более реалистичную физику, и такой подход в чем-то сильно упрощает реализацию физики. Конечно, появляются сложности выдерживания этого постоянного шага. Потому я и писал, что тред с обновлением состояния (или только тред физики) должен иметь наивысший приоритет.
Ну возможно для разных задач имеет смысл использовать разный подход. Я копался на форумах и основной подход был в воспроизводимости результатов. При использовании переменного шага воспроизводимость не может быть 100%, ибо существуют ошибки округления и прочий квантовый шум. А идея как раз была в 100% воспроизводимости (те же реплеи, но уже с применением реальной физики).
Вот, что я использую в своём прототипе:
-(void) update: (ccTime) dt // Using FIXED timestep
{
static double timeAccumulator = 0;
timeAccumulator += dt;
if (timeAccumulator > (MAX_CYCLES_PER_FRAME * UPDATE_INTERVAL)) {
timeAccumulator = UPDATE_INTERVAL;
}
while (timeAccumulator>=UPDATE_INTERVAL) {
timeAccumulator -= UPDATE_INTERVAL;
world->Step(UPDATE_INTERVAL, velocityIterations, positionIterations);
for (b2Body *b = world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
}
}
//
self.hero.jumpTimeout--;
[self.hero update];
//
[self processContacts];
[self addBody];
[self removeBody];
}
}
Не работает разметка что-то…
Вот, что я использую в своём прототипе:
-(void) update: (ccTime) dt // Using FIXED timestep
{
static double timeAccumulator = 0;
timeAccumulator += dt;
if (timeAccumulator > (MAX_CYCLES_PER_FRAME * UPDATE_INTERVAL)) {
timeAccumulator = UPDATE_INTERVAL;
}
while (timeAccumulator>=UPDATE_INTERVAL) {
timeAccumulator -= UPDATE_INTERVAL;
world->Step(UPDATE_INTERVAL, velocityIterations, positionIterations);
for (b2Body *b = world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
}
}
//
self.hero.jumpTimeout--;
[self.hero update];
//
[self processContacts];
[self addBody];
[self removeBody];
}
}
Не работает разметка что-то…
Как я понял в этом коде вычисляется сколько заданных квантов времени укладывается в прошедшем промежутке времени. Однако в аккумуляторе опять будет накапливаться значительная ошибка, т.к. при малом «dt» ошибка округления (особенно при хранении dt во float) будет достаточно велика.
Прим. перев.: в движке NeoAxis для физического объекта можно выставлять флаг Continuous Collision Detection; подозреваю, что по нему как раз и выполняется обработка, подобная описанной выше реализации игрового цикла.
CCD используется для детектирования коллизий быстрых объектов. Насколько я помню, есть 2 основных подхода:
1. Увеличить TICKS_PER_SECOND;
2. Быстрый объект растянуть по всему пути его следования.
Как я понял в NeoAxis делается интерполяция все-таки — просчитываются два положения между стандартными тиками и вычисляется позиция в промежутке.
>1. Увеличить TICKS_PER_SECOND;
В шутерах будет напряжно.
>2. Быстрый объект растянуть по всему пути его следования.
Сложно реализовать при криволинейных траекториях (особенно при действии силовых полей).
>1. Увеличить TICKS_PER_SECOND;
В шутерах будет напряжно.
>2. Быстрый объект растянуть по всему пути его следования.
Сложно реализовать при криволинейных траекториях (особенно при действии силовых полей).
Как я понял в NeoAxis делается интерполяция все-таки — просчитываются два положения между стандартными тиками и вычисляется позиция в промежутке.
>1. Увеличить TICKS_PER_SECOND;
В шутерах будет напряжно.
Поэтому частота обработки увеличивается только для объекта, для которого CCD включен (для пули). Получается как раз то, о чем вы написали — вычисляются промежуточные позиции. Иначе пуля за один тик может преодолеть большое расстояние и пролететь сквозь тонкую стенку, а не столкнуться с ней.
>2. Быстрый объект растянуть по всему пути его следования.
Сложно реализовать при криволинейных траекториях (особенно при действии силовых полей).
Да. Но при прямолинейном движении иногда можно получить выигрыш.
> Поэтому частота обработки увеличивается только для объекта, для которого CCD включен (для пули). Получается как раз то, о чем вы написали — вычисляются промежуточные позиции.
Интерполяция и увеличение частоты обработки — суть разные подходы. Интерполяция может выполняться на стандартных тиках. Просто делается проверка могла ли произойти коллизия быстрого объекта с медленным между последними двумя тиками или произойдет ли она между текущим тиком и следующим. В шутерах обрабатывать позиции пуль с частотами кратными главной частоте тиков тяжеловато, как я понимаю.
Интерполяция и увеличение частоты обработки — суть разные подходы. Интерполяция может выполняться на стандартных тиках. Просто делается проверка могла ли произойти коллизия быстрого объекта с медленным между последними двумя тиками или произойдет ли она между текущим тиком и следующим. В шутерах обрабатывать позиции пуль с частотами кратными главной частоте тиков тяжеловато, как я понимаю.
Насколько я понял статью gafferongames.com/post/fix_your_timestep там предлагается совсем не экстраполяция к предполагаемому будущему положению объектов, а наоборот остаток используется как коэффициент для интерполяции между текущим и предыдущим состоянием объектов. Т.е. объект на экране не опережает свое время, а наоборот отстает от рассчитанного положения (сильно отстает или не очень — зависит от величины остатка в аккумуляторе). Это сглаживает скачки + нет ошибки экстраполяции. Это отставание не так влияет на игровой процесс, так как в худшем случае это будет отставание в один кадр
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Игровые циклы или ЭлектроКардиоГама