using System.Collections; using System.Collections.Generic; using UnityEngine; /// /// Gère le passage d'une source audio à une autre avec un fondu (ambiances, musiques...). /// /// Utilisation dans un UnityEvent (bouton, Collider Event System, etc.) : /// - Play (AudioSource) : fondu vers cette source (vide = fondu vers le silence) /// - Stop () : fondu vers le silence /// - SetNextFadeDuration (float) : durée spéciale pour la prochaine transition seulement, /// à placer AVANT Play dans la liste de l'event /// /// Chaque source monte jusqu'au volume réglé dans son AudioSource. /// Chaque AudioSource doit avoir Loop coché et Play On Awake décoché. /// Aucun Animator ne doit animer leur volume, sinon il écrase les valeurs du script. /// public class AudioCrossfader : MonoBehaviour { [Tooltip("Optionnel : source jouée en fondu au lancement de la scène")] public AudioSource startSource; [Tooltip("Durée d'une transition, en secondes")] public float fadeDuration = 3f; const float DynamicRange = 60f; AudioSource current; AudioSource fadingOut; float nextFadeDuration = -1f; // Volume configuré de chaque AudioSource, mémorisé avant que les fondus ne le modifient readonly Dictionary baseVolumes = new(); void Start() { if (startSource != null) Play(startSource); } public void SetNextFadeDuration(float duration) { nextFadeDuration = duration; } public void Play(AudioSource next) { float duration = nextFadeDuration >= 0f ? nextFadeDuration : fadeDuration; nextFadeDuration = -1f; Play(next, duration); } public void Stop() { Play(null); } void Play(AudioSource next, float duration) { if (next == current) return; StopAllCoroutines(); if (fadingOut != null && fadingOut != next) fadingOut.Stop(); fadingOut = current; current = next; StartCoroutine(Crossfade(fadingOut, current, duration)); } IEnumerator Crossfade(AudioSource oldSource, AudioSource newSource, float duration) { float target = newSource != null ? BaseVolume(newSource) : 0f; if (newSource != null && !newSource.isPlaying) { newSource.volume = 0f; newSource.Play(); } float oldStart = oldSource != null ? oldSource.volume : 0f; float newStart = newSource != null ? newSource.volume : 0f; float t = 0f; while (t < duration) { t += Time.unscaledDeltaTime; float progress = Mathf.Clamp01(t / duration); if (oldSource != null && newSource != null) { // Crossfade à puissance constante : pas de creux sonore au milieu float angle = progress * Mathf.PI * 0.5f; newSource.volume = Mathf.Lerp(newStart, target, Mathf.Sin(angle)); oldSource.volume = oldStart * Mathf.Cos(angle); } else if (newSource != null) { // Fondu depuis le silence newSource.volume = ToLinear(Mathf.Lerp(ToPerceived(newStart), ToPerceived(target), progress)); } else if (oldSource != null) { // Fondu vers le silence oldSource.volume = ToLinear(Mathf.Lerp(ToPerceived(oldStart), 0f, progress)); } yield return null; } if (oldSource != null) oldSource.Stop(); if (newSource != null) newSource.volume = target; fadingOut = null; } float BaseVolume(AudioSource source) { if (!baseVolumes.TryGetValue(source, out float baseVolume)) { baseVolume = source.volume; baseVolumes[source] = baseVolume; } return baseVolume; } static float ToPerceived(float linearVolume) { if (linearVolume <= 0f) return 0f; return Mathf.Clamp01(1f + 20f * Mathf.Log10(linearVolume) / DynamicRange); } static float ToLinear(float perceived) { if (perceived <= 0f) return 0f; return Mathf.Pow(10f, (perceived - 1f) * DynamicRange / 20f); } }