Синхронизация ритма в музыкальных играх

http://www.gamasutra.com/blogs/YuChao/20170316/293814/Music_Syncing_in_Rhythm_Games.php
  • Перевод
image

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

Выяснилось, что самыми важными являются следующие три аспекта:

  • Использование AudioSettings.dspTime вместо Time.timeSinceLevelLoad для отслеживания позиции в песне.
  • Нужно всегда использовать позицию в песне для обновления движений.
  • Не обновляйте ноты в каждом кадре по разнице во времени, интерполируйте их.

Учтём это и приступим к работе!

Основной класс


Необходимо создать класс SongManager для отслеживания позиции в песне, создания нот и других функций управления песней.

Отслеживание позиции


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

//текущая позиция в песне (в секундах)
float songPosition;

//текущая позиция в песне (в ударах)
float songPosInBeats;

//длительность удара
float secPerBeat;

//сколько времени (в секундах) прошло после начала песни
float dsptimesong;

Мы инициализируем эти поля в функции Start():

void Start()
{
    //вычисление количества секунд в одном ударе
    //объявление bpm выполняется ниже
    secPerBeat = 60f / bpm;
    
    //запись времени начала песни
    dsptimesong = (float) AudioSettings.dspTime;

    //начало песни
    GetComponent<AudioSource>().Play();
}

Для удобства мы преобразуем bpm в secPerBeat. Позже secPerBeat будет использоваться для вычисления позиции в песне в ударах, что очень важно для создания нот.

Кроме того, мы записываем время начала песни в dsptimesong. Мы используем AudioSettings.dspTime вместо Time.timeSinceLevelLoad, потому что Time.timeSinceLevelLoad обновляется только в каждом кадре, а AudioSettings.dspTime обновляется чаще, так как это таймер аудиосистемы. Чтобы сохранять темп песни, нужно использовать таймер аудиосистемы. Таким образом нам удастся избежать задержки, вызванной разницей во времени между обновлениями кадров и обновлениями аудио.

В функции Update() вычисляется позиция в песне с помощью AudioSettings.dspTime:

void Update()
{
    //вычисление позиции в секундах
    songPosition = (float) (AudioSettings.dspTime - dsptimesong);

    //вычисление позиции в ударах
    songPosInBeats = songPosition / secPerBeat;
}

Мы вычисляем позицию в секундах вычитанием из текущего AudioSettings.dspTime времени начала песни (dsptimesong). Мы получили позицию в секундах, однако в мире музыки ноты записываются в ударах. Поэтому лучше преобразовать позицию в секундах в позицию в ударах. Разделив songPosition на secPerBeat (секунда / (секунда/удар) ), мы получим позицию в ударах.

Посмотрите на рисунок:



Позиция нот в ударах: 1, 2, 2.5, 3, 3.5, 4.5, а длительность удара — 0,5 с. Поэтому, если после начала песни прошло 1,75 с (songPosition == 1.75), то мы знаем, что находимся в позиции 1.75 (songPosition) / 0.5 (secPerBeat) = 3.5 удара, и необходимо создать ноту удара 3.5.

Информация о песне


Перейдём к полям, в которые мы записали информацию о песне:

//количество ударов в минуту
float bpm;

//сохранение всех позиций нот в ударах
float[] notes;

//индекс ноты, которую нужно создать следующей
int nextIndex = 0;

Для простоты я демонстрирую песню только с одной дорожкой нот (в Guitar Hero Mobile сделано три дорожки, а в Taikono Tatsujin — всего одна).

bpm — это количество ударов в минуту. Как мы видели, для удобства они преобразуются в secPerBeat.

notes — это массив, в котором хранятся все позиции нот в ударах. Например, для представленных на рисунке нот массив notes будет содержать {1f, 2f, 2.5f, 3f, 3.5f, 4.5f}:



И, наконец, nextIndex — это целое число, нужное для обхода массива. Оно инициализируется со значением 0, потому что следующая создаваемая нота будет первой нотой песни. При создании ноты счётчик nextIndex увеличивается на единицу.

Создание нот


Мы определяем, должна ли создаваться нота, в функции Update(). Однако сначала нужно определить, сколько ударов будет показываться заранее.

Например, для следующей дорожки:



текущая позиция в ударах равна 1, но удар 3 уже создан. Это означает, что заранее показываются 3 удара.

Добавим под songPosInBeats = songPosition / secPerBeat;, следующие строки:

if (nextIndex < notes.Length && notes[nextIndex] < songPosInBeats + beatsShownInAdvance)
{
    Instantiate( /* префаб ноты */ );

    //инициализация полей ноты

    nextIndex++;
}

