using System.Collections.Generic; using AK.Wwise; using Sirenix.OdinInspector; using UnityEngine; using UnityEngine.SceneManagement; using Event = AK.Wwise.Event; namespace SLSUtilities.WwiseAssistance { /// /// 背景音乐管理器。 /// /// 随 持久化(DontDestroyOnLoad),跨场景保持唯一实例。 /// 通过 Wwise Event 播放 / 停止音乐。 /// 淡入由 Wwise Event 自身的 Fade-In 配置控制, /// 淡出由 ExecuteActionOnPlayingID(Stop, transitionDuration) 实现。 /// /// public class BackgroundMusicManager : SerializedMonoBehaviour { // ──────────────────── 常量 ──────────────────── private const int DefaultFadeOutMs = 1000; private const string LoadingSceneName = "Loading"; // ──────────────────── 序列化字段 ──────────────────── [Title("Wwise")] [Tooltip("音乐状态名 → Wwise State 映射,播放时自动设置对应 State。")] public Dictionary baseMusicDictionary; [Required] [Tooltip("播放 BGM 的 Wwise Event。淡入请在 Wwise Authoring 的该 Event Play 动作上配置 Fade-In。")] public Event playMusicEvent; // ──────────────────── 运行时状态 ──────────────────── [Title("Runtime")] [ShowInInspector, ReadOnly] public bool isOverridden; [ShowInInspector, ReadOnly] private string lastMusicStateName; /// 当前播放的 Wwise Playing ID。 private uint currentPlayingID; /// 是否在等待下一个场景加载后自动重播。 private bool pendingReplay; /// 是否已经开始播放过音乐。用于避免在 Start 前就响应场景加载事件导致的重复播放。 private bool _isStarted = false; // ──────────────────── 生命周期 ──────────────────── private void Start() { if (_isStarted) return; _isStarted = true; PlayMusic("NormalMusic"); } private void OnEnable() => SceneManager.sceneLoaded += OnSceneLoaded; private void OnDisable() => SceneManager.sceneLoaded -= OnSceneLoaded; // ──────────────────── 公开 API ──────────────────── /// /// 播放指定状态的背景音乐。淡入效果由 Wwise Event 自身的 Fade-In 配置控制。 /// 被覆盖期间仅记录状态名,不实际播放。 /// public void PlayMusic(string musicStateName) { lastMusicStateName = musicStateName; if (isOverridden) return; StopImmediate(); // 设置 Wwise State 以选择正确的音乐变体 if (baseMusicDictionary.TryGetValue(musicStateName, out var state)) { state.SetValue(); } currentPlayingID = playMusicEvent.Post(gameObject); if (currentPlayingID == AkUnitySoundEngine.AK_INVALID_PLAYING_ID) { Debug.LogWarning("[BGM] playMusicEvent.Post 返回了无效的 Playing ID。"); } } /// /// 立即停止背景音乐(无淡出)。 /// public void StopMusic() => StopImmediate(); /// /// 带淡出停止当前音乐,并在下一个非 Loading 场景加载后自动重新播放。 /// 用于场景切换前调用。 /// public void TransitionToNextScene(int fadeOutMs = DefaultFadeOutMs) { pendingReplay = true; StopWithFade(fadeOutMs); } /// /// 设置覆盖标记。被覆盖时 仅记录状态名,不实际播放。 /// public void SetOverride(bool overridden) => isOverridden = overridden; // ──────────────────── 内部实现 ──────────────────── private void StopImmediate() { if (currentPlayingID == AkUnitySoundEngine.AK_INVALID_PLAYING_ID) return; AkUnitySoundEngine.ExecuteActionOnPlayingID( AkActionOnEventType.AkActionOnEventType_Stop, currentPlayingID, 0, AkCurveInterpolation.AkCurveInterpolation_Linear); currentPlayingID = AkUnitySoundEngine.AK_INVALID_PLAYING_ID; } private void StopWithFade(int fadeOutMs) { if (currentPlayingID == AkUnitySoundEngine.AK_INVALID_PLAYING_ID) return; AkUnitySoundEngine.ExecuteActionOnPlayingID( AkActionOnEventType.AkActionOnEventType_Stop, currentPlayingID, fadeOutMs, AkCurveInterpolation.AkCurveInterpolation_Linear); currentPlayingID = AkUnitySoundEngine.AK_INVALID_PLAYING_ID; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!pendingReplay || scene.name == LoadingSceneName) return; pendingReplay = false; PlayMusic(string.IsNullOrEmpty(lastMusicStateName) ? "NormalMusic" : lastMusicStateName); } } }