Was ist die Standardmethode zum Synchronisieren von Soundeffekten mit Sprite-Animationen?

8

Nehmen wir eine Situation, in der Sie ein Rollenspiel mit Zaubersprüchen haben und jede Zauberanimation eine andere Anzahl von Frames hat und sehr unterschiedliche Anforderungen an Soundeffekte stellt. Nehmen wir an, dass jedem Zauber nur eine fortlaufende Animation zugeordnet ist (im Gegensatz zu mehreren modularen Teilen, die zur Erstellung einer vollständigen Animation verwendet werden), wie bei den alten 16-Bit-Final-Fantasy-Spielen.

Der einzige Weg, den ich mir vorstellen kann, um sicherzustellen, dass die Sounds und Animationen synchronisiert werden, ist:

  • Ermitteln Sie die Anzahl der Frames einer Animation.
  • Holen Sie sich die Zeit zwischen jedem Bild der Animation. (Wenn es 30 fps ist, ist es 1/30 Sekunde pro Frame.)
  • Erstellen Sie dann eine Audiodatei, die genau so lang ist wie die Animation.

Das heißt, wenn eine Animation 5 Sekunden lang ist und mit 30 fps und insgesamt 150 Bildern ausgeführt wird, ist die Audiodatei ebenfalls 5 Sekunden lang. Wenn die Animation im 30. Bild einen "Aufprall" -Sound haben soll, bedeutet dies, dass die Sounddatei den Aufprallton bei der 1,0-Sekunden-Marke enthält.

Am Ende starten wir die Animation und den Soundeffekt genau zur gleichen Zeit und hoffen, dass die Frames und der Sound synchronisiert werden.

Dies scheint Probleme zu verursachen, wenn Frames übersprungen werden oder während der Animation etwas passiert und der Sound etwas zu früh oder zu spät abgespielt wird und der Sound und die Animation nicht mehr synchron sind. Ist das der beste Ansatz oder gibt es normalerweise einen besseren Weg, den ich einfach nicht sehe?

Die Antwort muss nicht unbedingt speziell für Cocos2D sein, wenn es konzeptionell ist, aber wenn es eine spezifische Lösung für cocos2d gibt, würde ich sie gerne hören.

BEARBEITEN: Mir ist nur auch klar, dass wir bei dieser Methode, wenn wir die Anzahl der Frames oder das Timing der Animation später anpassen, auch zurückgehen und die Sounddatei ändern müssen. Dies klingt nach einer schrecklichen Ursache für menschliches Versagen (das Vergessen, Sounddateien nach dem Ändern der Animation zu aktualisieren). Ich hoffe, es gibt bessere Methoden.

Jamornh
quelle
Aus diesem Grund sind Spielschleifen mit konstanter Zeit praktisch: Dann müssen Sie sich keine Sorgen mehr machen, dass die Animation nicht mehr synchron ist
Ratschenfreak
@ratchetfreak Ich glaube, cocos2d wird das Timing der Animation korrekt verwalten. Wenn ich eine Animation erstelle und cocos2d mitteile, dass zwischen den Frames genau 1/30 Sekunde liegen soll, wird dies sichergestellt und Frames übersprungen, wenn die Leistung nicht gut genug ist. Dies stellt sicher, dass die Animation zum richtigen Zeitpunkt (dh in konstanter Zeit) abgeschlossen wird. Wollen Sie angesichts dessen sagen, dass die oben beschriebene Methode der richtige Weg ist?
Jamornh

Antworten:

6

Mach es über Events.

Der Zauberbeginn ist ein Ereignis . Starten Sie den Sound für dieses Event.

Ein Feind, der vom Zauber getroffen wird, ist ebenfalls ein Ereignis. Wenn der Feind weiter entfernt ist und Sie beispielsweise einen Pfeil werfen, spielen Sie den zweiten Ton (Pfeilschlag) erst, wenn der Pfeil das Ziel erreicht (wenn Sie Werfen als Zauber betrachten).

Wenn Sie es an einen Frame binden müssen (spielen Sie beispielsweise den "Explode" -Sound in 30 Frames unabhängig von den Echtzeit-Frameraten ab ), verwenden Sie am einfachsten Rückrufe . Rückrufe sind nur "Codeblöcke, die Sie in Zukunft ausführen möchten". Hier ist ein Beispiel für meinen Rückruferstellungs-Setter:

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

