Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong

Привет, Пикабу! Пишу первый раз, хочется рассказать всему миру, что моя первая игра вышла на куче платформ, но об этом в конце. Сам пост о задаче, с которой столкнулся уже на релизе.
Итак, меня зовут Коля и я хочу рассказать тебе о проблеме, с который ты можешь столкнуться при разработке ритм-игры (и с которой точно столкнешься, если разработка будет под мобильные устройства). Ну и о её решении тоже поведаю, конечно.

В марте 2019 я выпустил в Steam свою первую ритм-игру Lofi Ping Pong. Это настольный теннис, в котором мяч надо отбивать в такт треку. Летом 2020 мне захотелось отдохнуть от разработки второй музыкальной поделки, так что решил портировать Пинг понг на мобилки и Switch. Тут-то и появились сложности - мяч вдруг начал лететь отрывисто, "заикаться". Чтобы вкратце разобраться с недугом, требуется вступление.
Ниже геймплей игры.

Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS

Про особенность музыкальных игр.

Не буду рассказывать про архитектуру ритм-игр в целом (собираюсь сделать это после выпуска второго проекта), но пояснить за основную идею я обязан.

В большинстве игр обязательно есть какое-либо движение - будь то носящийся по всей карте Meat Boy или падающий с потолка ящик в Portal. Любое перемещение становится плавным, когда мы добавляем один волшебный ингредиент - Delta Time, то есть время между фреймами. С помощью него, как известно, мы перестаем зависеть от выдаваемого фпс, что делает, к примеру, скорость персонажа всегда одинаковой.

Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS

И тут тебе приходит сумасшедшая мысль сделать игру, в которой что-то происходит в такт музыке. Ты набросал на бумаге идею, лезешь в любимый движок, чтобы воплощать её в жизнь. К примеру, захотелось сделать настольный теннис, в котором мяч отбиваешь в такт треку, как метроном. Что может быть проще?

У нас есть расстояние между началом и концом полета мяча S. Чтобы рассчитать время полёта T, требуется знать скорость песни - её BPM (beats per minute). Это количество ударов (долей) в минуту, как если бы вы отстукивали темп песни ладошкой по коленке и записали количество шлепков за 60 секунд. Количество чего-либо в единицу времени есть частота, значит, чтобы найти время одного удара (период), достаточно перевернуть её с ног на голову, не забыв перевести в секунды, умножив на 60 (T = 60 / BPM).

В школе вроде учили, что скорость V = S / T. Не забудем добавить в формулу наш любимый delta time, и готово!

Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS
Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS

Как только мяч долетает до нужной позиции, мы нажимаем кнопку, меняя end point и start point местам, и движение продолжается, но в обратную сторону. И всё идёт прекрасно, пока ты так не поиграешь 10, 30, 60 секунд. После этого начнётся сильнейший рассинхрон между играющей песней и скачущим мячом - каждый новый удар будет всё дальше удаляться от реального бита песни.

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

Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS

Тебе надо следить за позицией трека и считать, как много времени прошло с предыдущего бита. Время между битами - как расстояние между start point и end point. Delta between beats будет отображать позицию нашего мяча. Но это довольно странно ставить знак равно между временем (delta between beats) и координатой (позиция мяча). Понятнее будет перевести всё в доли- поделив delta between beats на time between beats мы получим процент между соседними битами. Этот процент будет таким же у мяча между начальной и конечной позицией.

Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS
Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS

Весь этот блок был написан, чтобы показать зачем и как использовать в качестве двигателя мяча именно сам трек, а не просто его скоростную характеристику. Это и есть то самое ядро, на котором строится ритм игра. Помимо этого, как и в других играх, есть куча нюансов, типа начального оффсета у песни или как учесть визуальный/аудио лаг у игрока в перемещении мяча. Самое главное, мы поняли, что Delta Time нам не нужен.

Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS

Про проявление заиканий и решение.

Разобравшись с главным концептом, ты делаешь основную игровую петлю, тестируешь на ПК, все идёт прекрасно. До того момента, как ты решишь запустить игру на мобильном устройстве.

Тут наступает ужасное - мяч летит отрывисто, заикается, как будто игра идёт в 15 фпс. Ты профайлишь игру, но все показатели в норме, да и остальные элементы игры, не зависящие от хода музыки, ведут себя адекватно. Может, мы рано решили избавиться от Delta time?

Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS

Ты начинаешь дебажить позицию песни каждый кадр - и что же ты видишь! Оказывается, позиция трека не обновляется покадрово, а скачет, как ей вздумается! Вместо того, чтобы в окне дебага видеть "0, 16, 33, 49, 65, 80..." (мс), показывается вот это "0, 0, 0, 48, 48, 65, 65, 65...". Аудиодвижок просто-напросто живёт своей жизнью и отказывается подчиняться обновлению каждый кадр (те кто работают в Гамаке знают, что если во время теста игры она у вас крашнется, то аудио продолжит работать в отрыве от картинки).

