Introduction
Cet article est le premier d'une série de trois articles techniques de Jater (Ruohao) Xu présentant le travail réalisé sur Reverse Collapse : Code Name Bakery. Ici, il explore comment Wwise a été utilisé pour contrôler les cinématiques du jeu et met en avant l'utilisation du plugin Time Stretch de Wwise. Restez à l'affût pour les parties 2 et 3 à venir dans les prochaines semaines !
Jater (Ruohao) Xu a récemment publié un autre article sur le blog d'Audiokinetic intitulé « Reverse Collapse : Code Name Bakery | The Important Role of Wwise in Remote Collaboration », où lui et Paul Ruskay partagent leur processus de gestion de la conception sonore pour les personnages et animations personnalisées, la création de systèmes musicaux interactifs pour le jeu, et bien plus encore.
Utiliser Wwise pour contrôler des cinématiques temps réel avec le plugin Time Stretch de Wwise
Série d'articles techniques | Partie 1
Pour résoudre les problèmes potentiels de synchronisation audio/vidéo liés à des situations comme les changements rapides entre fenêtres (Alt-Tab) ou les ralentissements causés par des limitations de performance, nous avons implémenté un code de synchronisation audio/vidéo basé sur l'horloge de Wwise, plutôt que de nous reposer uniquement sur l'horloge du jeu dans Reverse Collapse. Nous avons également poussé l'intégration un peu plus loin afin de prendre en charge les effets de ralenti et d'accélération, indispensables pour mettre en avant certains moments cinématiques du jeu. Pour cela, nous avons utilisé le plugin polyvalent de Wwise, Time Stretch. Ces approches se sont révélées efficaces pour atténuer les problèmes mentionnés. Il est intéressant de noter que, bien que le terme « cinématique » couvre plusieurs moteurs de jeu comme la Timeline de Unity et le Sequencer d'Unreal, nos exemples seront présentés dans un environnement Unity, puisque Reverse Collapse est développé avec ce moteur.
Les bases
Commençons par les fondamentaux : la logique sous-jacente qui permet à cette approche de fonctionner est d'une simplicité rafraîchissante et s'applique à différents moteurs de jeu.
Si le temps de lecture audio est plus lent que celui de la vidéo, mettez la vidéo en pause.
Si le temps de lecture audio est plus rapide que celui de la vidéo, faites avancer la vidéo jusqu'à atteindre le temps de lecture audio.
Si les deux valeurs sont identiques, ne faites rien : la synchronisation est déjà parfaite.
Puisque le jeu utilise Unity, un pseudo-algorithme en C# illustrera ces principes avec le code suivant :
private void AudioVideoSyncLogic()
{
if (audioTime < videoTime)
{
video.Pause();
}
else if (audioTime > videoTime)
{
video.Play();
}
else
{
continue;
}
}
Avec ce premier extrait de code, nous pouvons intégrer cette fonction dans la fonction Update() par défaut de Unity afin de garantir son exécution à chaque image pendant la lecture de la cinématique. Il est important de ne pas oublier de modifier la méthode de mise à jour de la Timeline, en passant de la valeur par défaut « Game Time » à la valeur « Manual », afin de garantir son bon fonctionnement.