Сначала нужно проверить, не осталось ли нот в песне (nextIndex < notes.Length). Если ноты ещё остались, то мы проверяем, достигла ли песня удара, при котором должна создаваться следующая нота (notes[nextIndex] < songPosInBeats + beatsShownInAdvance). Если достигла, создаём ноту и увеличиваем nextIndex, чтобы отслеживать следующую ноту, которую нужно создать.

Движение нот


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

Всегда обновляйте движение по позиции в песне, потому что:

  1. Таймер аудиосистемы имеет разницу во времени с таймером кадров
  2. Удары могут находиться ровно посередине двух кадров (что приводит к разнице во времени)

Итак, как же двигать ноты? Интерполяцией!

Для упрощения я вырежу весь код в классе MusicNote и оставлю только функцию Update(), в которой мы двигаем каждую ноту:

//функция обновления нот
void Update()
{
    transform.position = Vector2.Lerp(
        SpawnPos,
        RemovePos,
        (BeatsShownInAdvance - (beatOfThisNote - songPosInBeats)) / BeatsShownInAdvance
    );    
}

На представленной ниже схеме это чётко видно:



Заключение


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

Благодарю за прочтение статьи, надеюсь, она будет полезна. Моя собственная музыкальная игра Boots-Cuts будет готова в следующем году, следите за информацией.
  • +27
  • 9,8k
  • 9
Поделиться публикацией