ПК, как известно, платформа помощнее, чем мобильные устройства, и эти фризы там не так заметны (хотя они есть, если знаешь, с чем сравнивать).

Что ж, значит нам придётся вручную "догонять" позицию трека, чтобы она плавно переходила от одного значения к следующему. Плавно... где-то я это слышал... delta time! Почему бы здесь нам не использовать нашего старого друга, ведь всё же мы будем увеличивать позицию искусственным путём.

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

Пример первый, неправильный. Давайте введём новую переменную для отслеживания позиции трека в предыдущем фрейма lastFrameTrackPosition. Тогда мы каждый кадр можем сравнивать нынешнюю позицию песни и её позицию на предыдущем кадре. Если они совпадают, значит положение песни "не прибавилось", и мы сделаем это сами. Если позиция трека так долго не обновлялась, что lastFrameTrackPositon убежала вперед, то мы сами её увеличим.

Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS

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

Можно вновь обвинить Delta time и сказать, что дело в нём, но это не так. Точнее, мы просто слегка неправильно его используем.

Давайте оставим переменную lastFrameTrackPosition и введём ещё одну - trackPositionContainer, которая поможет нам не изменять позицию трека напрямую через прибавку delta time, но с помощью постепенного приближения (известного как easing). Мы опять начнём со сравнения положения песни в текущий и предыдущий кадр. Делаем только одно сравнение - не равны ли они, и если они и правда отличаются, то мы приблизим значение trackPositionContainer к позиции трека с помощью среднего арифметического. И возвращать в качестве позиции песни для MoveBall() мы будем именно приблИженное значение контейнера, но не самой рваной позиции трека.

Заикание в ритм-играх - симптомы и лечение на примере Lofi Ping Pong Ритм-игра, Gamedev, Релиз, Туториал, Гифка, Видео, Длиннопост, Steam, Nintendo Switch, Android, iOS

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

Теперь, наконец, гештальт закрыт. Я перерыл старый код, заново переписал игру, сделал порты на мобилки и сегодня выходит последний порт на Nintendo Switch. Надеюсь, было полезно и хоть немного интересно, ребятки. Оставлю ссылки на всевозможные сторы, если захотите посмотреть проект. Спасибо за прочтение!

Nintendo Switch: https://www.nintendo.ru/-/-Nintendo-Switch/Lofi-Ping-Pong-18...
App Store: https://apps.apple.com/app/id1539408060
Google Play: https://play.google.com/store/apps/details?id=com.Calvares.L...
Steam: https://store.steampowered.com/app/1028570/Lofi_Ping_Pong/

Лига Разработчиков Видеоигр

6.8K пост22.2K подписчик

Добавить пост

Правила сообщества

ОБЩИЕ ПРАВИЛА:

- Уважайте чужой труд и используйте конструктивную критику

- Не занимайтесь саморекламой, пишите качественные и интересные посты

- Никакой политики


СТОИТ ПУБЛИКОВАТЬ:

- Посты о Вашей игре с историей её разработки и описанием полученного опыта

- Обучающие материалы, туториалы

- Интервью с опытными разработчиками

- Анонсы бесплатных мероприятий для разработчиков и истории их посещения;
- Ваши работы, если Вы художник/композитор и хотите поделиться ими на безвозмездной основе

НЕ СТОИТ ПУБЛИКОВАТЬ:

- Посты, содержащие только вопрос или просьбу помочь
- Посты, содержащие только идею игры

- Посты, единственная цель которых - набор команды для разработки игры

- Посты, не относящиеся к тематике сообщества

Подобные посты по решению администрации могут быть перемещены из сообщества в общую ленту.

ЗАПРЕЩЕНО:

- Публиковать бессодержательные посты с рекламой Вашего проекта (см. следующий пункт), а также все прочие посты, содержащие рекламу/рекламные интеграции

- Выдавать чужой труд за свой

Подобные посты будут перемещены из сообщества в общую ленту, а их авторы по решению администрации могут быть внесены в игнор-лист сообщества.


О РАЗМЕЩЕНИИ ССЫЛОК:

Ссылка на сторонний ресурс, связанный с игрой, допускается только при следующих условиях:

- Пост должен быть содержательным и интересным для пользователей, нести пользу для сообщества

- Ссылка должна размещаться непосредственно в начале или конце поста и только один раз

- Cсылка размещается в формате: "Страница игры в Steam: URL"