using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Audio; namespace OCES.Audio { public class AudioSystem : MonoBehaviour { public static AudioSystem Instance { get; private set; } const string k_audioConfigPath = "AudioData"; const string k_audioResourcePath = "Audios"; AudioScheduler m_scheduler; MusicSystem m_musicSystem; AudioObjectConfig m_audioObjects; AudioGroupConfig m_groups; AudioMixer m_mixer; // ───────────────────────────────────────────── // 公开接口 // ───────────────────────────────────────────── public void Play(AudioObject audioObject) { this.m_scheduler.TryPlay(audioObject); } public void Play(int audioId) { AudioObject obj = this.m_audioObjects.QueryById((uint)audioId); if (obj != null) this.m_scheduler.TryPlay(obj); } public void Play(string audioName) { // TODO: 按文件名播放 } /// /// 更新游戏状态,驱动音乐与环境音系统切换。 /// 调用示例:AudioSystem.Instance.SetState(GameState.Game); /// public void SetState(TEnum state) where TEnum : Enum { this.m_musicSystem.OnStateChanged(state); } // ───────────────────────────────────────────── // 初始化 // ───────────────────────────────────────────── void Awake() { if ((bool)Instance && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); this.m_mixer = Resources.Load("Audios/Master"); // ── SFX 调度器 ── this.m_scheduler = gameObject.AddComponent(); this.m_audioObjects = AudioConfigLoader.Load($"{k_audioConfigPath}/AudioObject"); this.m_groups = AudioConfigLoader.Load($"{k_audioConfigPath}/AudioGroup"); this.m_scheduler.Initialize(this.m_groups, this.m_mixer); // ── 音乐与环境音系统 ── var segments = AudioConfigLoader.Load($"{k_audioConfigPath}/MusicSegment"); var containers = AudioConfigLoader.Load($"{k_audioConfigPath}/MusicContainer"); var musicPaths = AudioConfigLoader.Load($"{k_audioConfigPath}/MusicPath"); var ambiencePaths = AudioConfigLoader.Load($"{k_audioConfigPath}/AmbiencePath"); var musicTransitions = AudioConfigLoader.Load($"{k_audioConfigPath}/MusicTransition"); var ambienceTransitions = AudioConfigLoader.Load($"{k_audioConfigPath}/AmbienceTransition"); // MusicSystem 需要运行协程,作为 MonoBehaviour 挂载在同一 GameObject 上 this.m_musicSystem = gameObject.AddComponent(); // AudioSourcePool 由 MusicSystem 独占一个子节点,与 SFX pool 隔离 GameObject musicPoolRoot = new("MusicSourcePool"); musicPoolRoot.transform.SetParent(transform, false); AudioMixerGroup musicGroup = this.m_mixer.FindMatchingGroups("Master/Music")[0]; AudioSourcePool musicPool = new(musicPoolRoot.transform, musicGroup); GameObject ambiencePoolRoot = new("AmbienceSourcePool"); musicPoolRoot.transform.SetParent(transform, false); AudioMixerGroup ambienceGroup = this.m_mixer.FindMatchingGroups("Master/SFX/Ambience")[0]; AudioSourcePool ambiencePool = new(ambiencePoolRoot.transform, ambienceGroup); this.m_musicSystem.Initialize( segments, containers, musicPaths, ambiencePaths, musicTransitions, ambienceTransitions, musicPool, ambiencePool); // ── 注册 StateGroup ── // 在此处注册所有游戏状态 enum,TypeId 需与策划表中填写的数字一致 // 示例(请根据实际 enum 修改): // StateGroupRegistry.Register(1); // StateGroupRegistry.Register(2); StateGroupRegistry.Register(1); // ── 启动默认音乐与环境音 ── // 触发一次初始状态,让音乐系统从默认状态开始匹配 SetState(GameState.Home); } } static class AudioConfigLoader { public static Dictionary Load(string path, Func keySelector) { string json = System.IO.File.ReadAllText(path); var wrapper = JsonUtility.FromJson>(json); return wrapper.AudioObjects.ToDictionary(keySelector); } public static T Load(string tableName) where T : IBinarySerializable, new() { TextAsset bytes = Resources.Load(tableName); if (!bytes) Debug.LogError($"未找到表 {tableName}"); IBinarySerializable data = new T(); bool readOk = FileManager.ReadBinaryDataFromBytes(bytes.bytes, ref data); if (readOk) return (T)data; Debug.LogError($"{tableName} 解析出错,类型 {typeof(T)}"); return default; } class AudioObjectArrayWrapper { public T[] AudioObjects; } } }