Files
JJBB/Assets/Project/Script/GameLogic/Sound/SoundManager.cs

587 lines
21 KiB
C#
Raw Normal View History

2024-08-23 15:49:34 +08:00
using System.Collections;
using System.Collections.Generic;
using Games.GlobeDefine;
using GCGame.Table;
using Module.Log;
using UnityEngine;
public class SoundClip : ListItemBase<string>
{
// 自动卸载的限制时间需求
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<SoundClip> soundClips = new List<SoundClip>();
/// <summary>
/// 根据声音名称得到SoundClip不存在会自动添加
/// </summary>
/// <param name="nSoundId">音效id</param>
/// <param name="position">透传的播放位置</param>
/// <param name="token">调用组件的标记</param>
/// <param name="delFun">回调函数</param>
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);
}
}
/// <summary>
/// 根据声音名称得到SoundClip不存在会自动添加
/// </summary>
/// <param name="data">数据包</param>
/// <param name="position">音效播放的位置</param>
/// <param name="token">调用组件的标记</param>
/// <param name="delFun">回调函数</param>
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);
}
}
/// <summary>
/// 删除最长时间未使用的条目
/// </summary>
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; }
/// <summary>
/// 用于区分音效启用者的标记 - 非启用者不能关闭音效
/// </summary>
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<SfxSource> audioSourceList { get; private set; }
////////////////////////////////////方法实现//////////////////////////////////////////
private void Awake()
{
DontDestroyOnLoad(gameObject);
soundClipPools = new SoundClipPools();
musicController = new MusicController(this);
audioSourceList = new List<SfxSource>(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);
}
}
/// <summary>
/// 淡入淡出播放背景音乐
/// </summary>
/// <param name="clipName"></param>
/// <param name="fadeOutTime">淡出时间</param>
/// <param name="fadeInTime">淡入时间</param>
public void PlayBGMusic(int nClipID)
{
musicController.PlayMusic(nClipID);
}
public static void SetGlobalVolumn(float volumn)
{
AudioListener.volume = volumn;
}
/// <summary>
/// 停止音效
/// </summary>
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();
}
/// <summary>
/// 将音效接收器交由SoundManager予以管理
/// </summary>
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<AudioListener>();
GetAudioListener(null);
}
}
// public void DestroyAudioListener()
// {
// if (_audioListener != null)
// {
// Destroy(_audioListener.gameObject);
// _audioListener = null;
// }
// }
//////////////////////////////////播放音效////////////////////////////////////
/// <summary>
/// 根据目标的坐标和接收者的坐标listenerPos的距离来确定音量,用于技能音效
/// </summary>
public void PlaySoundEffectAtPos(int nSoundId, Vector3 playSoundPos, int? token = null)
{
if (PlayerPreferenceData.SystemSoundEffect && nSoundId > 0)
soundClipPools.GetSoundClip(nSoundId, playSoundPos, token, OnPlaySoundEffect);
}
/// <summary>
/// 播放音效,默认音量缩放系数可以不填,配表值
/// </summary>
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<AudioSource>());
}
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>();
_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
}