using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; namespace OCES.Audio { /// /// 与Transition无关的音量/生命周期管理逻辑 /// public class ChannelFader { readonly MusicContainerPlayer m_player; readonly MonoBehaviour m_coroutineHost; public ContainerPlayHandle CurrentHandle { get; private set; } public uint CurrentContainerId { get; private set; } public float CurrentVolume { get; private set; } public ChannelFader(MusicContainerPlayer player, MonoBehaviour coroutineHost) { this.m_player = player; this.m_coroutineHost = coroutineHost; } void StartNew(uint containerId, float startVolume) { CurrentContainerId = containerId; CurrentVolume = startVolume; CurrentHandle = this.m_player.Play(containerId); } public void StopCurrent() { StopHandle(CurrentHandle); CurrentHandle = null; CurrentContainerId = 0; } void StopHandle(ContainerPlayHandle handle) { if (handle == null) return; this.m_player.Stop(handle); } /// /// 淡出分支:fire-and-forget,由调用方 StartCoroutine /// internal IEnumerator FadeOutBranch(ContainerPlayHandle outgoingHandle, float outgoingVolume, ITransitionConfig transition) { if (outgoingHandle == null) yield break; if (transition?.FadeOutOffset > 0f) { //Debug.Log($"Waiting for {transition.FadeOutOffset} to fade out."); yield return new WaitForSeconds(transition.FadeOutOffset); } if (transition?.FadeOutTime > 0f ) yield return this.m_coroutineHost.StartCoroutine( FadeOut(outgoingHandle, outgoingVolume, transition.FadeOutTime)); else StopHandle(outgoingHandle); } /// /// 淡入分支:等待 FadeInOffset 后启动新音乐并淡入。 /// 主协程 yield return 此分支,以便 DoTransition 在新音乐就绪后才结束。 /// internal IEnumerator FadeInBranch(uint newContainerId, ITransitionConfig transition, Action onContainerStarted = null) { if (transition?.FadeInOffset > 0f) { //Debug.Log($"Waiting {transition.FadeInOffset} to fade in."); yield return new WaitForSeconds(transition.FadeInOffset); } if (newContainerId == 0) { CurrentHandle = null; CurrentContainerId = 0; yield break; } float startVolume = transition?.FadeInTime > 0f ? 0f : 1f; StartNew(newContainerId, startVolume); onContainerStarted?.Invoke(); if (transition?.FadeInTime > 0f) { yield return this.m_coroutineHost.StartCoroutine( FadeIn(CurrentHandle, transition.FadeInTime)); } } IEnumerator FadeOut(ContainerPlayHandle handle, float fromVolume, float duration) { //Debug.Log($"Fading out in {duration} seconds."); if (handle == null || handle.Cancelled) yield break; float elapsed = 0f; List sources = new(); handle.CollectActiveSources(sources); while (elapsed < duration) { if (handle.Cancelled) break; elapsed += Time.deltaTime; // 为每个 source 创建 DOFade(仅创建一次) sources.Clear(); handle.CollectActiveSources(sources); foreach (AudioSource src in sources) { if (!src) continue; if (DOTween.IsTweening(src)) continue; src.volume = fromVolume; src.DOFade(0f, duration - elapsed).SetEase(Ease.Linear); } yield return null; } // 确保最终状态 sources.Clear(); handle.CollectActiveSources(sources); foreach (AudioSource src in sources) if (src) src.volume = 0f; StopHandle(handle); } IEnumerator FadeIn(ContainerPlayHandle handle, float duration) { if (handle == null || handle.Cancelled) yield break; List sources = new(); float elapsed = 0f; while (elapsed < duration) { if (handle.Cancelled) yield break; elapsed += Time.deltaTime; // 每帧收集当前活跃的 sources,并为新加入的 source 创建 tween sources.Clear(); handle.CollectActiveSources(sources); foreach (AudioSource src in sources) { if (!src) continue; // 如果这个 source 还没被 tween 过,则创建一个 DOFade if (DOTween.IsTweening(src)) continue; src.volume = 0f; src.DOFade(1f, duration - elapsed).SetEase(Ease.Linear); } yield return null; } // 确保最终音量 sources.Clear(); handle.CollectActiveSources(sources); foreach (AudioSource src in sources) if (src) src.volume = 1f; CurrentVolume = 1f; } } }