A TimedCallbackist nur ein Wrapper um a std::function(oder Sie könnten ein Objective-C verwenden ^{block}. Das std::functionwird ausgeführt, wenn sein Frame aktiv ist.

Eine andere Möglichkeit (weniger global) besteht darin, Ereignisse in Ihre Animation aufzunehmen . Wenn Sie also häufig bestimmte Sounds auf bestimmten Animationsframes abspielen müssen, speichern Sie a map<int,Sound*>in der AnimationKlasse. Überprüfen Sie in jedem Frame der Animation, ob Sound*für diesen Frame eine entsprechende Wiedergabe vorhanden ist .

Sie können auch ein Objekt map<int, std::function>im AnimationObjekt speichern , um functionwährend der Animation ein a zurückzurufen.

Bobobobo
quelle
Ich denke, Sie haben die Frage möglicherweise leicht falsch interpretiert. Ihre Methode funktioniert für kurze Sounds, die für den Typ "Punch", "Hit", "Kick", "Shoot" gedacht sind und nicht länger als den Bruchteil einer Sekunde dauern, wenn die Synchronisation kein Problem darstellt (Sie können einfach den Soundeffekt spielen " Ereignis "wie Sie vorgeschlagen haben.) Jedoch mit einer langen Animation + Sound (dh Armageddon, wo 5-6 Meteore vom Himmel fallen und in verschiedenen Frames auf den Boden treffen, aber Teil einer Sprite-Animation sind [wie die endgültige Fantasie sie macht] wird nur 1 Startereignis haben, nicht eines pro Meteorit) Diese Methode gewährleistet keine Synchronisation, oder?
Jamornh
3
@Jamornh In Ihrem Meteorschauer-Beispiel schießen Sie ein Ereignis: Für jeden Meteor, der zu fallen beginnt, für jeden Meteor, der auf den Boden trifft, vielleicht einen für die Charaktere, die Schwierigkeiten haben, den Zauber zu wirken. Mit dieser Lösung können Sie sogar die Anzahl der Meteore ändern und haben keine Probleme mit Audio.
Akaltar
1
@Jamornh Sie können auch ein Ereignis für "Explosionssound in 30 Bildern abspielen" in die Warteschlange stellen. Der einfachste Weg, dies zu tun, ist die Verwendung einer Rückruffunktion . Ich werde dies in meiner Antwort detailliert beschreiben.
Bobobobo
1
@Jamornh Die Animation muss nicht unbedingt modular aufgebaut sein, um mehrere Ereignisse (zu festgelegten Zeiten) aufnehmen zu können. In Ihrem Effekt-Editor können Sie einfach sagen, spielen Sie Boom-Sound bei Frame 32.
Akaltar
1
Ja, das würde funktionieren. Wenn Sie JSON verwenden, würde ich eine Datenstruktur wie vorschlagen { 'images':'sprite-%02d.png', 'beginRange':1, 'endRange':32, 'sounds':{ 0 : 'startSpell.wav', 30 : 'impact.wav' } }. Wenn sich Ihr Spiel in einem sehr frühen Stadium befindet und die Kompilierungszeit noch kurz ist, können Sie zunächst die Datenstrukturen fest codieren, um festzustellen, ob es funktioniert.
Bobobobo
1

So mache ich es, mache benutzerdefinierte Ereignis-Listener für meine Animationsklasse und lasse sie meinen Sound steuern. also wenn meine Animation gestartet wurde callback.start (); und starte meinen Sound in dieser Methode. Wenn meine Animation angehalten wurde, führen Sie callback.pause () aus. und pausiere den Ton. Wenn die Animation beendet ist, rufen Sie call.end () auf. und habe auch das Tonende.

aber für eine perfekte Synchronisation würde ich so weit gehen, Soundframes zu zählen und meinen Sound zu schlafen (anzuhalten), wenn es zu weit geht und dasselbe für meine Animation tun.

Ich musste das bis heute nie tun, weil der erste Vorschlag meine Bedürfnisse vorerst ganz gut befriedigt.

Jonathan Camarena
quelle
Könnten Sie näher erläutern, wie Sie einen Sound "Frame" zählen würden? Meinen Sie damit, die Anzahl der Millisekunden zu zählen, die seit dem Start des Sounds vergangen sind, und eine Zählung für jedes vordefinierte Intervall durchzuführen?
Jamornh
nein nein, ich meine ganz wörtlich den Rahmen, auf dem der Sound ist. Ich weiß nicht, wie Sie es in cocos2D machen, aber in Java-Sound gibt es Methoden, um die Anzahl der Sounds Framecount und den aktuellen "Frame" zu erhalten. Es gibt auch eine Möglichkeit, den aktuellen Frame auf einen bestimmten Wert zu setzen. Ich bin mir sicher, wenn Sie sich das ansehen, könnten Sie ähnliche Variablen in Ihrer Sound-Oberfläche finden, aber wie jemand darauf hingewiesen hat, ist es am besten, wenn Ihre Updates die Animationszeit usw. verarbeiten. damit Sie keine solchen Synchronisierungs-Hacks machen müssen.
Jonathan Camarena