L'image ci-dessus montre la configuration spécifique du script RCAudioClockSync.cs. Dans ce cas, nous pouvons également assigner un Event ou une ambiance que nous souhaitons jouer en tant qu'entrée sous forme de chaîne de caractères (string).
Résolution des problèmes
Lors de l'intégration du code décrit ci-dessus dans le script, plusieurs problèmes peuvent survenir, nécessitant des révisions et des améliorations pour garantir que la cinématique reste jouable dans les différents scénarios interactifs du jeu. Les problèmes les plus courants à résoudre sont les suivants :
- Récupération du temps de lecture audio en cours : s'assurer que le script suit avec précision la progression de l'audio.
- Synchronisation de la vidéo et de l'audio lorsque la cinématique est stoppée : garantir que la vidéo s'arrête correctement en même temps que l'audio lorsque la cinématique se termine.
- Implémentation de méthodes de sécurité : fournir des garanties en cas de mauvaise configuration de la cinématique.
- Fonctionnalités d'assurance qualité pour la synchronisation audio : développer des fonctions supplémentaires que l'équipe de test pourrait utiliser pour vérifier la synchronisation audio par rapport à une configuration par défaut non synchronisée.
En traitant ces problématiques, nous pouvons renforcer la robustesse et la fiabilité du script dans la gestion des séquences cinématiques du jeu.
Récupération du temps de lecture audio en cours
Wwise propose une fonction très utile pour les programmeurs. Il s'agit de GetSourcePlayPosition() (Lien : GetSourcePlayPosition (audiokinetic.com)).
GetSourcePlayPosition() renvoie la durée de lecture audio actuelle pour un ID de lecture donné. L'intégration de Wwise dans Unity propose une version plus accessible de cette fonction par rapport à son appel original en C++, ce qui est très pratique :
public static AKRESULT GetSourcePlayPosition(uint in_PlayingID, out int out_puPosition, bool in_bExtrapolate)
Pour utiliser cette méthode, il faut d'abord assigner le résultat de l'appel au playingID défini. Lorsqu'un résultat Ak_Success est obtenu, le temps audio actuel est transmis au modificateur de paramètre, out_puPosition. Si un Ak_Fail survient pour une raison quelconque, la valeur retournée sera -1. Le pseudo-code ci-dessous illustre cette solution. Notez que la partie de récupération de la variable playingID est omise dans l'exemple.
public int GetSourcePlaybackPositionInMilliseconds(uint playingID, bool extrapolated)
{
int returnPos = 0;
AKRESULT returnResult = AkSoundEngine.GetSourcePlayPosition(playingID, out returnPos, extrapolated);
if (returnResult == AKRESULT.AK_Success)
{
return returnPos;
}
else
{
return -1;
}
}
Ainsi, en appelant GetSourcePlaybackPositionInMilliseconds() et en assignant la valeur de retour à la variable audioTime, nous pouvons récupérer le temps de lecture audio en cours en temps réel. N'oubliez pas de gérer la situation où la variable obtient -1 et d'ignorer la logique de synchronisation audio-vidéo.
Synchronisation de la vidéo et de l'audio lorsque la cinématique est stoppée
Rappelez-vous la fonction AudioVideoSyncLogic() mentionnée plus haut. Lorsque l'audio se termine, la vidéo doit se synchroniser de manière transparente et poursuivre sa dernière action, c'est-à-dire conserver son état actuel. Idéalement, si l'audio et la vidéo se terminent simultanément, les deux doivent s'arrêter. Par exemple, si la vidéo est en pause lorsque l'audio se termine, elle restera en pause, ce qui risque de bloquer le jeu. Inversement, si le fichier audio comporte quelques secondes de silence à la fin, la vidéo continuera à être lue, ce qui peut provoquer des artefacts visuels jusqu'à l'arrêt de l'audio. Pour éviter d'introduire des bugs susceptibles de perturber le jeu, nous devons anticiper et résoudre ces problèmes dans notre implémentation.
Parfois, ce problème peut être résolu en supprimant le game object de la cinématique lui-même, mais cette approche dépend de nombreux facteurs qui échappent au contrôle du département audio. Nous pouvons mettre en place une solution universelle et explicite en arrêtant directement la vidéo lorsque l'audio arrive à sa fin. Pour ce faire, il suffit d'ajouter le pseudo-code ci-dessous à la fin de la fonction AudioVideoSyncLogic(). Notez que la partie de récupération de la variable videoDuration est omise dans l'exemple :
if (audioTime > videoDuration)
{
video.Stop();
}
Implémentation de méthodes de sécurité
Durant le développement, nous rencontrons souvent des situations où les cinématiques ont un son temporaire ou sont dépourvues de son. Concernant le gameplay, même si cela est rare, il peut arriver que certaines méthodes ne s'exécutent pas, ce qui entraîne un blocage de la vidéo et fige le jeu. Ainsi, nous ne pouvons pas nous reposer uniquement sur ces méthodes pour garantir une stabilité logicielle. Une méthode de sécurité intégrée doit être mise en place lorsque ces méthodes échouent, que ce soit lors dans l'éditeur lors de la phase de développement d'une cinématique ou pendant l'exécution du jeu. Cette approche diffère de l'utilisation classique de la méthode de mise à jour par défaut de Unity, en particulier lors de l'exécution du jeu, car les joueurs ne doivent pas avoir le contrôle des méthodes de mise à jour utilisées. Passer à la méthode de mise à jour par défaut en plein milieu d'une cinématique pourrait entraîner des effets imprévus.
La fonction ci-dessous illustre une méthode de mise à jour manuelle pour les cinématiques, dans ce cas, une méthode de repli (fallback). Notez que la récupération de la variable videoTime est omise dans l'exemple :
private void AudioVideoSyncFallbackLogic()
if (videoTime < videoDuration)
{
videoTime += deltaTime
video.Play();
}
else
{
video.Stop();
}
}
AudioVideoSyncLogic() s'exécutera lorsqu'un playingID et un temps audio valides auront tous deux été obtenus. Ainsi, AudioVideoSyncFallbackLogic() sera utile dans toutes les autres situations.
En résumé, dans la méthode update ou tick, nous devrions inclure les segments de code et les appels de fonction suivants, en utilisant ce pseudo-code d'exemple :
private void LateUpdate()
{
int audioTime = GetSourcePlaybackPositionInMilliseconds((uint)playingID, true);
if (playingID != -1 && audioTime != -1)
{
AudioVideoSyncLogic();
}
else
{
AudioVideoSyncFallbackLogic();
}
}
Notez que nous utilisons ici LateUpdate() afin de nous assurer que toutes les animations et autres éléments de la cinématique (par exemple, la Timeline Unity) sont entièrement mis à jour avant de synchroniser l'audio et la vidéo. Cette approche permet d'obtenir de meilleurs résultats dans les rares cas où un élément ne se mettrait pas correctement à jour. Bien que la fonction standard Update() puisse être utilisée, la fonction LateUpdate() est recommandée pour les timelines comportant un grand nombre de scripts et de caractéristiques visuelles personnalisées. L'implémentation peut varier d'un projet à l'autre, et il peut être utile d'ajouter des paramètres d'entrée personnalisés aux fonctions afin de référencer une Timeline jouable ou d'autres ressources.
Fonctionnalités d'assurance qualité pour la synchronisation audio
Nous avons ajouté une option pour notre équipe d'assurance qualité afin de faciliter la comparaison A/B des vidéos en cas de bugs liés aux cinématiques. Cette option permet de désactiver la synchronisation audio-vidéo et sa méthode de repli, en revenant aux méthodes de mise à jour du temps de jeu par défaut avant l'enregistrement de la vidéo. De cette manière, nous pouvons facilement déterminer si les bugs sont causés par la nouvelle fonction de synchronisation audio-vidéo ou par les assets ou scripts présents dans la Timeline.
Effets de ralenti et d'accélération à l'aide du plugin Time Stretch de Wwise
En suivant toutes les étapes décrites ci-dessous, vous devriez obtenir un système de synchronisation audio-vidéo performant pour de nombreux jeux. Cependant, chaque projet peut présenter des cas particuliers nécessitant des fonctions personnalisées supplémentaires. Dans Reverse Collapse, par exemple, nous devons ajouter à ce système de synchronisation le support des fonctions de ralentissement et d'accélération afin de mettre en valeur certains moments clés des cinématiques.
Pour atteindre cet objectif tout en conservant la synchronisation audio-vidéo, ces nouvelles fonctionnalités doivent être intégrées à l'infrastructure existante. C'est là qu'intervient le plugin Time Stretch de Wwise (Lien : Time Stretch (Time Stretch (audiokinetic.com))). Ce plugin permet d'ajuster la vitesse de lecture des voix audio dans Wwise sans en modifier la hauteur, ce qui le rend idéal dans notre cas.
Pour configurer Time Stretch, accédez à l'onglet des effets de l'Actor-Mixer, du Container ou de l'Audio Source concernée dans la hiérarchie du Project Explorer. Dans notre cas, nous l'appliquons à l'Actor-Mixer des SFX des cinématiques. Cet Actor-Mixer contrôle globalement la piste des SFX des scènes cinématiques, ce qui signifie que toute modification apportée à Time Stretch s'appliquera à l'ensemble des SFX de cinématiques inclus sous cet Actor-Mixer. Cette configuration s'applique à toutes les cinématiques du jeu, qui sont jouées une par une. (Ce sera la piste utilisée pour contrôler la Timeline de Unity).

