using System.Collections; using System.Collections.Generic; using Games.GlobeDefine; using GCGame.Table; using Module.Log; using UnityEngine; public class SoundClip : ListItemBase { // 自动卸载的限制时间需求 private const float _idleToDestroyTime = 20f; public AudioClip audioclip { get; set; } public int inUseTime { get; private set; } // 卸载时间 public float endTime { get; private set; } public void AddReference() { inUseTime++; endTime = float.PositiveInfinity; } public void RemoveReference() { inUseTime--; if (inUseTime <= 0) endTime = Time.realtimeSinceStartup + _idleToDestroyTime; } } public class SoundClipPools { public delegate void GetSoundClipDelegate(SoundClip soundClip, Tab_Sounds data, Vector3 position, int? token); public const string soundTableHashKey = "soundTable"; public const string soundDelegateHashKey = "soundDelegate"; public const string soundPosHashKey = "soundPosition"; public const string soundTokenHashKey = "soundToken"; public readonly List soundClips = new List(); /// /// 根据声音名称得到SoundClip,不存在会自动添加 /// /// 音效id /// 透传的播放位置 /// 调用组件的标记 /// 回调函数 public void GetSoundClip(int nSoundId, Vector3 position, int? token, GetSoundClipDelegate delFun) { if (nSoundId > 0) { var data = TableManager.GetSoundsByID(nSoundId, 0); if (data == null) LogModule.ErrorLog(string.Format("无法获得id为{0}的音效!", nSoundId)); else GetSoundClip(data, position, token, delFun); } } /// /// 根据声音名称得到SoundClip,不存在会自动添加 /// /// 数据包 /// 音效播放的位置 /// 调用组件的标记 /// 回调函数 public void GetSoundClip(Tab_Sounds data, Vector3 position, int? token, GetSoundClipDelegate delFun) { if (data == null) { LogModule.ErrorLog("无法获得音效数据"); } else if (string.IsNullOrEmpty(data.FullPathName)) { //LogModule.ErrorLog(string.Format("id={0}的音效数据没有配置对应的FullPathName", data.Id)); //delFun(null, position); } else { var soundClip = soundClips.GetItem(data.FullPathName, ListGetMode.get); if (soundClip == null) { var hash = new Hashtable(); hash[soundTableHashKey] = data; hash[soundDelegateHashKey] = delFun; hash[soundPosHashKey] = position; hash[soundTokenHashKey] = token; string bundleName; string assetName; SoundManager.GetMusicBundleName(LoadAssetBundle.BUNDLE_PATH_SOUND, data.FullPathName, out bundleName, out assetName); LoadAssetBundle.Instance.LoadSoundAsync(bundleName, assetName, OnLoadSound, hash); } else { delFun(soundClip, data, position, token); } RemoveLastUnUsedClip(); } } private void OnLoadSound(string soundPath, AudioClip audioClip, Hashtable hashParam) { var data = hashParam[soundTableHashKey] as Tab_Sounds; var delFun = hashParam[soundDelegateHashKey] as GetSoundClipDelegate; var position = (Vector3) hashParam[soundPosHashKey]; var token = (int?) hashParam[soundTokenHashKey]; if (data != null && audioClip != null) { // 防止反复构造 var soundClip = soundClips.GetItem(data.FullPathName, ListGetMode.create); soundClip.audioclip = audioClip; if (soundClip.audioclip.loadState == AudioDataLoadState.Failed) { LogModule.DebugLog("Cannot decompress the sound resource " + soundPath); soundClip = null; } if (delFun != null) delFun(soundClip, data, position, token); } else { LogModule.DebugLog("Cannot load the sound resource " + soundPath); } } /// /// 删除最长时间未使用的条目 /// private void RemoveLastUnUsedClip() { for (var i = soundClips.Count - 1; i >= 0; i--) if (soundClips[i].endTime < Time.realtimeSinceStartup) { LogModule.DebugLog(string.Format("Remove Used Clip ({0}), End At {1}, Current Time {2}", soundClips[i].id, soundClips[i].endTime, Time.realtimeSinceStartup)); //以后注释掉 string bundleName; string assetName; SoundManager.GetMusicBundleName(LoadAssetBundle.BUNDLE_PATH_SOUND, soundClips[i].id, out bundleName, out assetName); soundClips.RemoveAt(i); LoadAssetBundle.Instance.UnloadAsset(bundleName, assetName); } } } public class SfxSource { private readonly AudioSource _audioSource; public float endTime; public SfxSource(AudioSource audioSource) { _audioSource = audioSource; endTime = float.NegativeInfinity; } public Tab_Sounds data { get; private set; } public SoundClip sound { get; private set; } /// /// 用于区分音效启用者的标记 - 非启用者不能关闭音效 /// public int? token { get; private set; } public void Play(SoundClip soundClip, Tab_Sounds soundData, Vector3 position, int? instanceToken) { data = soundData; token = instanceToken; sound = soundClip; sound.AddReference(); _audioSource.playOnAwake = false; _audioSource.clip = soundClip.audioclip; _audioSource.volume = data.Volume; _audioSource.spread = data.Spread; _audioSource.priority = data.Priority; _audioSource.spatialBlend = data.PanLevel; _audioSource.minDistance = data.MinDistance; _audioSource.loop = data.IsLoop; // Hack:被Play的AudioClip必然是最后一次取出的AudioClip;因此AudioClip的EndTime会匹配; endTime = data.IsLoop ? float.PositiveInfinity : Time.realtimeSinceStartup + soundClip.audioclip.length; _audioSource.transform.position = position; _audioSource.Play(); } public void Stop() { if (_audioSource != null) { _audioSource.Stop(); _audioSource.clip = null; } if (sound != null) { sound.RemoveReference(); sound = null; } data = null; token = null; endTime = float.NegativeInfinity; } } public class SoundManager : MonoBehaviour { public const int m_SFXChannelsCount = 30; //最大声道数量 private AudioListener _audioListener; public SoundClipPools soundClipPools { get; private set; } //声音数据池 public MusicController musicController { get; private set; } public List audioSourceList { get; private set; } ////////////////////////////////////方法实现////////////////////////////////////////// private void Awake() { DontDestroyOnLoad(gameObject); soundClipPools = new SoundClipPools(); musicController = new MusicController(this); audioSourceList = new List(m_SFXChannelsCount); SetGlobalVolumn(PlayerPreferenceData.SystemSoundVolum); } private void Start() { PlayerPreferenceData.SystemSoundEffect.onSettingUpdate += OnSfxEnableUpdate; CreateAudioListener(); } private void OnSfxEnableUpdate(bool isEnable) { if (!isEnable) StopAllSoundEffect(); } private void Update() { musicController.UpdateMusic(); // 试图释放已经播放结束的AudioClip for (var i = 0; i < audioSourceList.Count; i++) if (audioSourceList[i].data != null && audioSourceList[i].endTime < Time.realtimeSinceStartup) audioSourceList[i].Stop(); #if UNITY_EDITOR if (trackClipCount) LogModule.DebugLog("Current Audio Count: " + soundClipPools.soundClips.Count); if (showClipList) { showClipList = false; for (var i = 0; i < soundClipPools.soundClips.Count; i++) { var clip = soundClipPools.soundClips[i]; LogModule.WarningLog("Audio " + clip.audioclip.name + " in use " + clip.inUseTime + " endTime " + clip.endTime); } } #endif } public void PlaySceneMusic() { var sceneClass = TableManager.GetSceneClassByID(GameManager.gameManager.RunningScene, 0); if (sceneClass != null && sceneClass.BGMusic >= 0) { var soundsTable = TableManager.GetSoundsByID(sceneClass.BGMusic, 0); if (soundsTable != null) GameManager.gameManager.SoundManager.PlayBGMusic(soundsTable.Id); } } /// /// 淡入淡出播放背景音乐 /// /// /// 淡出时间 /// 淡入时间 public void PlayBGMusic(int nClipID) { musicController.PlayMusic(nClipID); } public static void SetGlobalVolumn(float volumn) { AudioListener.volume = volumn; } /// /// 停止音效 /// public void StopSoundEffect(int nSoundId, int token) { for (var i = 0; i < audioSourceList.Count; i++) { var audioSource = audioSourceList[i]; if (audioSource.data != null && audioSource.data.Id == nSoundId && audioSource.token == token) audioSource.Stop(); } } public void StopAllSoundEffect() { for (var i = 0; i < audioSourceList.Count; i++) audioSourceList[i].Stop(); } /// /// 将音效接收器交由SoundManager予以管理 /// public void GetAudioListener(Transform root) { if (root == null) root = transform; _audioListener.transform.SetParent(root); _audioListener.transform.localPosition = Vector3.zero; _audioListener.transform.localRotation = Quaternion.identity; _audioListener.transform.localScale = Vector3.one; _audioListener.enabled = true; } public void CreateAudioListener() { if (_audioListener == null) { var audioListenerObj = new GameObject("AudioListener"); _audioListener = audioListenerObj.AddComponent(); GetAudioListener(null); } } // public void DestroyAudioListener() // { // if (_audioListener != null) // { // Destroy(_audioListener.gameObject); // _audioListener = null; // } // } //////////////////////////////////播放音效//////////////////////////////////// /// /// 根据目标的坐标和接收者的坐标listenerPos的距离来确定音量,用于技能音效 /// public void PlaySoundEffectAtPos(int nSoundId, Vector3 playSoundPos, int? token = null) { if (PlayerPreferenceData.SystemSoundEffect && nSoundId > 0) soundClipPools.GetSoundClip(nSoundId, playSoundPos, token, OnPlaySoundEffect); } /// /// 播放音效,默认音量缩放系数可以不填,配表值 /// public void PlaySoundEffect(int nSoundId, int? token = null) { PlaySoundEffectAtPos(nSoundId, transform.position, token); } private void OnPlaySoundEffect(SoundClip soundClip, Tab_Sounds data, Vector3 position, int? token) { if (PlayerPreferenceData.SystemSoundEffect && soundClip != null) if (soundClip.audioclip == null) { LogModule.ErrorLog("PlaySoundEffect soundClip.Audioclip is null"); } else { SfxSource source = null; for (var i = 0; i < audioSourceList.Count; i++) // 发现可重用的音效源 if (audioSourceList[i].data == null) { source = audioSourceList[i]; break; } if (source == null && audioSourceList.Count < m_SFXChannelsCount) { source = CreateSfxSource(); audioSourceList.Add(source); } if (source != null) source.Play(soundClip, data, position, token); } } private SfxSource CreateSfxSource() { var sourceObj = new GameObject("AudioSource: " + audioSourceList.Count); sourceObj.transform.SetParent(transform); return new SfxSource(sourceObj.AddComponent()); } public static void GetMusicBundleName(string sourceBundle, string sourceAsset, out string targetBundle, out string targetAsset) { sourceBundle += sourceAsset; var segments = sourceAsset.Split('/'); sourceAsset = segments[segments.Length - 1]; targetBundle = sourceBundle; targetAsset = sourceAsset; } //////////////////////////////////播放音效结束//////////////////////////////////// #region 背景音乐处理流程 public class MusicController { private readonly AudioSource _audioSource; // 当前播放中的音乐 private Tab_Sounds _currentData; private bool _musicPlayable; // 等待播放的下一段音乐 private AudioClip _nextClip; // 需要加载的音乐 private Tab_Sounds _nextData; // ******** 简单处理转换逻辑 ******** // 如果_currentData != _nextData 代表音乐处于切换中 // 未切换中,音量FadeIn直到达到预期音量; // 切换中,音量FadeOut直到0;音量到达0后,等待_nextClip不为空,然后修改为未切换; // 音乐不播放时,_nextData为空,_currentData记录应该播放的音乐;仅仅阻止音量到达0后的切换; // 用于音乐关闭和开启切换时,记录应该播放的音乐 - _nextData会被清空 private int _targetId = GlobeVar.INVALID_ID; public MusicController(SoundManager manager) { PlayerPreferenceData.SystemMusic.onSettingUpdate += SetMusicPlayable; _musicPlayable = PlayerPreferenceData.SystemMusic; _audioSource = manager.gameObject.AddComponent(); _audioSource.bypassEffects = true; _audioSource.bypassListenerEffects = true; //_audioSource.ignoreListenerVolume = true; _audioSource.panStereo = 0f; _audioSource.loop = true; _audioSource.playOnAwake = false; _audioSource.spatialBlend = 0f; _audioSource.dopplerLevel = 0f; } public void UpdateMusic() { // 音乐FadeOut if (_currentData != _nextData) { if (_currentData == null) { TryToPlayNextClip(); } else { if (_audioSource.volume > 0f && _currentData.FadeOutTime > 0f) _audioSource.volume = Mathf.MoveTowards(_audioSource.volume, 0f, Time.unscaledDeltaTime * _currentData.Volume / _currentData.FadeOutTime); else TryToPlayNextClip(); } } // 音乐FadeIn else { if (_currentData != null) if (_currentData.Volume > _audioSource.volume) if (_currentData.FadeInTime > 0f) _audioSource.volume = Mathf.MoveTowards(_audioSource.volume, _currentData.Volume, Time.unscaledDeltaTime * _currentData.Volume / _currentData.FadeInTime); else _audioSource.volume = _currentData.Volume; } } private void TryToPlayNextClip() { if (_nextData == null || _nextClip != null) { // 释放当前播放中的音乐 if (_currentData != null) { _audioSource.clip = null; _audioSource.Stop(); // 处理一个当前音乐文件等同下次的情况 if (_nextData == null || _nextData.FullPathName != _currentData.FullPathName) { string bundlePath; string assetName; GetMusicBundleName(LoadAssetBundle.BUNDLE_PATH_SOUND, _currentData.FullPathName, out bundlePath, out assetName); LoadAssetBundle.Instance.UnloadAsset(bundlePath, assetName); } _currentData = null; } // 开始下一段音乐 if (_nextClip != null) { _currentData = _nextData; _audioSource.clip = _nextClip; _audioSource.volume = 0f; _audioSource.Play(); _nextClip = null; } } } public void PlayMusic(int clipId, bool force = false) { // 不重复处理同样的切换请求 if (force || _targetId != clipId) if (clipId < 0) { _targetId = clipId; _nextClip = null; _nextData = null; } else { var data = TableManager.GetSoundsByID(clipId, 0); if (data != null) { _targetId = clipId; // 仅仅音乐可以播放时,对NextData进行赋值,否则只记录targetSoundId if (_musicPlayable) { _nextData = data; var hash = new Hashtable(); hash[SoundClipPools.soundTableHashKey] = data; string bundlePath; string assetName; GetMusicBundleName(LoadAssetBundle.BUNDLE_PATH_SOUND, data.FullPathName, out bundlePath, out assetName); LoadAssetBundle.Instance.LoadSoundAsync(bundlePath, assetName, OnMusicLoad, hash); // 不使用SoundPool,因为音乐的存活时间应该远远大于Pool配置的存活时间 //_manager.m_SoundClipPools.GetSoundClip(_targetData, Vector3.zero, OnMusicLoad); } } else { LogModule.ErrorLog(string.Format("无法获得id={0}的音效!", clipId)); } } } private void OnMusicLoad(string name, AudioClip audioClip, Hashtable hash) { if (audioClip != null) { var data = hash[SoundClipPools.soundTableHashKey] as Tab_Sounds; // 防止在一次加载结束前,出现第二次加载请求的情况 if (data != null && data == _nextData) _nextClip = audioClip; } } private void SetMusicPlayable(bool isPlayable) { if (_musicPlayable != isPlayable) { _musicPlayable = isPlayable; if (_musicPlayable) { if (_targetId > GlobeVar.INVALID_ID) PlayMusic(_targetId, true); } // 执行切换到空音乐的逻辑 else { _nextClip = null; _nextData = null; } } } } #endregion #if UNITY_EDITOR public bool trackClipCount; public bool showClipList; #endif }