Комментарии 9

    0
    Спасибо за статью, и сразу вопрос ) А что если в песне bpm будет меняться?
      0
      Еще одно.
      Делайте в настройках регулируемую задержку между звуком и изображением, в идеале небольшой кнопочкой перед запуском уровня. Т.к. человек в разном состоянии имеет разную скорость реакции «глаз/ухо-мозг-обработка-руки», то при полностью синхронном выводе изображения и звука обязательно получится ощущаемый рассинхрон.
        0

        Я так и не понял, как из аудиофайла вычислить ноты и bps.

          0
          Я увы тоже (
          Но это лишь перевод…
          Сам дошёл лишь до «эквалайзер вид сверху»…
            0
            Если в кратце, то никак :)

            В ритм играх (допускаю, что конкретно в Guitar Hero это может быть не точно так, как написал), как правило, есть аудиотрек и так называемый simfile — мета-файл в котором и описаны:
            • GAP (OFFSET) — задержка от начала аудиофайла до первой ноты. Авторы часто подгоняют до первой ноты после вступления
            • BPM — темп. Что очень важно, его надо считать очень точно, желательно 5-7 цифр после запятой
            • BPM Changes — обычно время=новый_темп, время=новый_темп и т.д.
            • Delay/Pause — остановки в треке. С нимим есть проблемы точности: паузы вычисляются со своей точностью, которая не совпадает с делением на доли ритма в треке. Их как правило стараются заменить на кратную смену темпа на что-то < 5 и обратно компенсируют. Для таких дел даже скрипт написан
            • Массив данных — собственно, в каждый шаг трека какая нота должна быть сыграна


            Вот пример для кнопочной ритм-игры: DDR (ITG).
            Там 4 кнопки — поэтому 4 символа в слове для состояния. Разумеется есть редактор, вручную набирать не приходиться:
            Cookie Thumper.sm
            #TITLE:Cookie Thumper;
            #SUBTITLE:;
            #ARTIST:Die Antwoord;
            #TITLETRANSLIT:;
            #SUBTITLETRANSLIT:;
            #ARTISTTRANSLIT:;
            #GENRE:;
            #CREDIT:;
            #BANNER:../banner.png;
            #BACKGROUND:../bg.png;
            #LYRICSPATH:;
            #CDTITLE:;
            #MUSIC:Cookie Thumper.ogg;
            #OFFSET:0.003;
            #SAMPLESTART:48.350;
            #SAMPLELENGTH:12.000;
            #SELECTABLE:YES;
            #BPMS:0.000=134.000,107.000=67.000,143.000=134.000;
            #STOPS:92.000=0.448
            ;
            #BGCHANGES:;
            #KEYSOUNDS:;

            //---------------dance-single — Nix----------------
            #NOTES:
            dance-single:
            Nix:
            Challenge:
            10:
            0.631,0.711,0.437,0.164,0.665:
            0000
            0000
            0000
            0000
            ,
            0000
            0000
            0000
            0000
            ,
            0000
            0000
            0000
            0000
            ,
            0000
            0000
            0000
            0000
            ,
            0000
            0000
            0000
            0000
            ,
            0000
            0000
            0000
            0000
            ,
            2001
            0000
            3100
            1000
            0010
            0100
            0000
            0110
            ,
            0000
            0100
            0001
            0010
            1000
            0000
            0010
            0000
            ,
            0011
            0000
            0001
            0100
            1000
            0100
            0000
            0110
            ,
            0000
            0010
            1000
            0100
            0010
            0000
            0001
            0000
            ,
            1000
            0000
            1000
            0100
            0000
            0100
            0010
            0000
            0001
            0000
            0100
            0010
            1000
            0000
            0100
            0000
            ,
            0010
            0001
            0010
            0100
            0000
            0100
            1000
            0010
            0100
            0000
            0001
            0000
            1001
            0000
            0000
            0001
            ,
            0100
            0010
            1000
            0010
            0000
            0010
            0100
            0001
            0010
            0000
            0100
            0000
            1100
            0000
            0000
            1000
            ,
            0020
            0000
            0230
            0000
            0301
            0100
            0001
            0000
            0101
            0000
            1001
            0000
            2020
            0000
            3030
            0000
            ,
            0120
            0000
            0130
            0001
            0100
            0010
            1000
            0000
            1100
            0000
            0110
            0000
            0011
            0000
            0000
            0000
            ,
            0001
            0000
            0100
            0010
            1000
            0100
            0010
            0001
            1000
            0000
            0010
            0000
            0100
            0000
            0001
            0000
            ,
            0010
            0100
            1000
            0001
            0000
            0001
            1000
            0010
            0100
            0000
            0001
            0000
            0020
            0000
            0000
            0000
            ,
            0030
            0000
            0000
            0000
            0100
            1000
            0100
            0010
            0001
            0000
            0100
            0000
            0110
            0000
            0000
            0000
            ,
            1000
            0010
            0100
            0001
            1000
            0100
            1000
            0001
            0010
            0000
            0100
            0000
            0020
            0000
            0031
            0000
            ,
            0100
            0010
            0000
            1000
            0000
            0010
            0000
            0010
            0100
            0000
            0001
            0000
            1000
            0000
            0100
            0010
            ,
            0001
            0010
            0100
            1000
            0000
            1000
            0001
            0100
            0010
            1000
            0010
            0000
            0100
            0000
            0000
            0000
            ,
            0001
            0010
            0000
            1000
            0000
            0010
            0000
            0100
            1000
            0010
            0100
            0000
            0001
            0000
            0000
            0000
            ,
            1000
            0100
            0010
            0100
            0001
            0100
            0010
            0000
            0022
            0000
            0000
            0000
            0033
            0000
            1000
            0000
            ,
            1010
            0000
            0010
            0100
            0001
            0000
            1000
            0000
            1100
            0000
            0000
            0000
            0100
            0000
            0100
            0010
            ,
            0001
            0010
            0100
            1000
            0001
            0000
            0100
            0000
            0020
            0000
            0000
            0000
            0030
            0000
            0100
            0000
            ,
            0001
            0000
            0000
            0000
            1000
            0000
            1000
            0010
            0100
            0001
            0100
            0000
            0220
            0000
            0330
            0000
            ,
            2002
            0000
            0000
            0000
            0000
            0000
            3003
            0000
            0001
            0000
            1000
            0001
            1000
            0000
            0000
            1000
            ,
            0001
            0100
            0000
            0110
            0000
            0010
            1000
            0100
            0001
            0000
            0100
            0000
            1100
            0000
            0000
            1000
            ,
            0010
            0100
            0000
            0110
            0000
            0100
            0001
            0010
            1000
            0000
            0010
            0000
            0110
            0000
            0100
            1000
            ,
            0010
            0100
            0000
            0101
            0000
            0001
            0010
            1000
            0001
            0000
            0100
            0000
            1100
            0000
            0100
            0010
            ,
            0001
            0000
            0100
            0000
            0000
            0000
            0101
            0000
            0000
            0000
            0001
            0000
            1000
            0010
            1000
            0010
            1000
            0000
            0001
            0100
            0001
            0000
            0001
            0000
            0101
            0000
            0000
            0000
            0000
            0000
            0100
            0000
            ,
            0010
            1000
            0000
            1100
            0000
            0100
            0001
            1000
            0010
            0000
            0100
            0000
            1100
            0000
            1000
            0010
            ,
            0100
            0001
            0000
            0011
            0000
            0010
            1000
            0010
            0001
            0000
            0100
            0000
            0110
            0000
            0010
            0001
            ,
            0010
            1000
            0000
            1010
            0000
            0010
            0001
            1000
            0100
            0000
            0001
            0000
            1001
            0000
            1000
            0100
            ,
            0001
            0000
            0100
            0000
            0000
            0000
            1100
            0000
            0000
            0000
            1000
            0000
            0001
            0010
            0001
            0010
            0001
            0000
            1000
            0100
            1000
            0000
            1000
            0000
            0200
            0000
            0000
            0000
            0000
            0000
            M0MM
            0000
            ,
            0000
            0000
            0000
            0000
            0000
            0000
            M0MM
            0000
            0000
            0000
            0000
            0000
            0000
            0000
            M0MM
            0000
            0000
            0000
            0000
            0000
            0300
            1000
            0001
            0000
            0101
            0000
            0000
            0000
            0000
            0000
            0000
            0000
            ,
            0000
            0000
            0101
            1001
            1010
            1000
            0100
            0010
            ,
            0001
            0000
            0100
            0001
            1000
            0000
            0010
            0000
            0110
            0000
            0000
            0000
            0100
            0001
            1000
            0000
            ,
            0100
            0000
            0000
            0010
            0000
            0000
            0001
            0000
            0000
            0010
            0000
            0000
            0100
            0000
            0000
            0000
            0000
            0000
            1000
            0000
            0000
            0000
            0000
            0000
            0010
            0000
            0000
            0000
            1000
            0000
            0100
            0000
            0000
            0000
            0000
            0000
            1100
            0000
            0000
            0000
            0000
            0000
            0000
            0000
            0000
            0110
            0000
            0000
            ,
            0000
            0000
            0011
            0000
            0000
            1000
            0001
            0000
            1011
            0000
            0000
            1000
            0100
            0000
            0200
            0000
            ,
            0310
            0000
            0001
            0000
            0100
            0000
            1000
            0000
            0100
            0000
            0000
            0000
            0001
            0010
            0000
            1000
            ,
            0000
            0001
            0000
            0100
            0010
            0000
            0001
            0000
            0100
            0000
            0000
            0000
            0010
            1000
            0000
            0001
            ,
            0000
            0100
            0010
            1000
            0100
            0000
            0010
            0000
            0001
            0000
            0000
            0000
            1000
            0100
            0000
            0001
            ,
            0000
            0100
            1000
            0010
            1000
            0000
            0100
            0000
            0010
            0000
            0000
            0010
            0001
            0000
            0100
            0000
            ,
            0010
            0000
            1000
            0010
            1000
            0000
            0100
            0000
            0010
            0000
            0000
            0000
            0010
            0000
            0100
            1000
            ,
            0001
            0100
            0010
            1000
            0100
            0000
            0010
            0000
            0001
            0000
            0000
            1000
            0100
            1000
            0010
            0000
            ,
            0100
            0001
            1000
            0000
            0100
            0000
            0010
            0000
            0001
            0000
            0000
            0000
            0101
            0000
            0110
            0000
            ,
            0010
            0000
            1000
            0010
            1000
            0010
            0100
            0000
            0001
            0000
            0010
            0000
            1000
            0000
            0010
            0000
            ,
            0100
            0000
            1000
            0000
            0010
            0100
            0010
            0000
            1000
            0000
            0000
            0000
            0101
            0000
            0001
            0000
            ,
            0010
            0000
            0100
            1000
            0001
            1000
            0010
            0000
            0100
            0000
            0110
            0000
            1010
            0000
            0001
            0010
            ,
            0100
            1000
            0100
            0010
            0001
            0000
            1000
            0000
            0010
            0000
            0100
            0000
            0001
            0010
            0001
            0000
            ,
            0010
            0100
            1000
            0010
            0200
            0000
            0000
            0000
            ,
            0300
            0000
            0000
            0000
            ;
              0

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

                0
                А на каком этапе понимания проблемы у вас возник вопрос? Уже после всяких преобразований Фурье или вообще с самого начала (с сырого аудиофайла)?
                  0

                  Где-то на этапе между скачиванием готовой библиотеки и загрузкой в неё мп3

                    0
                    Скорее всего решается через это самое преобразование Фурье и последующей фильтрацией трека по частотам — например можно найти низкие частоты басовой бочки и по пикам амплитуды определить расстояние. Скорее всего этот «бит» будет отбивать четверти или восьмые с редкими ускорениями, не выбивающимися из общего ритма. И с мелодией (обычно более высокими частотами) то же самое — фильтруем по нужной частоте и смотрим пики амплитуды. Так же, насколько я знаю, в этом деле всегда присутствует какая-то стохастика — есть только определённая вероятность верного определения нужных параметров. Сам я интересовался этой проблемой достаточно поверхностно, но вещи все очень хорошо гуглятся, тема популярная.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое