что такое игровые циклы
Игровой цикл: как удержать игрока у экрана
Снова просадили все выходные в любимой игре? Объясняем почему.
Вы когда-нибудь спрашивали себя, почему красивые, дорогие в производстве блокбастеры часто хочется бросить уже через пару часов, а инди-песочницы с непритязательной картинкой могут запросто затянуть вас на месяцы? Что есть такого в симуляторе строительства фабрик, чего нет в свирепом шутере про истребление демонов из преисподней?
Казалось бы, спорить об этом можно бесконечно. Но, вообще-то, ответ на оба вопроса давно найден. Всё дело в игровых циклах.
Столпы геймдизайна
Как выглядит типичная битва с боссом, например, в Dark Souls? Вы проходите на арену — видите босса — сражаетесь с ним — видите экран с надписью YOU DIED — пытаетесь понять, почему вас убили — сражаетесь снова, пока не убьёте босса.
Эти действия образуют игровой (геймплейный) цикл ( англ. gameplay loop) — набор повторяющихся действий, которые в сумме создают единую систему.
Как вы уже заметили, я опустил ту часть битв с боссами в Dark Souls, где вы прорубаетесь к ним от ближайшего костра сквозь группы рядовых врагов. Дело в том, что это уже другой геймплейный цикл, так же как и прокачка, изучение локаций и мультиплеер.
Игровой энтузиаст. Любит видеоигры, настолки и рассказывать всем, как они устроены.
Вместе они замыкаются в долгосрочный цикл, который можно упрощённо описать так: открыл новую локацию — изучил её — поговорил с персонажами и отыскал секреты — победил боссов и мини-боссов — прокачался — оставил подсказки другим игрокам и пошёл дальше.
Все эти механики связывает то, что называют столпами ( англ. pillars). Левел-дизайнер Макс Пирс (Ubisoft, CD Projekt Red) характеризует их как «главные элементы, которые игра исследует; эмоции, которые испытывают игроки». По сути, это всё те же циклы, но в вертикальном разрезе.
Пирс приводит в качестве примера столпы геймдизайна The Last of Us: крафтинг, история, AI-напарники, стелс. Каждый из них состоит из своего набора циклов — как небольших, так и долгосрочных, — но вместе они образуют ядро геймплея.
Стоит отметить, что формула всех игровых циклов примерно одинакова. Мэттью Крол, ведущий канала Extra Credits, сравнивает её с научным экспериментом: игрок наблюдает цель — видит препятствия — формирует стратегию — проигрывает — ищет причины проигрыша — пробует снова.
Тем не менее эта простая последовательность действий лежит в основе всего многообразия существующих игр. И, как показывает практика, чем раньше авторы определятся с её наполнением, тем лучше. Сырое ядро геймплея погубило немало амбициозных проектов.
Разработчики Anthem, например, признавались, что базовый геймплейный цикл, включая стрельбу и полёты, удалось сделать только в последний год перед релизом, — поэтому всё остальное, включая сюжет и прокачку, осталось незавершённым.
Как же отличить увлекательный игровой цикл от неудачного — заставляющего забросить игру буквально через полчаса после старта? Ответ прежде всего в умении разработчика поставить задачу.
Вижу цель — не вижу препятствий
Мэттью Крол замечает, что в ситуации, когда игрок не может понять, что ему делать дальше, он быстро начинает чувствовать себя потерянным и раздражённым. Поэтому ведущий Extra Credits советует на каждом этапе игры чётко телеграфировать цели и задачи, стоящие перед игроком.
Геральт ищет приёмную дочь Цири. Марио хочет спасти принцессу Пич и победить злодея Боузера. Челл пытается сбежать из-под контроля ГЛаДОС и покинуть лаборатории Aperture.
Всё это — долгосрочные задачи, дающие игроку мотивацию продолжать играть. Но задача может быть и не связана с сюжетом. Неслучайно после успеха Call of Duty 4: Modern Warfare в мультиплеерные шутеры стали массово внедрять систему прокачки. Она автоматически создаёт конечную цель — достичь максимального уровня.
Эта долгосрочная задача дробится на множество более мелких — среднесрочных и краткосрочных. Чтобы достичь потолка в прокачке, нужно набирать уровни. Чтобы набирать уровни, нужно выигрывать матчи. Чтобы выиграть матч, нужно убивать противников, захватывать контрольные точки и так далее.
И очень важно, чтобы игрок хорошо понимал стоящие перед ним цели на каждом из этих этапов. Марк Браун, ведущий канала Game Maker’s Toolkit, считает, что игроку стоит время от времени напоминать о сюрпризах и открытиях, которые ждут его впереди, — будь то разгадка сюжетной тайны или просто новая пушка.
Поэтому в первой Dark Souls вскоре после старта вы натыкаетесь на пока ещё запертые врата крепости Сена. Поэтому в меню выбора персонажа в Injustice или Mortal Kombat вы видите ещё не открытых героев. И по той же причине каждый эпизод Alan Wake и Quantum Break заканчивается клиффхэнгером.
Эти намёки разжигают любопытство игрока и побуждают его не бросать игру — но для того, чтобы увлечь его по-настоящему, нужны другие средства.
Повторение — мать мучения
В том же эссе об игровых циклах Марк Браун признался, что так и не прошёл Doom 2016 года. «[Игра] потрясающая! Её боевые механики — среди лучших за последние годы. Но в определённый момент я потерял интерес и так и не прошёл её», — говорит журналист.
Как противоположность Doom Браун упоминает серию Uncharted. Она, как и The Last of Us, опирается сразу на несколько столпов: решение пазлов, платформинг, стелс, перестрелки и заскриптованные сцены. Naughty Dog плавно переходит от одного цикла к другому, прежде чем какой-то из них успеет надоесть игроку.
В Uncharted 2 неторопливое изучение древнего храма сменяется напряжённой городской перестрелкой, перестрелка — платформингом в железнодорожном депо, а платформинг — эпическим штурмом мчащегося на полном ходу поезда, где найдётся место и стрельбе, и акробатике, и сюжетной драме, и боссу-вертолёту.
Но чем цеплять публику тем играм, у которых нет такого разнообразия в геймплее (и бюджета голливудского блокбастера)? Разнообразием контента.
Hades не может похвастаться таким же ворохом взаимосвязанных механик, как, к примеру, Assassin’s Creed: Valhalla или Cyberpunk 2077. Но это не помешало ей попасть во всевозможные списки лучших игр 2020 года — потому что у неё есть проработанная история.
Главный герой Hades Загрей, сын греческого бога мёртвых Аида, пытается сбежать из загробного мира. Раз за разом он терпит неудачу и возвращается в дом отца — но после каждой его смерти мы встречаем новых персонажей, видим новые сценки с участием уже знакомых NPC и получаем новые кусочки игрового лора.
Для этого композитору и актёру озвучки Даррену Корбу понадобилось записать 20 тысяч строчек диалогов — большой объём для «рогалика», — но результат того стоил. Благодаря усилиям Корба (а также усилиям сценаристов) сюжет постоянно развивается — а значит, у пользователей есть стимул продолжать играть.
«Рогалики» ( англ. roguelike) — хороший пример того, как выжать из простой механики десятки часов игрового процесса. В основе жанра лежит всего два принципа:
И тут очень важно не переборщить с количеством механик или объёмом контента. Ведь в погоне за вниманием публики или собственными амбициями очень легко упустить главное — суть игрового процесса.
Когда море по колено
В 2008 году, после четырёх лет напряжённого ожидания, вышла Spore — симулятор бога, где вверенный вам биологический вид эволюционировал от клетки до галактической цивилизации. После релиза игру встретили прохладные отзывы критиков и аудитории.
Спустя ещё пять лет Сорен Джонсон, ветеран серии Civilization и один из участников разработки Spore, попробовал определить причины провала игры. По мнению геймдизайнера, к неудаче привёл прежде всего конфликт между двумя главными идеями проекта: процедурной генерацией и многоуровневым геймплеем.
Первая идея заключалась в том, что весь контент в игре создаётся алгоритмами, которые будут выполнять ту же функцию, что и молекулы ДНК. Вторая идея свелась к тому, что игровая кампания будет делиться на пять эволюционных ступеней: «Клетка», «Существо», «Племя», «Цивилизация» и «Космос».
«Главная проблема Spore в том, что геймплей на каждой стадии был недостаточно глубоким, потому что команда работала над пятью играми одновременно», — писал разработчик. На каждом этапе игроков встречали новый интерфейс, новое управление, новые термины, новые цели — и ничто из этого не было проработано на должном уровне.
Это был океан механик глубиной с лужу. Более того, на поздних стадиях игры из-за фокуса на социальном аспекте на задний план уходила вторая главная идея Spore — процедурная генерация. Впрочем, она и так играла скорее косметическую роль, ведь внешний вид существ никак не влияет на геймплей и нужен только для самовыражения игрока.
Этот конфликт между двумя основными целями игры геймдизайнер Алекс Жаффе (Riot Games) называет «проклятой проблемой геймдизайна». Разработчик приводит в пример ещё один проект, столкнувшийся с той же дилеммой, — Diablo III.
Летом 2012 года в игре начал работать аукцион, где игроки могли продавать найденные предметы за игровые деньги и покупать за реальные. Нововведение должно было искоренить продажу лута на сторонних сайтах (и принести процент от продаж Blizzard) — но оно шло вразрез с остальными механиками.
Базовый игровой цикл Diablo выглядит так: игрок убивает монстров в подземельях — находит лут — улучшает с его помощью персонажа. С приходом аукциона первые два этапа — убийство монстров и поиск лута — оказались необязательными.
И Джонсон, и Жаффе предлагают одинаковое решение проблемы: «Не бойтесь отказываться от изначального видения проекта». Так и поступила Blizzard, которая осенью 2013 года объявила о закрытии аукциона. Поклонники встретили это решение с восторгом — и некоторых оно даже побудило вернуться в игру.
Легко начать, тяжело забросить
Есть ещё одна причина не переусложнять ядро геймплея — уважение к личному времени игрока.
Почему в настольные RPG играет так мало людей? Ответ простой: чтобы начать партию, нужно прочитать целую книжку с правилами.
Эти игры страдают проблемой, которые Джеймс Портнов (Extra Credits) называет нечленимой сложностью. Иначе говоря, в них столько взаимосвязанных игровых циклов, что научить им по отдельности никогда не получится. Нужно запоминать всё и сразу, а потом пытаться удержать эти знания в голове.
В настольных RPG всегда есть мастер игры, который поможет разобраться. А вот что делать игроку, впервые включившему The Dwarf Fortress или Crusader Kings? Это отличные игры, но они так и останутся предметом культа у нишевой аудитории — ведь далеко не каждый готов потратить несколько часов, чтобы просто разобраться в их механиках.
Это необязательно проблема — скорее факт, с которым стоит смириться. А вот что действительно проблема, так это игры, растянутые искусственно. В год выхода Dragon Age: Inquisition одним из самых популярных текстов на Kotaku был материал «Внимание! Если вы играете в Dragon Age, уходите из Внешних Земель».
В погоне за масштабом разработчики набили первую открытую зону игры фетч-квестами по типу «убей пять волков, принеси три подорожника». Игроки, которые хотели пройти каждое задание, попросту увязали во Внешних Землях и не добирались до основного сюжета.
В другой масштабной RPG, третьем «Ведьмаке», фетч-квестов практически нет. Глава отдела квестов Матеуш Томашкевич ещё в начале разработки взял за правило не делать скучных заданий. И тем не менее игру все равно прошли до конца всего 26% пользователей. Не все пережили путешествие по островам Скеллиге.
Когда перед человеком встаёт выбор, согласиться на шестьдесят часов сюжетной кампании «Ведьмака» или на часовой матч в Battlefield, последний вариант наверняка перевесит. И только спустя несколько месяцев игрок поймёт, что потратил на эти матчи двести часов реального времени.
Желание людей сэкономить время прекрасно понимает Factorio — симулятор строительства фабрик на далёкой планете. Конечная цель здесь — построить ракету и улететь домой. Но для того, чтобы построить ракету, вам сначала нужно построить завод.
И вот вы делаете первый шаг — налаживаете производственную цепочку по производству шестерёнок. Как только вы завершили эту задачу, у вас появляется мысль: «А что, если я автоматизирую производство, чтобы мне не пришлось с ним возиться?» И вот тут игра начинается по-настоящему.
Как заметил Марк Браун: «Очень сложно устоять перед желанием построить оптимизированную систему, которая работает сама по себе».
Ядро геймплея в Factorio обманчиво простое: вы открываете новую технологию — налаживаете её производство — автоматизируете его — открываете другую технологию. А ещё убиваете огромных жуков, которые злятся на вас за то, что вы загрязняете их планету.
И тем не менее в игре уйма механик и контента, которые займут вас на долгое время. При этом Factorio, в отличие от Spore, никогда не упускает из внимания ключевые столпы геймплея. Неважно, что вы делаете: прокладываете железную дорогу, катаетесь на вездеходе или расстреливаете насекомых из турелей, вы всегда занимаетесь производством и автоматизацией.
Игра не тратит ни минуты вашего времени на посторонние задачи и скучные квесты. Поэтому поклонники с радостью проводят в Factorio сотни и даже тысячи часов.
Потому и нужно с самого начала определить ядро геймплея — чтобы игрок никогда не занимался тем, что ему неинтересно. И если вы всё сделаете правильно, он с благодарностью отдаст вам свои деньги и свободное время.
Чем больше, тем хуже или почему важен игровой цикл
По мере эволюции геймдизайна в течение последних 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 минут времени, то стоит пересмотреть онбординг и сам геймплей.
Игровые циклы или ЭлектроКардиоГама
Игровой цикл
Каждая игра содержит последовательность вызовов чтения пользовательского ввода, обновления состояния игры, обработки ИИ, проигрывания музыки и звуковых эффектов, отрисовки графики. Эта последовательность вызовов выполняется внутри игрового цикла. Т.е., как и было сказано в тизере, игровой цикл — это пульс каждой игры. В статье я не буду углубляться в детали реализации упомянутых выше тасков, а сконцентрируюсь исключительно на проблеме игрового цикла. По той же причине я упрощу список тасков до двух функций: обновление состояния и отрисовка. Ниже представлен пример кода для наиболее простой реализации игрового цикла.
Проблема этой реализации в том, что она не обрабатывает время. Игра просто выполняется. На слабом железе игра работает медленно, на сильном — быстро. Давным давно, когда производительность компьютера была известной и примерно одинаковой на разных машинах, такая реализация не рождала проблем. Сегодня же, когда существует множество платформ с разной производительностью, появилась необходимость в обработке времени. Сделать это можно разными путями. О них я расскажу позже. А пока позвольте мне разъяснить пару моментов, которые дальше будут использоваться.
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), которую вы определенно должны избегать. Постоянная частота кадров может быть приемлемой на мобильных устройствах. Однако, если вы хотите портировать игру на разные платформы, то придется развязывать частоту обновления и частоту отрисовки, реализовывать интерполяцию и предсказание. Если не хотите заморачиваться с предсказанием и интерполяцией, то можете использовать большую частоту обновления состояния, но найти оптимальное ее значение для слабого и мощного железа может оказаться сложной задачей.