¿Cuál es la forma estándar de sincronizar efectos de sonido con animaciones de sprites?

8

Consideremos una situación en la que tienes un juego de rol con hechizos y cada animación de hechizo tiene un número diferente de fotogramas y tienen requisitos muy diferentes para los efectos de sonido. Supongamos que cada hechizo tiene solo 1 animación continua asociada (a diferencia de las múltiples piezas modulares que se utilizan para formar una animación completa) en los viejos juegos de 16 bits de Final Fantasy.

La única forma en que puedo pensar para asegurarme de que los sonidos y las animaciones se sincronicen es:

  • Obtener el número de cuadros de una animación.
  • Obtenga el tiempo entre cada cuadro de la animación. (si son 30 fps, entonces es 1/30 de segundo por fotograma).
  • Luego, cree un archivo de sonido que tenga exactamente la misma longitud que la animación.

Esto significa que si una animación dura 5 segundos y se ejecuta a 30 fps, con un total de 150 fotogramas, el archivo de sonido también durará 5 segundos. Si la animación debe tener un sonido de "impacto" en el cuadro 30, eso significa que el archivo de sonido incluirá el sonido de impacto en la marca de 1.0 segundo.

Al final, comenzamos la animación y el efecto de sonido exactamente al mismo tiempo y esperamos que los cuadros y el sonido se sincronicen.

Parece que podría plantear problemas cuando se omiten los fotogramas o sucede algo durante la animación y el sonido se reproduce un poco demasiado pronto o demasiado tarde, y hará que el sonido y la animación no estén sincronizados. ¿Es este el mejor enfoque o generalmente hay una mejor manera que simplemente no estoy viendo?

La respuesta no necesariamente tiene que ser para Cocos2D específicamente si es conceptual, pero si hay una solución específica para cocos2d, me encantaría escucharla.

EDITAR: también me doy cuenta de que con este método, si entramos y ajustamos el número de fotogramas o el tiempo de la animación más tarde, también DEBEMOS regresar y cambiar el archivo de sonido. Esto suena como una causa terrible de error humano (olvidando actualizar los archivos de sonido después del cambio de animación). Espero que haya mejores métodos disponibles.

Jamornh
fuente
esta es la razón por la cual los bucles de juego de tiempo constante son útiles: entonces no necesita preocuparse por la falta de sincronización de la animación
fanático de trinquete
@ratchetfreak Creo que cocos2d administrará el tiempo de la animación correctamente. Si creo una animación y le digo a cocos2d que quiero exactamente 1/30 de segundo entre fotogramas, garantizará esto y omitirá fotogramas si el rendimiento no es lo suficientemente bueno. Esto garantiza que la animación se complete en el momento correcto (es decir, en tiempo constante). Dado eso, ¿está diciendo que el método que describí anteriormente es el camino correcto?
Jamornh

Respuestas:

6

Hazlo a través de eventos.

El hechizo comienza es un evento . Comience a reproducir el sonido para ese evento.

Enemigo ser golpeado por un hechizo también es un evento. Si el enemigo está más lejos y arrojas un dardo, por ejemplo, solo juegas el segundo sonido (dardo) una vez que el dardo alcanza el objetivo (si consideras Lanzar como un hechizo).

Si necesita vincularlo a un cuadro (por ejemplo, reproduzca el sonido de "explosión" dentro de 30 cuadros a pesar de las velocidades de cuadro en tiempo real ), la forma más fácil de hacerlo es mediante devoluciones de llamada . Las devoluciones de llamada son solo "bloques de código que programa para ejecutar en el futuro". Aquí hay un ejemplo de mi creador de creación de devolución de llamada:

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

A TimedCallbackes solo un contenedor alrededor de un std::function(o podría usar un Objective-C ^{block}. El std::functionse ejecuta cuando su marco está activo.

Otra forma (menos global) es incluir eventos en su animación . Entonces, si necesita reproducir sonidos específicos en cuadros de animación específicos, almacene un map<int,Sound*>en la Animationclase. En cada cuadro de la animación, verifique si hay un Sound*juego correspondiente para ese cuadro.

También podría almacenar un map<int, std::function>, en el Animationobjeto, que le permitiría devolver la llamada a functiondurante la animación.

bobobobo
fuente
Creo que puede haber malinterpretado la pregunta ligeramente. Su método funcionaría para sonidos cortos destinados a "golpear", "golpear", "patear", "disparar" tipo que no dura más de una fracción de segundo donde la sincronización no es un problema (simplemente puede reproducir el efecto de sonido "en evento "como sugirió.) Sin embargo, con una animación larga + sonido (es decir, armageddon, donde 5-6 meteoros caen del cielo y golpean el suelo en diferentes cuadros, pero son parte de la animación de 1 sprite [como lo hace la fantasía final] solo tendrá 1 evento de inicio, no uno por meteorito) este método no garantizará la sincronización ¿verdad?
Jamornh
3
@Jamornh En tu ejemplo de lluvia de meteoritos, disparas un evento: por cada meteorito que comienza a caer, por cada meteorito que cae al suelo, tal vez uno para los personajes que luchan por lanzar el hechizo. Con esta solución, incluso puede cambiar la cantidad de meteoros y no tener problemas con el audio.
akaltar
1
@Jamornh También puede poner en cola un evento para "reproducir sonido de explosión dentro de 30 cuadros", la forma más fácil de hacerlo es utilizando una función de devolución de llamada . Detallaré esto en mi respuesta.
bobobobo 01 de
1
@Jamornh La animación no necesariamente tiene que ser modular para poder disparar múltiples eventos (en momentos predeterminados). En su editor de efectos, podría simplemente decir, reproducir sonido boom en el cuadro 32.
akaltar
1
Sí, eso funcionaría. Si estaba utilizando JSON, sugeriría una estructura de datos como { 'images':'sprite-%02d.png', 'beginRange':1, 'endRange':32, 'sounds':{ 0 : 'startSpell.wav', 30 : 'impact.wav' } }. Si su juego está en una etapa muy temprana y el tiempo de compilación aún es bajo, puede comenzar codificando las estructuras de datos para ver si funciona.
bobobobo 01 de
1

La forma en que lo hago es hacer oyentes de eventos personalizados para mi clase de animación y hacer que controlen mi sonido. así que si mi animación ha comenzado callback.start (); y empiezo mi sonido en ese método. si se pausó mi animación do callback.pause (); y pausa el sonido. cuando la animación ha terminado, callback.end (); y que el sonido también termine.

pero para una sincronización perfecta, iría tan lejos como contar cuadros de sonido y dormir (pausar) mi sonido si llega a un extremo y hacer lo mismo para mi animación.

Nunca he tenido que hacer eso hasta este día porque la primera sugerencia satisface mis necesidades perfectamente por ahora.

Jonathan Camarena
fuente
¿Podrías explicar cómo contarías un "marco" de sonido? ¿Te refieres a contar el número de milisegundos que han pasado desde que comenzó el sonido y realizar un conteo por cada intervalo predefinido?
Jamornh
no no, literalmente me refiero al marco en el que está el sonido. No sé cómo lo haces en cocos2D, pero en java sound hay métodos para obtener el número de fotogramas de sonido y el "fotograma" actual, también hay una manera de establecer el fotograma actual en cierto. Estoy seguro de que si lo investigas, podrías encontrar variables similares dentro de tu interfaz de sonido, pero como alguien señaló, es mejor que tus actualizaciones manejen el tiempo de animación, etc. para que no tengas que hacer tales hacks de sincronización.
Jonathan Camarena