Files
JJBB/Assets/Project/Script/GameLogic/Sound/SoundManager.cs
2024-08-23 15:49:34 +08:00

587 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}