using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using Random = UnityEngine.Random; namespace OCES.Audio { /// /// 负责递归播放一个 MusicContainer 树。 /// 被 MusicChannelPlayer 和 AmbienceChannelPlayer 共同使用。 /// 不持有状态机逻辑,只负责"把这个 Container 播完"。 /// class MusicContainerPlayer { readonly MusicContainerConfig m_containerConfig; readonly MusicSegmentConfig m_segmentConfig; readonly AudioSourcePool m_pool; readonly MonoBehaviour m_coroutineHost; UnityEngine.Audio.AudioMixerGroup m_mixerGroup; /// /// 开始播放Container时触发 音乐回调系统 /// container本身,继承来的bpm,进入时刻的dspTime /// internal event Action OnContainerEntered; internal event Action OnBlendError; // Sequence Step 模式的全局游标,key = containerId readonly Dictionary m_sequenceStepIndex = new(); // Random 模式的不放回历史,key = containerId readonly Dictionary> m_randomHistory = new(); public MusicContainerPlayer( MusicContainerConfig containerConfig, MusicSegmentConfig segmentConfig, AudioSourcePool pool, MonoBehaviour coroutineHost) { this.m_containerConfig = containerConfig; this.m_segmentConfig = segmentConfig; this.m_pool = pool; this.m_coroutineHost = coroutineHost; } // ───────────────────────────────────────────── // 公开入口 // ───────────────────────────────────────────── /// /// 开始播放指定 Container,播放完毕后调用 onFinished。 /// 返回可用于外部停止的句柄。 /// internal ContainerPlayHandle Play(uint containerId, Action onFinished = null, float inheritedBpm = 0f) { MusicContainer container = this.m_containerConfig.QueryById(containerId); if (container == null) { Debug.LogError($"[MusicContainerPlayer] 找不到 ContainerId: {containerId}"); onFinished?.Invoke(); return null; } ContainerPlayHandle handle = new(); handle.TargetVolume = 1f; handle.Coroutine = this.m_coroutineHost.StartCoroutine( PlayContainerCoroutine(container, handle, inheritedBpm, onFinished)); return handle; } /// /// 停止一个播放句柄(立即停止,不淡出) /// public void Stop(ContainerPlayHandle handle) { if (handle == null) return; handle.Cancelled = true; foreach (ContainerPlayHandle child in handle.ChildHandles) { Stop(child); } handle.ChildHandles.Clear(); if (handle.Coroutine != null) this.m_coroutineHost.StopCoroutine(handle.Coroutine); foreach (AudioSource src in handle.ActiveSources) { ReturnSource(src); } handle.ActiveSources.Clear(); } // ───────────────────────────────────────────── // 核心协程 // ───────────────────────────────────────────── IEnumerator PlayContainerCoroutine( MusicContainer container, ContainerPlayHandle handle, float inheritedBpm, Action onFinished) { float effectiveBpm = container.Bpm > 0f ? container.Bpm : inheritedBpm; Debug.Log($"[MusicContainerPlayer] OnContainerEntered firing, container={container.Id}, subscribers={OnContainerEntered != null}"); OnContainerEntered?.Invoke(container, effectiveBpm, AudioSettings.dspTime); int loopsCompleted = 0; while (true) { yield return this.m_coroutineHost.StartCoroutine( PlayContainerOnce(container, handle.TargetVolume, handle, effectiveBpm)); if (handle.Cancelled) yield break; loopsCompleted++; // -1 = 无限循环,一直重复 if (container.LoopCount == -1) continue; // 0 = 不循环,播一次就结束 if (container.LoopCount == 0) break; // >= 1,播满次数后结束 if (loopsCompleted >= container.LoopCount) break; } if (!handle.Cancelled) onFinished?.Invoke(); } /// /// 播放一个 Container 一轮(不含循环逻辑) /// IEnumerator PlayContainerOnce(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm) { if (container.Segments == null || container.Segments.Count == 0) yield break; switch (container.ContainerType) { case ContainerType.Blend: yield return PlayBlend(container, volumeScale, handle, inheritedBpm); break; case ContainerType.Sequence: yield return PlaySequence(container, volumeScale, handle, inheritedBpm); break; case ContainerType.Random: yield return PlayRandom(container, volumeScale, handle, inheritedBpm); break; } } // ───────────────────────────────────────────── // Blend(同时播放) // ───────────────────────────────────────────── IEnumerator PlayBlend(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm) { IEnumerable tempos = container.Segments.Select(id => { MusicContainer c = this.m_containerConfig.QueryById(id); return c?.Bpm ?? 0f; }).Where(b => b > 0f).Distinct(); if (tempos.Count() > 1) { OnBlendError?.Invoke(container); } // 同时启动所有子元素,等待全部结束 List childHandles = new(); bool allDone = false; int remaining = container.Segments.Count; foreach (uint segId in container.Segments) { if (handle.Cancelled) yield break; ContainerPlayHandle childHandle = PlayChild(segId, volumeScale, () => { remaining--; if (remaining <= 0) allDone = true; }, inheritedBpm); if (childHandle != null) { childHandles.Add(childHandle); handle.ChildHandles.AddRange(childHandles); } } yield return new WaitUntil(() => allDone || handle.Cancelled); if (handle.Cancelled) { foreach (var ch in childHandles) Stop(ch); } } // ───────────────────────────────────────────── // Sequence // ───────────────────────────────────────────── IEnumerator PlaySequence(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm) { bool isStep = container.ContainerPlayMode; if (isStep) { // Step: 每次只播一个,游标全局推进 int index = GetNextSequenceIndex(container); yield return PlayChildAndWait(container.Segments[index], volumeScale, handle, inheritedBpm); } else { // Continuous: 顺序播完所有子元素,算一轮 for (int i = 0; i < container.Segments.Count; i++) { if (handle.Cancelled) yield break; // StrategyParam 作为两段之间的间隔时间(秒) if (i > 0 && container.StrategyParam > 0) yield return new WaitForSeconds(container.StrategyParam); yield return PlayChildAndWait(container.Segments[i], volumeScale, handle, inheritedBpm); } } } // ───────────────────────────────────────────── // Random // ───────────────────────────────────────────── IEnumerator PlayRandom(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm) { bool isStep = container.ContainerPlayMode; // 同上,音乐系统默认 Continuous if (isStep) { // Step Random: 随机选一个播放,算一次 loopCount uint chosen = PickRandomChild(container); yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm); } else { // Continuous Random: 随机不放回地播完所有子元素,算一轮 var remaining = new List(container.Segments); while (remaining.Count > 0) { if (handle.Cancelled) yield break; int idx = Random.Range(0, remaining.Count); uint chosen = remaining[idx]; remaining.RemoveAt(idx); if (container.StrategyParam > 0 && remaining.Count < container.Segments.Count - 1) yield return new WaitForSeconds(container.StrategyParam); yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm); } } } // ───────────────────────────────────────────── // 子元素分发(Segment 或嵌套 Container) // ───────────────────────────────────────────── /// /// 播放一个子元素(segment 或 container),等待其完成后返回。 /// IEnumerator PlayChildAndWait(uint id, float volumeScale, ContainerPlayHandle parentHandle, float inheritedBpm) { bool done = false; ContainerPlayHandle child = PlayChild(id, volumeScale, () => done = true, inheritedBpm); if (child != null) parentHandle.ChildHandles.Add(child); yield return new WaitUntil(() => done || parentHandle.Cancelled); if (parentHandle.Cancelled && child != null) Stop(child); } /// /// 启动一个子元素的播放,不等待,返回句柄。 /// ContainerPlayHandle PlayChild(uint id, float volumeScale, Action onDone, float inheritedBpm) { // ID < 1000000 是 MusicSegment,否则是嵌套 Container return id < 1000000u ? PlaySegment(id, volumeScale, onDone) : Play(id, onDone, inheritedBpm: inheritedBpm); } // ───────────────────────────────────────────── // Segment 播放 // ───────────────────────────────────────────── ContainerPlayHandle PlaySegment(uint segmentId, float volumeScale, Action onFinished) { MusicSegment segment = this.m_segmentConfig.QueryById(segmentId); if (segment == null) { Debug.LogError($"[MusicContainerPlayer] 找不到 SegmentId: {segmentId}"); onFinished?.Invoke(); return null; } AudioClip clip = Resources.Load($"Audios/{segment.Name}"); if (!clip) { Debug.LogError($"[MusicContainerPlayer] 音频文件未找到: {segment.Name}"); onFinished?.Invoke(); return null; } AudioSource source = this.m_pool.AcquireAudioSource(); source.clip = clip; source.loop = false; source.volume = volumeScale; source.Play(); var handle = new ContainerPlayHandle(); handle.ActiveSources.Add(source); handle.Coroutine = this.m_coroutineHost.StartCoroutine( WaitSegmentFinish(source, handle, onFinished)); return handle; } IEnumerator WaitSegmentFinish(AudioSource source, ContainerPlayHandle handle, Action onFinished) { yield return new WaitWhile(() => source.isPlaying && !handle.Cancelled); source.Stop(); ReturnSource(source); handle.ActiveSources.Remove(source); if (!handle.Cancelled) onFinished?.Invoke(); } // ───────────────────────────────────────────── // 工具 // ───────────────────────────────────────────── void ReturnSource(AudioSource source) { source.Stop(); this.m_pool.ReturnToPool(source.gameObject); } int GetNextSequenceIndex(MusicContainer container) { int current = this.m_sequenceStepIndex.GetValueOrDefault(container.Id, 0); this.m_sequenceStepIndex[container.Id] = (current + 1) % container.Segments.Count; return current; } uint PickRandomChild(MusicContainer container) { if (!this.m_randomHistory.TryGetValue(container.Id, out HashSet history)) { history = new HashSet(); this.m_randomHistory[container.Id] = history; } List available = container.Segments.Where(id => !history.Contains(id)).ToList(); if (available.Count == 0) { history.Clear(); available = new List(container.Segments); } uint chosen = available[Random.Range(0, available.Count)]; history.Add(chosen); return chosen; } } // ───────────────────────────────────────────── // 播放句柄 // ───────────────────────────────────────────── /// /// 一次 Container 播放的句柄,用于外部停止或淡出时访问正在播放的 AudioSource。 /// class ContainerPlayHandle { internal Coroutine Coroutine; internal bool Cancelled; internal float TargetVolume = 1f; internal List ActiveSources = new(); internal List ChildHandles = new(); /// /// 递归收集所有正在发声的 AudioSource(用于淡出) /// internal void CollectActiveSources(List result) { result.AddRange(this.ActiveSources); foreach (ContainerPlayHandle child in this.ChildHandles) child.CollectActiveSources(result); } } }