Что такое игровой цикл
Чем больше, тем хуже или почему важен игровой цикл
По мере эволюции геймдизайна в течение последних 20 лет, продолжительность игр постоянно увеличивалась. В The Witcher 3 легко можно наиграть сотни часов, а Factorio или Dwarf Fortress можно вообще перепроходить не один раз.
Разработчики давно преследуют идею игр с бесконечной реиграбельностью. Отсюда и появились игры-сервисы. Многие геймдизайнеры (особенно на AAA-проектах) смотрят на игровой опыт как на десятки часов геймплея. А это не всегда хорошо.
Вступление
Когда-то давно я написал статью о макро и микроэлементах в AAA-тайтлах. Микро — это то, что игрок делает в течение нескольких минут, а макро — это основные цели и задачи. Во многих играх с открытым миром микроэлементы остаются очень базовыми или включают в себя только переход от одного макрособытия к другому.
То же самое можно сказать и о RPG с многочасовыми квестами и кучей сюжетных линий — в них сражения и перемещение по миру второстепенны по отношению к основным целям. В подобных играх игрока нацеливают на долгосрочные цели, а не краткосрочный прогресс.
Этот подход сработает для фанатов, но вряд ли замотивирует новых игроков, у которых может не быть много времени на игру. Среди разработчиков популярна идея, что чем больше игра, тем она лучше. Сейчас же постоянно выходят десятки новых проектов, поэтому у игроков не всегда есть время «ждать удовольствие» от игры.
Именно поэтому игры, которые я люблю, не пытаются навязать 100-часовое прохождение. У них просто другой подход к основному игровому циклу (core gameplay loop).
Игровой цикл
Игровой цикл — это система или набор механик, которые будет использовать игрок. Будь то строительство, сражения, выращивание урожая, что угодно — это мгновенный геймплей, который создает микрослой проекта.
Это главная причина, по которой сегодня я предпочитаю инди-игры вместо ААА. У них малые бюджеты, поэтому разработчики пытаются сосредоточиться на игровом цикле в первую очередь. Его нельзя измерять часами — игрок должен понять суть игры в течение нескольких минут после запуска. Если цикл цепляет, то есть большой шанс, что вы останется здесь надолго.
Хороший игровой цикл создает «спринты» геймплея, которые приносят удовольствие при их повторении. Мало кто запускает игру в ожидании, что будет играть в нее сотни часов. Но если пользователь получает удовольствие, то общее проведенное время в игре начнет заметно увеличиваться.
Один из лучших примеров — соревновательные игры. Катки в Mortal Kombat, Counter-Strike и даже в League of Legends изначально не рассчитаны на 10 часов. Это короткие сессии, которые повторяются с каждым новым матчем.
Если вы понимаете ваш игровой цикл, и сможете разделить его так, чтобы каждый раз игроку было интересно — это сильно улучшит игру.
Roguelike
Рогалики — еще один яркий пример с отличными игровыми циклами, которые обеспечивают долгосрочную ценность. Лучшие из них предоставляют минуты развлечений, которые затем повторяются, но уже с новыми челленджами и условиями. Прелесть дизайна roguelike в том, что разработчики могут сосредоточиться на дополнительном контенте, который добавляет разнообразие, а не растягивает прохождение.
Нет ни одного рогалика, который бы открывал перед игроком свой основной цикл часами.
Внимательная проработка одной сессии в рогалике дает отличные ощущения «быстрого подхвата» игрока. Когда я играю в Slay the Spire, Spelunky, Binding of Isaac и другие, то у меня есть четкое представление, сколько времени это займет. Возвращаясь к главной мысли: у некоторых в Binding of Isaac наиграны сотни часов, которые сложились из 30-60 минутных отрезков.
Но совет «делайте рогалик» не поможет. Давайте поговорим, как подойти к дизайну игры, которая будет удерживать пользователей.
Срез геймплея
Не важно, сколько длится ваша игра (2 или 200 часов), задумайтесь о том, что игрок делает от момента к моменту. Так вы начнете разбивать дизайн на части и фиксировать те, которые станут основным игровым циклом игры.
Задания и цели в игре можно превратить в сегменты геймплея, которые фокусируют внимание игрока. Открытый мир специализируется как раз на таком: он предоставляя игроку огромное игровое пространство и множество точек интереса для исследования.
В Super Mario Odyssey прогресс определяется лунами: их можно найти как во всех мирах, так и после особых испытаний. Игрок определяет прогресс по количеству найденных лун или по разблокированным мирам. Чтобы пользователь видел игровой цикл в действии, он должен понимать происходящее.
Как измерить прогресс
Чтобы разбить игру на части, игрок должен видеть и понимать четыре основные вещи:
Лучшим примером станет начало любой MMO. Игрок начинает с простой цели, задания выполняются быстро, за них дают опыт и разблокируют новые квесты. Это также выделяет лучшие и худшие примеры MMO и RPG: неважно, насколько хорош эндгейм, если люди теряют интерес за несколько часов до него.
Игры, которые удерживают игрока, фокусируются на микроэлементах или игровом цикле, а не на макро. Игрок должен примерно понимать, сколько времени займет выполнение задачи, и (что более важно) сможет ли он закончить ее за один сеанс.
Наконец, должно быть что-то, что будет навсегда сохранено и застраховано от проигрыша. Именно поэтому во многих MMO-играх списки квестов и задания на достижение так увлекательны. Видеть, как список наполняется прогрессом и растет от 0% до 100% завершения — это сильный мотиватор. Игрок не должен рисковать потерей прогресса, чтобы потом проходить все с начала.
Самые важные минуты
Если все сделать правильно, то можно создать игровой цикл, который будет работать как в краткосрочной, так и в долгосрочной перспективе. В любом случае, разработчик должен сначала сосредоточиться на краткосрочной геймплее — без него игроки могут не захотеть тратить свое время на дальнейшую игру.
Если пользователи не уделяют игре даже 30 минут времени, то стоит пересмотреть онбординг и сам геймплей.
Геймдизайнерам об игроках или как программировать игроков?
Вступление 1.
Много думал и анализировал информацию на тему разработки игр. И чтобы не потерять, а так же поделиться с людьми, получить подсказки, советы и комментарии — решил оформить и выложить статью. На сегодняшний день “геймдизайн” все больше обретает очертания некой науки, требующей особого подхода и изучения. К сожалению, в общем доступе находится очень мало структурированной информации. Что касается иностранных авторов, имеющих большой опыт и ряд успешных проектов — зачастую их труды так и не попадают на прилавки отечественных магазинов. А изучить их в оригинале многим мешает языковой барьер.
В данной ситуации спасением служат статьи, где люди делятся впечатлениям и личным опытом по разработке игр. Эти статьи не только и не столько доносят до читателей новую информацию, сколько подают ее в новом свете, позволяя более четко осознать и отобразить в жизнь.
Вступление 2
Хочу отметить, что на написание материала оказало влияние выступление на Sociality Rocks 2011 Henric Suuronen — руководитель студии Wooga, разработчика Bubble Island и других хитов. Особого внимания заслуживает статья Андрея Плахова “Геймдизайнерам о программистах или как программировать программистов”. По аналогии с той статьей возникла мысль, что программировать можно и пользователей тоже. Программировать их модель поведения, чтобы их развлекать и извлекать нужные нам ценности — платежи, удержание в игре, привлечение новых игроков. И одним из инструментов удержания служит набор функций в приложении, который можно условно назвать игровым циклом. Об этом мы и поговорим далее
1. Что такое игровой цикл и зачем он нужен?
Основой всей игры является контент. С одной стороны он определяет возможности пользователей и определяет объем работ для создания проекта с другой стороны. Именно он затягивает людей и является причиной, почему они возвращаются в игру. При этом большая часть действий в процессе получения этого контента циклически повторяется. Я самый умный и один заметил, что все в этих социальных играх повторяется. Эти повторы и есть игровые циклы.
Игровой цикл — последовательность действий, выполняемая на протяжении всей игры раз за разом. Например: постройка здания, ожидание, получение валюты, постройка здания, ожидание и т.д.
Можно ли обойтись без игровых циклов? — Можно, но давайте представим, что в нашем проекте существует только линейный (не зацикленный) контент. Десятки и сотни часов игрок видит все новый и новый арт, анимации, мини-игры, слышит постоянно новую музыку. Сколько будет весить такая игра? Сколько времени она будет разрабатываться? Сколько устанавливаться? Нафига это надо? Заходим в такую игру и видим что-то похожее на сюжетную линию длинного квеста, с большим количеством уникальных действий. Cложно придумать длинную интересную игру. А мы привыкли: быстрые игры — легкие деньги. Чтобы решить эти проблемы вводятся элементы повторения одних и тех же действий с различными внешними проявлениями. Цепочка таких действий и образует игровой цикл.
2. Типовые игровые циклы (housing, PvP, PvE)
Housing:
Рассмотрим социальные игры. В них мы можем определить следующие действия игроков — копание грядок, строительство домов, крафтинг вещей — эти действия относятся к игровым циклам типа Housing. В этом типе игровых циклов люди могут тратить много времени в ожиданиях, которое стоит как-то скрасить.
Далее рано или поздно в игре появляются какие-нибудь NPC, управляемые компьютером (NPC), которые дают квесты, нападают на дома, посевы и т.д. — это цикл взаимодействия с окружающей средой, PvE. Этот тип более динамичен потому что ждать обычно ничего не нужно, необходимо действовать быстро, реагируя на поведения игрового персонажа. Но так как искусственный интеллект персонажей линейный и сложностей не вызывает, то он просто вносит в игру разнообразие.
Третий вид циклов — это взаимодействие с другими игроками. В военных играх — это нападения и битвы — цикл PvP, В мирных же играх — помощь друзьям, подарки, соревнования. Назовем его Player for player. Этот вид циклов наиболее важен. Правильное взаимодействие с другими игроками вызывает наибольший интерес у публики.
3. Игровые циклы в мирных и военных играх
Социальные игры построены на прохождении игровых циклов для получения большего количества очков для дальнейшего соревнования с живыми людьми. В мирных играх — игроки соревнуются получая очки за постройки, выполнения квестов, помощь другу. Такими показателями обычно являются уровень, стоимость города, количество достижений. Игроки помогают друг другу, повышая количество своей энергии, очков и т.д. В военных же играх — пользователи получают очки за нападения, повышая свои показатели и характеристики. Эти циклы похожи. Разница лишь в том, что в мирных от взаимодействия выигрывают обе стороны, в военных — только победившая.
4. Каким должен быть игровой цикл
5. Примеры игровых циклов на хорошо известных играх.
Пример 2.1 (Castleville):
1. Выбираем нужное здание, покупаем его, строим и ждем прибыль. по истечении фиксированного времени получаем прибыль, потом строим еще здание, опять ждем и опять получаем прибыль.
Рис. 1. — Игровой цикл: постройка здания — ожидание — прибыль.
2. В игре у нас есть грядки на которых можно выращивать растения. Выбираем грядку, сажаем на ней растение, ждем заданное время и собираем урожай. И так по кругу.
Рис. 2. — Игровой цикл: посадил растение — ожидание — прибыль.
Игровые циклы в Castleville предельно просты – один раз показываете его, дальше игрок все делает на автомате.
Пример 2.2 (Airport city):
1. Выбираем здание, покупаем, строим, собираем прибыль и еще строим зданий. Этот цикл типичен для многих игр.
2. Выбираем рейс, заправляем самолет, загружаем, отправляем, ждем заданное время, собираем прибыль и снова выбираем рейс. Хотя кажется, что действий больше — они все интуитивно понятны. И несмотря на это все равно на каждом шаге пользователя окружают подсказки. Ими можно не пользоваться. Выбор использовать/не использовать очень актуален в начале игры.
Рис. 3. — Игровой цикл: выбор рейса — снаряжение самолета — ожидание — прибыль
Рис. 4 — Игровой цикл: постройка здания — ожидание — прибыль
Можно сделать вывод, что сопровождающие подсказки – альтернатива простоте цикла, но злоупотреблять сложностью все равно не стоит.
6. Как начать игровой цикл?
Вот вы вошли в игру. С чего начать? Игроку нельзя позволять разбираться в игре самостоятельно. Это быстро надоедает и у него появляется желание закрыть приложение, а так как положительных эмоций у него не осталось – он не вернется. В игру нужно ввести обучение, которое научит игрока самостоятельно входить в игровой цикл и идти по нему, а также расскажет об основных элементов управления. Л учшим началом первого игрового цикла будет кнопочка «нажми меня». В дальнейшем игрок, наученный положительным опытом, увидев пустую грядку или голодную коровку, сам все сделает. Следовательно, чтобы начать цикл — необходимо всякими мигающими занаками, звуковыми сигналами и анимациями показать, куда надо нажать, чтобы что-то начать делать.
7. Как вести игроков по игровому циклу?
Был такой эксперимент над крысами, когда им в мозг вводили электроды. И давали кнопку, при нажатии на которую, в мозг поступали разряды и доставляли крысам приятные ощущения. Животные переставали есть, пить, спать. И жали на кнопку до тех пор, пока не умирали. Так вот: принцип ведения игрока по циклу примерно такой же. На протяжении всего игрового цикла игрок должен знать, что ему делать дальше и где его кнопка счастья. Получить бонус награду или что-то построить — повести любое действие. Процесс показывает обучение. А далее мигающими значками, звуками, движениями действующих лиц и небольшими роликами отмечать достижения и обозначать новую цель. И интенсивность и торжественность мигания калибровать в зависимости от важности достигнутой цели (эффект кнопки счастья). Либо таймером показать время до события.
8. Как закончить игровой цикл
По окончанию игрового цикла у пользователя должно остаться впечатление, что все в его игровом мире хорошо. Все что мог, сделал. Получил награду и готов делать новые ответственные задания. Позитивное настроение в конце игрового цикла необходимо для успешного его закрытия.
9. Разорванные игровые циклы
Мы рассмотрели простые игровые циклы, которые используются чаще всего. Но для того чтобы завлечь игрока необходимо сделать игру похожей на живое существо. Чтобы она скучала по нему и присылала извещения, чтобы она ждала его, и игроку было, зачем возвращаться.
Игровые циклы бывают короткие по времени — когда мы что-то строим ждем 5 минут и получем какую-нибудь “классную штуку”. Такие циклы происходят во время одного игрового цикла. Бывают же циклы длинные по времени, когда для того чтобы дождаться профита нужно ждать много времени. Обычно такие циклы занимают 2 и более игровых сессий. Такие циклы называются разорванными игровыми циклами. Например, мы посадили какую-нибудь мега-тыкву. Чтобы она созрела, и мы могли получить за нее много бонусов, надо подождать всего-то 100500 часов. Конечно, никто столько не будет сидеть в игре и ждать. Но игрок может сделать все другие дела, потратить всю энергию, посетить всех друзей и вернуться в следующий раз, когда все созреет.
Если короткие игровые циклы дают пользователю для организации игрового процесса, то разорванные циклы тоже важны — это основной инструмент удержания пользователя в игре.
10. Как это все использовать?
Привлечение игроков и монетизация – это темы других статей. Что же касается удержания пользователей, то этому способствуют разорванные игровые циклы. Они создают видимость того, что игра идет даже в отсутствии игрока и тем самым помогают возвращаться пользователям обратно после закрытия приложения.
11. Подведем итоги
Описание циклов игры: примеры и советы
Директор игрового департамента Rocket Jump о проектировании геймплея.
Директор игрового департамента студии Rocket Jump Константин Сахнов написал для DTF статью, в которой рассказал о построении и описании игровых циклов — одном из ключевых аспектов в работе геймдизайнера.
В этой статье я структурирую методику описания игровых циклов (core gameplay loop) и поделюсь своим опытом в этой области, а также расскажу, как наша команда использует её в работе над одним из проектов студии — Dakota Farm Adventures.
У каждого, кто связан с геймдизайном, есть своё представление об игровых циклах. Главное — понимать, какие задачи решаются их описанием. Начинать работу над игрой стоит не с подбора инструментов и даже не с проработки концепта, а с осознания результатов, которых команда хочет достигнуть. Руководствуясь этой логикой, можно выделить следующие цели описания core gameplay:
Одна из самых распространённых ошибок при создании игры — отсутствие единого видения у всей команды. Если вас двое, и вы делаете инди-игру, шансы прийти к консенсусу гораздо выше, чем в студии из пары десятков разработчиков. И если ключевые аспекты проекта нигде не зафиксированы, он рискует дойти до релиза совершенно отличным от изначальной задумки. Если, конечно, дойдёт.
На крупных проектах, которые разрабатываются в течение нескольких лет, команда неизбежно меняется в процессе работы. Кто-то уходит, кто-то приходит. Лучший способ донести до нового сотрудника суть игрового процесса — показать core loop. Ни тонны продуктовой документации, ни многочасовые собрания не смогут так быстро объяснить, о чём проект. Лучше геймплейной схемы в курс дела может ввести только сам продукт.
Для описания игровых циклов чаще всего используются UML-диаграммы. Такая практика сложилась давно и остаётся общепризнанной, однако никаких ограничений инструментария нет. Каждый геймдизайнер сам выбирает формат и логику построения диаграмм с учётом особенностей продукта и сложившейся в команде практики. Далее я поделюсь правилами, которыми пользуюсь сам.
Придумать базовый игровой процесс для нового проекта – амбициозная задача. Особенно если это не клон, а переосмысление популярного жанра. Но пока давайте предположим, что наша задача – описать уже продуманный core gameplay. С чего начать?
Для лучшего понимания я разделил процесс на несколько шагов. Чтобы алгоритм был нагляднее, рассмотрим его на примере популярных игр: Clash of Clans, Game of War, Candy Crush Saga.
Выписываем и распределяем по заготовке схемы все фичи и элементы, лежащие в основе игры. В первой итерации лучше взять самые важные и крупные элементы, чтобы показать общую картину. В дальнейшем можно будет добавлять детали, если это необходимо. В случае c Clash of Clans основные элементы геймплея — это сражения и ситибилдер.
Далее нужно понять, как именно связаны между собой эти блоки. Типов связей может быть много, но большую часть игр покрывают три:
Рассмотрим связи на примере Game of War. Поток ресурсов в ней — это, например, захват камня или дерева с точек на глобальной карте. Жёсткий «лок» — это требование по уровню одного здания, без которого невозможен апгрейд другого. А прогресс и мощь — это развитие армии за счёт её прокачки.
Деление на три группы — условное, и необходимо только для удобства и структурирования связей. Как разграничить связи и поделить их на группы? Можно ли отнести юнитов в Clash of Clans (которых можно считать и ресурсом) к прогрессу и мощи?
В принятой мною модели нельзя. Прогресс — это односторонняя связь. Он может только расти. Чаще всего показатель прогресса — это уровень игрока или персонажа. Ресурсы — это элементы, которые можно добывать и расходовать по желанию. Так, юниты, которые могут погибнуть в бою — это расходуемый ресурс. А развитие бессмертного героя — прогресс. В Candy Crush ресурсом выступают очки здоровья, расходуемые игроком при неудачном прохождении уровней.
И, наконец, жёсткие «локи». Почему это не ресурс и не прогресс? «Локи» отличаются от других типов связей необратимостью. Для доступа к новым уровням Candy Crush предлагает заплатить реальной валютой или активностью. Однажды сняв один «лок», игрок с ним больше не столкнётся. Но впереди могут быть другие преграды.
Выделив основные элементы игры и осознав типы имеющихся между ними связей, завершаем создание диаграммы — расставляем направляющие стрелки и подписываем потоки. В нашем случае ситибилдер даёт боёвке юнитов (нанимаемых за эликсир), забирая себе ресурсы обратно. Себя же боёвка обеспечивает рейтингом. Обратите внимание, что рейтинг не является ресурсом, так как его нельзя «потратить» в прямом смысле этого слова. Его можно «повысить» или «понизить» в зависимости от ваших умений и опыта, он может влиять на матчмейкинг и ощущения от игры, но за него нельзя покупать сущности.
Напоследок отмечу несколько простых правил, соблюдение которых поможет сделать вашу схему лучше.
Лаконичная схема удобнее слишком подробной. Чем дольше нужно вчитываться, чтобы вспомнить или понять геймплейную структуру, тем хуже. Если вы понимаете, что схема слишком сложна, задумайтесь, не перемудрили ли вы с геймплеем. Может, стоит убрать двенадцатую вариацию механики охоты за подводными кроликами? Проигнорируйте незначительные детали и даже некоторые правила, если это поможет сделать схему core gameplay понятней.
Любое описание важно не только правильно продумать, но и качественно оформить. Это позволит быстрее вникнуть в суть и не тратить время на домысливание того, что хотел сказать автор. Но при этом никакое оформление не поможет, если страдает логика. Пускай ваша диаграмма будет кривой и ввергающей в первобытный ужас всех джуниоров — главное, чтобы она грамотно описывала геймплей. К оформлению стоит приступать только после того, как вы разобрались с логикой и самими механиками.
Очевидно, что каждая игра имеет свои особенности, а значит и описание диаграммы циклов для каждого продукта может отличаться в нюансах. Где-то есть только поток ресурсов, где-то важно обозначить расход энергии. Каждый геймдизайнер может по-своему прочесть схему. Для исключения разночтений мы сопровождаем диаграммы легендой. Важно следить за тем, чтобы выбранная логика обозначений сохранялась внутри проекта на всех диаграммах. Если вы выбрали красный цвет стрелок для жёстких «локов» на первой диаграмме, не стоит отмечать их синим в дальнейшем.
Давайте посмотрим, как можно использовать приведённую модель на примере недавно запущенной нами фермы Dakota Farm Adventures.
Нашим игрокам открываются три ключевых геймплейных блока — ферма, дом и исследовательские локации. На ферме игрок выращивает овощи и деревья, кормит животных и перерабатывает урожай в более ценные ресурсы, а также зарабатывает деньги, торгуя продуктами и материалами на доске заказов. В доме живут родственники главной героини, открывающие доступ к новым типам производства по мере разблокировки комнат. На карте мира можно путешествовать по различным локациям, где выполняются квесты и добываются особые производственные ресурсы — например, древесина, лён, глина. К примеру, кузина Дженни лепит горшки из глины, а дядюшка Бен стругает доски из древесины.
Это — схема core gameplay проекта, которую мы используем в пайплайне разработки:
В заключение хочу отметить, что каждый геймдизайнер может использовать собственный подход как в разработке, так и в документировании игрового процесса. Главное – видеть цель, к которой вы хотите прийти, и обладать глубоким пониманием того, чем же на самом деле является core gameplay в контексте вашего продукта.
Я пользуюсь описанным методом, так как он довольно прост, удобен и достаточно универсален. Но, возможно, вы сможете предложить другие способы и модели. Как происходит построение и описание игровых циклов в вашей команде? Расскажите в комментариях.
Игровые циклы или ЭлектроКардиоГама
Игровой цикл
Каждая игра содержит последовательность вызовов чтения пользовательского ввода, обновления состояния игры, обработки ИИ, проигрывания музыки и звуковых эффектов, отрисовки графики. Эта последовательность вызовов выполняется внутри игрового цикла. Т.е., как и было сказано в тизере, игровой цикл — это пульс каждой игры. В статье я не буду углубляться в детали реализации упомянутых выше тасков, а сконцентрируюсь исключительно на проблеме игрового цикла. По той же причине я упрощу список тасков до двух функций: обновление состояния и отрисовка. Ниже представлен пример кода для наиболее простой реализации игрового цикла.
Проблема этой реализации в том, что она не обрабатывает время. Игра просто выполняется. На слабом железе игра работает медленно, на сильном — быстро. Давным давно, когда производительность компьютера была известной и примерно одинаковой на разных машинах, такая реализация не рождала проблем. Сегодня же, когда существует множество платформ с разной производительностью, появилась необходимость в обработке времени. Сделать это можно разными путями. О них я расскажу позже. А пока позвольте мне разъяснить пару моментов, которые дальше будут использоваться.
FPS
FPS — это аббревиатура от «Frames Per Second» (Кадров В Секунду, прим. перев.). В контексте представленной выше реализации игрового цикла это количество вызовов display_game() за одну секунду.
Скорость игры
Скорость игры — это количество обновлений состояния игры за одну секунду. Иными словами, количество вызовов update_game() в секунду времени.
FPS, зависящий от постоянной скорости игры
Реализация
Самое простое решение проблемы тайминга — просто выполнять вызовы с фиксированной частотой 25 раз/сек. Код, реализующий этот подход ниже.
Это реализация с одним большим плюсом: ПРОСТОТА! Коль скоро вы знаете, что update_game() вызывается 25 раз в секунду, написание остального кода становится проще пареной репы. К примеру, реализация функционала реплея становится тривиальной задачей. Если в игре не используются случайные величины, то вы просто можете логгировать пользовательский ввод и воспроизводить его позже. На своей тестовой машине вы можете подобрать некое компромиссное значение для FRAMES_PER_SECOND, но что произойдет на более быстром или более медленном железе? Давайте выясним это.
Слабое железо
Если железо способно выдерживать заданное FPS, то проблемы нет. Проблемы появятся, когда машина не сможет держать FPS на заданном уровне. Игра будет работать медленнее. В худшем случае игра будет лагать некоторые промежутки времени, а в другие работать нормально. Время будет течь с разной скоростью, что в итоге может сделать вашу игру неиграбельной.
Производительное железо
На мощном железе проблем не будет, но компьютер будет простаивать, тратя впустую «драгоценное» (видимо это ирония? — прим. перев.) процессорное время. Постыдились бы запускать игру с 25..30 FPS, когда она могла бы выдавать из за 300! Ваша игра потеряет в привлекательности по сравнению с тем, что она могла бы показать при использовании процессора на всю катушку. С другой стороны на мобильных платформах оно может быть и к лучшему — позволит сэкономить энергию.
Вывод
Завязывание FPS на фиксированную скорость игры — решение простое, позволяющее сохранить простоту кода. Но есть проблемы: задав слишком большое значение для FPS мы породим проблемы на слабом железе; задав слишком низкое значение мы неэффективно будем использовать мощное железо.
Скорость игры, зависящая от переменного FPS
Реализация
Другое решение проблемы — дать игре работать как можно быстрее и сделать скорость игры зависящей от текущего FPS. Игра будет обновляться с использованием промежутка времени, потраченного на отрисовку предыдущего кадра.
Код усложняется, т.к. мы теперь должны обрабатывать дельту времени в 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
А нука попробуем посчитать пройденный путь для 40 FPS.
>>> get_distance( 40 )
10.000000000000075
Постойте ка! Это не 10.0 попугаев! Что произошло? Все просто… Т.к. мы разбили вычисление пути на 400 кадров, то при суммировании накопилась значительная ошибка. Представляете что будет при 100 FPS?
>>> get_distance( 100 )
9.9999999999998312
Вывод
На первый взгляд этот тип игрового цикла кажется очень хорошим, но только на первый. Как слабое, так и мощное железо смогут породить проблемы. Кроме того, реализация функции обновления состояния усложнилось по сравнению с первой реализацией. Стало быть в топку ее?
Постоянная скорость игры и максимальное FPS
Реализация
Наша первая реализация, «FPS, зависящее от постоянной скорости игры», имеет проблемы на слабом железе. Она порождает лаги как FPS так и обновления состояния игры. Возможное решение этой проблемы — выполнять обновление состояния с фиксированной частотой, но снижать частоту отрисовки. Ниже код реализации такого подхода:
Игра будет обновляться с фиксированной частотой 50 раз в секунду, а отрисовка будет выполняться с максимально возможной частотой. Заметьте, если отрисовка будет выполняться чаще чем обновление состояние, то некоторые соседние кадрый будут одинаковыми, так что в действительности максимальное значение FPS будет ограничено частотой обновления состояния игры. На слабом железе FPS будет снижаться до тех пор, пока цикл обновления состояния не будет достигать значения MAX_FRAMESKIP. На практике это означает, что игра действительно начнет тормозить, только когда FPS отрисовки проседает ниже значения 5 (= FRAMES_PER_SECOND / MAX_FRAMESKIP).
Слабое железо
На слабом железе FPS просядет, но сама игра будет работать с большой вероятностью на нормальной скорости. Если же железо не сможет выдерживать даже минимальное FPS, то начнет тормозить и обновление состояния, а отрисовка потеряет даже намек на плавную анимацию.
Мощное железо
На мощном железе игра будет работать без особых проблем, но как и в первой реализации процессор будет использоваться неэффективно. Поиск баланса между быстрым обновлением и возможностью работать на слабом железе получает решающее значение.
Вывод
Использование фиксированной скорости игры и максимально возможного FPS — решение, которое несложно реализовать и которое сохраняет код простым. Но все равно имеются некоторые проблемы: задание слишком большой частоты обновления состояния породит проблемы на слабом железе (пусть и не настолько серьезные, как в случае первой реализации), а задание малой частоты обновления состояния будет неэффективно использовать вычислительные мощности (ресурсы можно было бы использовать для увеличения плавности анимаций, но вместо этого они тратятся на частую отрисовку).
Постоянная скорость игры, независящая от переменного FPS
Реализация
Возможно ли улучшить предыдущую реализацию, чтобы она работала быстрее на слабом железе и была бы визуально привлекательнее на мощном? Ну, к счастью для нас, да, это возможно! Состояние игры не нужно обновлять 60 раз в секунду. Пользовательский ввод, ИИ, а также обновление состояния игры, достаточно обновлять 25 раз в секунду (я с этим не согласен, не всегда, прим. перев.). Так что давайте вызывать update_game() 25 раз в секунду, не чаще, не реже. А вот отрисовка пусть выполняется так часто, как железо потянет. Но медленная отрисовка не должна сказываться на частоте обновления состояния. Как добиться этого показано в следующем коде.
В результате реализация 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), которую вы определенно должны избегать. Постоянная частота кадров может быть приемлемой на мобильных устройствах. Однако, если вы хотите портировать игру на разные платформы, то придется развязывать частоту обновления и частоту отрисовки, реализовывать интерполяцию и предсказание. Если не хотите заморачиваться с предсказанием и интерполяцией, то можете использовать большую частоту обновления состояния, но найти оптимальное ее значение для слабого и мощного железа может оказаться сложной задачей.