On ne le répétera jamais assez : pour bien travailler, il faut de bons outils. Unity gère nativement énormément de choses, mais pour ce qui est du détail, de la petite touche qui va donner encore plus de caractère à vos productions, c’est à vous de jouer… et de créer des outils adaptés.
Il est très simple de jouer un son ou une musique avec un composant AudioSource attaché à un GameObject. Mais dès qu’il est question de fondus audio, il faut le faire soi même. Ce n’est pas complexe en soi, mais lorsqu’il faut le coder à chaque fois, ça devient moins sympa. Et si on se créait un petit outil qui gère les fondus sonores ?
Disclaimer
Cet article a été originalement écrit le 17/10/2014 sur mon précédent blog. Certaines informations présentées ici peuvent donc ne plus fonctionner telles quelles.
Mise en place
Pour ce tutoriel, nous n’aurons pas besoin de grand chose sur la scène. Rien de visuel à vrai dire, puisque c’est le son qui nous intéresse ici. Nous allons simplement créer un conteneur pour le script qui va piloter le fondu ainsi qu’un autre conteneur audio. Quelque chose comme ça :
Pensez bien à ajouter un AudioSource sur le GameObject Music !
Création du gestionnaire de fondus
L’objectif est de créer un script utilitaire statique. De cette manière, on ne crée pas de dépendances fortes entre les scripts et c’est également plus simple à utiliser. Pour effectuer les fondus, nous allons avoir besoin des coroutines, et donc d’un GameObject instancié sur la scène. Mais dans l’absolu, on se passerait bien d’avoir à s’en occuper à chaque fois. Notre script statique va donc gérer sa « sauce » en interne.
On va créer à la volée un GameObject qui sera le conteneur de nos coroutines et ce, en toute transparence depuis l’extérieur. On ne va en créer qu’un seul et le stocker dans une variable privée statique. Pour gérer l’instanciation de ce GameObject, on va passer par une propriété :
public class AudioFade : MonoBehaviour { private static AudioFade __instance; private static AudioFade Instance { get { if (__instance == null) { var o = new GameObject("AudioFade"); __instance = o.AddComponent<AudioFade>(); } return __instance; } } }
Voilà la structure de base. Maintenant que nous pouvons à tout moment créer une coroutine via le GameObject stocké en statique. Passons à l’écriture des méthodes de fondu. On va avoir deux méthodes publiques : « In » et « Out ». De cette manière on aura une utilisation vraiment transparente : AudioFade.In(…) et AudioFade.Out(…). Ces deux méthodes vont faire presque la même chose dans un sens différent. On peut donc mutualiser tout ça dans une seule méthode que l’on va appeler « FadeAudio ». Cette méthode ne sera pas accessible depuis l’extérieur :
private IEnumerator FadeAudio(GameObject container, float from, float to, float duration) { var startTime = Time.time; while (startTime + duration > Time.time) { container.audio.volume = Mathf.Lerp(from, to, (Time.time - startTime) / duration); yield return null; } }
Rien de bien méchant ici. La méthode prend en paramètre le GameObject conteneur de l’AudioSource sur lequel on veut faire le fondu, le volume de départ, le volume de fin et la durée du fondu. Cette méthode pourra donc être utilisée dans les deux sens comme expliqué plus haut (fade-in et fade-out).
Enfin, il ne nous reste plus que les méthodes statiques qui seront utilisées depuis l’extérieur :
public static void In(GameObject container, float to, float duration) { Instance.StartCoroutine(Instance.FadeAudio(container, 0F, to, duration)); } public static void Out(GameObject container, float from, float duration) { Instance.StartCoroutine(Instance.FadeAudio(container, from, 0F, duration)); }
La syntaxe interne peut paraître un peut barbare, mais depuis l’extérieur, c’est très simple à comprendre. Et c’est bien le but de ce tutoriel !
Remarque importante : un seul GameObject est suffisant pour gérer toutes les coroutines, y compris en pseudo-simultané (Unity est mono thread). Elles ne se marcheront pas dessus, n’ayez crainte. Vous pouvez avoir plusieurs fondus en même temps avec cette technique !
Pour aller plus loin
Vous pourriez avoir envie de gérer un callback afin de déclencher de nouvelles actions uniquement une fois que le fondu est terminé. L’adaptation du script pour prendre en compte ce cas est assez simple. Nous allons modifier la coroutine de fondu comme ceci :
private IEnumerator FadeAudio(GameObject container, float from, float to, float duration, GameObject target, string callback) { var startTime = Time.time; while (startTime + duration > Time.time) { container.audio.volume = Mathf.Lerp(from, to, (Time.time - startTime) / duration); yield return null; } if(target != null) { target.SendMessage(callback); } }
Nous avons là le GameObject cible du callback et le nom de la méthode à appeler. On passe par le système d’événements d’Unity et le tour est joué. Le test de nullité est là car cette méthode sera également utilisée dans le cas où on ne veut pas utiliser de callback !
Il faut ensuite ajuster les méthodes statiques existantes pour qu’elles prennent en compte le cas avec ou sans callback :
public static void In(GameObject container, float to, float duration) { Instance.StartCoroutine(Instance.FadeAudio(container, 0F, to, duration, null, null)); } public static void In(GameObject container, float to, float duration, GameObject target, string callback) { Instance.StartCoroutine(Instance.FadeAudio(container, 0F, to, duration, target, callback)); } public static void Out(GameObject container, float from, float duration) { Instance.StartCoroutine(Instance.FadeAudio(container, from, 0F, duration, null, null)); } public static void Out(GameObject container, float from, float duration, GameObject target, string callback) { Instance.StartCoroutine(Instance.FadeAudio(container, from, 0F, duration, target, callback)); }
Conclusion
Pour tester tout ça, créez un nouveau script « Manager » qui va faire un fade-in ou fade-out sur la musique de fond :
public class Manager : MonoBehaviour { private GameObject _music; void Start() { _music = GameObject.Find("Music"); } void OnGUI() { if (GUILayout.Button("Fade-in")) { AudioFade.In(_music, 1F, 3F); } if(GUILayout.Button("Fade-in avec callback")) { AudioFade.In(_music, 1F, 3F, gameObject, "FadeInCallback"); } if (GUILayout.Button("Fade-out")) { AudioFade.Out(_music, 1F, 3F); } if (GUILayout.Button("Fade-out avec callback")) { AudioFade.Out(_music, 1F, 3F, gameObject, "FadeOutCallback"); } } public void FadeInCallback() { print("FadeInCallback"); } public void FadeOutCallback() { print("FadeOutCallback"); } }
Attachez un asset audio à l’AudioSource de Music, et vous pouvez tester.
Simple, efficace et concis. Fini le code spaghetti ! Voilà, votre outil de fondu audio est prêt. Vous pouvez l’utiliser dès maintenant dans vos projets. Le fonctionnement est très similaire à iTween : les coroutines sont masquées afin de simplifier l’écriture de code sans pour autant le limiter. D’ailleurs, on pourrait également utiliser le principe de Hash vu dans un tutoriel précédent pour gérer les paramètres multiples !