Qual é a maneira padrão de sincronizar efeitos sonoros com animações de sprite?

8

Vamos dar uma situação em que você tem um RPG com feitiços e cada animação de feitiço tem um número diferente de quadros e eles têm requisitos muito diferentes para efeitos sonoros. Vamos supor que cada feitiço tenha apenas 1 animação contínua associada (em oposição a várias peças modulares usadas para formar uma animação completa), além dos antigos jogos de 16 bits de Final Fantasy.

A única maneira de pensar para garantir a sincronização de sons e animações é:

  • Obter o número de quadros de uma animação.
  • Obtenha o tempo entre cada quadro da animação. (se for 30 qps, será 1/30 de segundo por quadro.)
  • Em seguida, crie um arquivo de som com exatamente o mesmo comprimento da animação.

Portanto, isso significa que se uma animação tiver 5 segundos, rodando a 30 qps, com um total de 150 quadros, o arquivo de som também terá 5 segundos. Se a animação tiver um som de "impacto" no 30º quadro, isso significa que o arquivo de som incluirá o som do impacto na marca de 1,0 segundo.

No final, iniciamos a animação e o efeito sonoro exatamente ao mesmo tempo e esperamos que os quadros e o som sejam sincronizados.

Parece que isso pode causar problemas quando os quadros são ignorados ou algo acontece durante a animação e o som é reproduzido um pouco cedo ou tarde demais, e fará com que o som e a animação fiquem fora de sincronia. Essa é a melhor abordagem ou geralmente há uma maneira melhor que eu simplesmente não estou vendo?

A resposta não precisa necessariamente ser para o Cocos2D especificamente se for conceitual, mas se houver uma solução específica para o cocos2d, eu adoraria ouvi-lo.

EDIT: Eu também percebi que, com esse método, se ajustarmos o número de quadros ou o tempo da animação posteriormente, DEVEMOS também voltar e alterar o arquivo de som. Isso soa como uma causa terrível de erro humano (esquecendo de atualizar os arquivos de som após a alteração da animação.) Espero que haja métodos melhores por aí.

Jamornh
fonte
é por isso que loops de jogo de tempo constante são úteis: então você não precisa se preocupar com a animação vai fora de sincronia
aberração catraca
@ratchetfreak Eu acredito que o cocos2d gerenciará corretamente o tempo da animação. Se eu criar uma animação e dizer ao cocos2d que eu quero exatamente 1/30 de segundo entre os quadros, ele garantirá isso e pulará os quadros se o desempenho não for bom o suficiente. Isso garante que a animação seja concluída no tempo certo (ou seja, em tempo constante.) Dado isso, você está dizendo que o método que descrevi acima é o caminho certo a seguir?
Jamornh

Respostas:

6

Faça isso através de eventos.

O feitiço começa é um evento . Comece a tocar o som para esse evento.

O inimigo atingido pelo feitiço também é um evento. Se o inimigo estiver mais longe e você jogar um dardo, por exemplo, você só toca o segundo som (dardo) quando o dardo atinge o alvo (se você considerar o lançamento como um feitiço).

Se você precisar amarrá-lo a um quadro (por exemplo, toque o som "explodir" daqui a 30 quadros, independentemente das taxas de quadros em tempo real )), a maneira mais fácil de fazer isso é usando retornos de chamada . Retornos de chamada são apenas "blocos de código que você planeja executar no futuro". Aqui está um exemplo do meu configurador de criação de retorno de chamada:

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

A TimedCallbacké apenas um invólucro em torno de um std::function(ou você pode usar um Objective-C ^{block}. O std::functioné executado quando seu quadro está ativo.

Outra maneira (menos global) é incluir eventos em sua animação . Portanto, se você precisar reproduzir sons específicos em quadros específicos de animação, armazene um map<int,Sound*>na Animationclasse. Em cada quadro da animação, verifique se há um correspondente Sound*para reproduzir nesse quadro.

Você também pode armazenar a map<int, std::function>, no Animationobjeto, o que permitiria retornar uma chamada functiondurante a animação.

bobobobo
fonte
Eu acho que você pode ter interpretado mal a pergunta. Seu método funcionaria para sons curtos destinados ao tipo "punch", "hit", "kick", "shoot" que não dura mais de uma fração de segundo em que a sincronização não é um problema (você pode simplesmente tocar o efeito sonoro "em evento ", como você sugeriu.) No entanto, com uma longa animação + som (por exemplo, armageddon, onde 5-6 meteoros caem do céu e atingem o chão em diferentes quadros, mas fazem parte de uma animação de sprite [como a fantasia final os faz] terá apenas um evento inicial, não um por meteorito) esse método não garante a sincronização, certo?
Jamornh
3
@Jamornh No seu exemplo de chuva de meteoros, você grava um evento: para cada meteoro começando a cair, para cada meteoro atingindo o chão, talvez um para os personagens que lutam lançando o feitiço. Usando esta solução, você pode alterar o número de meteoros e não ter problemas com o áudio.
Akaltar # 1/13
11
@ Jamornh Você também pode enfileirar um evento para "tocar som de explosão daqui a 30 quadros", a maneira mais fácil de fazer isso é usando uma função de retorno de chamada . Vou detalhar isso na minha resposta.
bobobobo
11
@ Jamornh A animação não precisa necessariamente ser modular para poder gravar vários eventos (em horários predeterminados). No seu editor de efeitos, você poderia dizer: toque o som do boom no quadro 32.
akaltar
11
Sim, isso funcionaria. Se você estivesse usando JSON, eu sugeriria uma estrutura de dados como { 'images':'sprite-%02d.png', 'beginRange':1, 'endRange':32, 'sounds':{ 0 : 'startSpell.wav', 30 : 'impact.wav' } }. Se o seu jogo está em estágio inicial e o tempo de compilação ainda é baixo, você pode começar codificando as estruturas de dados para ver se funciona.
bobobobo
1

A maneira como faço isso é criar ouvintes de eventos personalizados para minha aula de animação e controlá-los. portanto, se minha animação começou callback.start (); e comece meu som nesse método. se minha animação foi pausada, faça callback.pause (); e pausar o som. quando a animação termina, você callback.end (); e ter o som final também.

mas, para uma sincronização perfeita, eu chegaria ao ponto de contar os quadros de som e adormecer (pausar) meu som, se ele chegar muito longe e fazer o mesmo com a minha animação.

Eu nunca tive que fazer isso até hoje, porque a primeira sugestão satisfaz minhas necessidades muito bem por enquanto.

Jonathan Camarena
fonte
Você poderia explicar como contaria um "quadro" sonoro? Você quer dizer contar o número de milissegundos que passaram desde o início do som e executar uma contagem por cada intervalo predefinido?
Jamornh
não, não, eu literalmente quero dizer o quadro em que o som está. Eu não sei como você faz isso no cocos2D, mas no som java existem métodos para obter a contagem de quadros de sons e o "quadro" atual, há também uma maneira de definir o quadro atual para um determinado. tenho certeza de que, se você olhar para ele, poderá encontrar variáveis ​​semelhantes em sua interface de som, mas, como alguém apontou, é melhor que suas atualizações manejem o tempo de animação etc. para que você não faça esses hacks de sincronização.
Jonathan Camarena