Каков стандартный способ синхронизации звуковых эффектов со спрайтовой анимацией?

8

Давайте возьмем ситуацию, когда у вас есть RPG с заклинаниями, и каждая анимация заклинания имеет разное количество кадров, и у них очень разные требования к звуковым эффектам. Предположим, что каждое заклинание имеет только 1 непрерывную анимацию, связанную с ним (в отличие от нескольких модульных фигур, которые используются для формирования полной анимации), как в старых 16-битных играх Final Fantasy.

Единственный способ убедиться, что звуки и анимация синхронизированы, это:

  • Получить количество кадров анимации.
  • Получите время между каждым кадром анимации. (если это 30 кадров в секунду, то это 1/30 секунды на кадр.)
  • Затем создайте звуковой файл точно такой же длины, что и анимация.

Таким образом, это означает, что если анимация длится 5 секунд, работает со скоростью 30 кадров в секунду и имеет в общей сложности 150 кадров, звуковой файл также будет иметь длину 5 секунд. Если анимация должна иметь «ударный» звук на 30-м кадре, это означает, что звуковой файл будет содержать звук удара на отметке 1,0 секунды.

В конце мы одновременно запускаем анимацию и звуковой эффект и надеемся, что кадры и звук синхронизируются.

Похоже, что это может создать проблемы, когда кадры пропускаются или что-то происходит во время анимации, и звук воспроизводится немного слишком рано или слишком поздно, что приведет к потере синхронизации звука и анимации. Это лучший подход, или я обычно не вижу лучшего способа?

Ответ не обязательно должен быть конкретно для Cocos2D, если это концептуально, но если есть конкретное решение для cocos2d, я бы хотел услышать его.

РЕДАКТИРОВАТЬ: Я просто также понимаю, что с этим методом, если нам случится войти и настроить количество кадров или время анимации позже, мы ДОЛЖНЫ также вернуться и изменить звуковой файл. Это звучит как ужасная причина человеческой ошибки (забыв обновить звуковые файлы после смены анимации). Я надеюсь, что есть лучшие методы.

Jamornh
источник
Вот почему игровые циклы с постоянным временем удобны: тогда вам не нужно беспокоиться о том, что анимация выйдет из синхронизации
трещотка урод
@ratchetfreak Я верю, что cocos2d будет правильно управлять временем анимации. Если я создаю анимацию и сообщаю cocos2d, что я хочу ровно 1/30 секунды между кадрами, он это гарантирует и пропускает кадры, если производительность не достаточно хорошая. Это гарантирует, что анимация будет завершена в нужное время (т. Е. В постоянное время). Учитывая это, вы говорите, что метод, который я описал выше, является правильным способом?
Jamornh

Ответы:

6

Сделайте это с помощью событий.

Начало заклинания - это событие . Начните воспроизводить звук для этого события.

Враг, получивший заклинание, также является событием. Если враг находится еще дальше, и вы, например, бросаете дротик, вы воспроизводите второй звук (удар дротиком) только тогда, когда дротик достигает цели (если вы рассматриваете Бросок как заклинание).

Если вам нужно привязать его к кадру (например, воспроизводить звук «взрыва» через 30 кадров независимо от частоты кадров в реальном времени )), самый простой способ сделать это - использовать обратные вызовы . Обратные вызовы - это просто «блоки кода, которые вы планируете запускать в будущем». Вот пример моего метода создания обратного вызова:

- (void) addCallback:(Callback*)callback inHowManyTicks:(unsigned long long)execTicksIntoTheFuture
{
  callbacks.push_back( new TimedCallback( tick + execTicksIntoTheFuture, callback ) ) ;
}

A TimedCallback- это просто оболочка вокруг std::function(или вы можете использовать Objective-C ^{block}. The std::functionвыполняется, когда его фрейм поднят.

Другой способ (менее глобальный) - включить события в анимацию . Итак, если вам нужно много раз проигрывать определенные звуки на определенных кадрах анимации, сохраните a map<int,Sound*>в Animationклассе. На каждом кадре анимации проверьте, есть ли соответствующий Sound*кадр для этого кадра.

Вы также можете сохранить объект map<int, std::function>в Animationобъекте, что позволит вам вызвать его functionво время анимации.

bobobobo
источник
Я думаю, что вы, возможно, неправильно поняли вопрос. Ваш метод будет работать для коротких звуков, предназначенных для типов "удар", "удар", "удар", "выстрел", который длится не более доли секунды, когда синхронизация не является проблемой (вы можете просто воспроизвести звуковой эффект "на событие ", как вы предложили.) Однако с длинной анимацией + звуком (то есть армагеддоном, где 5-6 метеоров падают с неба и ударяются о землю в разных кадрах, но являются частью 1 спрайтовой анимации [например, как это делает финальная фантазия) будет иметь только 1 стартовое событие, а не одно на метеорит) этот метод не гарантирует синхронизацию, верно?
Jamornh
3
@Jamornh В вашем примере с метеоритным потоком вы снимаете событие: для каждого метеора, начинающего падать, для каждого метеора, падающего на землю, возможно, один для персонажей, борющихся за наложение заклинания. Используя это решение, вы даже можете изменить количество метеоров и не иметь проблем со звуком.
октября
1
@Jamornh Вы также можете поставить в очередь событие «Воспроизвести звук взрыва через 30 кадров», самый простой способ сделать это - использовать функцию обратного вызова . Я подробно опишу это в своем ответе.
Бобобобо
1
@Jamornh Анимация не обязательно должна быть модульной, чтобы можно было снимать несколько событий (в заранее установленное время). В вашем редакторе эффектов вы можете просто сказать, играть звук бум в 32-м кадре.
akaltar
1
Да, это будет работать. Если бы вы использовали JSON, я бы предложил такую ​​структуру данных { 'images':'sprite-%02d.png', 'beginRange':1, 'endRange':32, 'sounds':{ 0 : 'startSpell.wav', 30 : 'impact.wav' } }. Если ваша игра находится на очень ранней стадии, а время компиляции все еще мало, вы можете начать с жесткого кодирования структур данных, чтобы посмотреть, работает ли она.
Бобобобо
1

То, как я это делаю, - это настраиваемые слушатели событий для моего класса анимации и позволяющие им контролировать мой звук. так что, если моя анимация началась callback.start (); и начать мой звук в этом методе. если моя анимация была приостановлена, делайте callback.pause (); и приостановить звук. когда анимация заканчивается, вы вызываете callback.end (); и имейте конец звука также.

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

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

Джонатан Камарена
источник
Не могли бы вы уточнить, как бы вы посчитали звуковой «кадр»? Вы имеете в виду подсчет количества миллисекунд, прошедших с момента запуска звука, и подсчет за каждый заданный интервал?
Jamornh
нет-нет, я буквально имею в виду кадр, на котором включен звук. Я не знаю, как вы делаете это в cocos2D, но в звуке Java есть методы для получения звуков framecount и текущего «кадра», также есть способ установить текущий кадр на определенный. Я уверен, что если вы посмотрите на него, вы можете найти похожие переменные в вашем звуковом интерфейсе, но, как кто-то заметил, лучше, чтобы ваши обновления обрабатывали время анимации и т.д. чтобы у вас не было таких хаков синхронизации.
Джонатан Камарена