587 lines
21 KiB
C#
587 lines
21 KiB
C#
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
|
||
} |