Laissons le plugin avec ses paramètres par défaut pour la plupart de ses propriétés, mais voyons comment modifier la propriété Time Stretch via un RTPC qui sera implémenté dans le code.


La fonction Time Stretch fournie par Wwise offre une plage de 25 à 1600 sur l'axe des Y, selon la documentation officielle de Wwise. Cette valeur représente le pourcentage de la durée du son original, où 25 signifie une lecture quatre fois plus rapide, et 1600 % une lecture 16 fois plus lente. Pour simplifier les calculs, nous avons créé un RTPC (Real-Time Parameter Control) avec une plage de 0,25 à 16 sur l'axe des X. Cette valeur représente le multiplicateur inversé de la vitesse de lecture réelle de l'audio original. En divisant 1 par ce nombre, on obtient le multiplicateur utilisable. Par exemple, 1 divisé par 1/4 donne 4, ce qui indique une lecture quatre fois plus rapide, tandis que 1 divisé par 16 donne 0,0625, ce qui représente une lecture 16 fois plus lente.
Le seul inconvénient de cette configuration est la limitation du multiplicateur d'étirement du temps à une plage comprise entre 0,25 et 16. Si nous atteignons la limite supérieure ou inférieure, nous ne pouvons pas dépasser ces valeurs pour obtenir une lecture plus rapide ou plus lente. Cependant, pour notre cas spécifique, et probablement pour de nombreux autres jeux, cette plage est largement suffisante pour couvrir toutes les situations de mouvements lents ou rapides. Dans Reverse Collapse, nous limitons le multiplicateur à une plage de 0,25 à 4, conformément aux demandes de l'équipe de conception et d'animation du jeu.
Dans cet exemple, nous allons créer une petite fonction wrapper pour extraire la valeur de sortie du modificateur de paramètre et l'appliquer dans la zone où nous avons l'intention d'utiliser cette fonctionnalité.
public float GetGlobalRTPC(string rtpcName)
{
int rtpcType = 1;
float acquiredRtpcValue = float.MaxValue;
AkSoundEngine.GetRTPCValue(rtpcName, null, 0, out acquiredRtpcValue, ref rtpcType);
if(acquiredRtpcValue >= 0.25f && acquiredRtpcValue <= 16.0f)
{
return acquiredRtpcValue;
}
else
{
return 1.0f;
}
}
Outre la définition globale du RTPC, la fonction ci-dessus garantit également que si des valeurs incorrectes sont détectées, elle ignorera la modification du RTPC et réinitialisera la valeur à 1,0f, qui est la valeur par défaut.
Enfin, il nous reste à ajouter cette fonction dans le segment de code où la logique de synchronisation audio-vidéo est exécutée. Insérez maintenant la ligne suivante entre l'exécution de la logique de lecture de la cinématique et celle de l'arrêt de la cinématique après la fin de la vidéo, et affectez la valeur calculée à la variable timeScale de Unity.
Time.timeScale = 1.0f / GetGlobalRTPC(“TimelineTimeDilation”);
Avec toutes les étapes présentées ci-dessus, nous avons réussi à implémenter l'utilisation de Wwise pour piloter des timelines de cinématiques dans le jeu. De plus, nous avons la possibilité d'utiliser des fonctionnalités telles que le ralenti (jusqu'à 16x) et l'accéléré (jusqu'à 4x) pour n'importe quelle durée et temps d'image, le tout à portée de main. Nous pouvons désormais définir librement le RTPC dans la fenêtre Timeline de Unity.

L'image ci-dessus illustre un exemple de ralenti de 0,1x entre les images 1071 et 1078. Dans notre implémentation, le multiplicateur doit être réinitialisé manuellement à 1 en créant un autre RTPC après l'image de fin de ralenti, dans ce cas, l'image 1079.
Pour le résultat final, les sons et les images ralentissent correctement, pilotés par Wwise, ce qui élimine les problèmes de synchronisation audiovisuelle. Cette approche permet également aux concepteurs sonores de gagner du temps en réduisant la nécessité de créer des éléments audio spécifiques pour les passages en ralenti ou en accéléré. Et pour les animateurs, plus besoin de peaufiner les courbes de ralenti et d'accéléré.
Voici un clip vidéo pour illustrer le résultat final. Il y a de nombreux arrêts sur image intentionnels tout au long de la vidéo pour mettre en avant cette fonctionnalité. Le ralenti provoqué par Time Stretch peut également être entendu dans le clip à 0:23.
Avertissement : Les extraits de code utilisés dans cet article sont des versions génériques reconstituées et destinées uniquement à des fins d'illustration. La logique sous-jacente a été vérifiée pour fonctionner correctement, les appels et fonctions API spécifiques au projet ont été omis des exemples en raison de restrictions potentielles en matière de droits d'auteur.

Commentaires