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