最近做了项目的音频的播放,总结一下。
主要要熟悉三个东西:
1.AuidoSource
这个是音频的播放器,可以存在多个
(1)AudioClip要播放的音频文件
(2)SpatialBlend 为0是2D音效,为1是3D的音效
(3)play接口默认播放AudioSource的clip属性的音频,如果再次播放另外一个音频,那么会中断之前的播放的音频,播放新的。
(4)PlayOneShot接口要传入一个音频的名字来用于播放,可以多次调用同时播放,不会中断之前的播放,但是你也失去了控制每个音频暂停的权利,只有调用AudioSource的stop来暂停所有正在播放的音频。
(5)如果要播放多个音频,建议一个音频对应一个AudioSouce播放器,这样更好控制,我们游戏中也是这样实现的。
2.AudioListener
这个是音频的接受器,应该保持整个游戏中只存在一个!!
1.播放2D音频的时候可以放在任何位置,但是播放3D音频的时候我们就要放在特定位置了
2.最好我们的音频管理器自己管理一个对象然后将AudioListener挂上去,这个对象的生命期由我们自己掌握,因为挂其他对象上会牵扯进其他系统的逻辑,如果对应的AudioListener的obj被销毁了,那么我们将听不到声音。
3.AudioClip
这个是音频资源,支持ogg,mp3等格式, 游戏中我们把资源打包成了AB来进行加载
4.音频压缩
下文原链接:这里哦
官方文档:点击打开链接
使用声音文件时考虑以下建议:
对于音乐和(或)环境声(Ambient Sounds):
存储在长音频剪辑剪辑(Audio Clips)中的音乐会占据大量内存,您有两种选择:
- 使用流(Streaming)载入方式(Load Type),并且设置压缩格式(Compression Format)为Vorbis。如此设置即可使内存使用量减至最低,但相对的会占用更多CPU资源和I/O吞吐量。
- 使用压缩并存储至内存(Compressed In Memory)载入方式,设置压缩格式为Vorbis。与第一个方案唯一的区别是,前者占据更多I/O吞吐,而此种方式占用更多内存。注意,您可以调整Quality滑块来通过降低音频质量来减小音频剪辑压缩后的尺寸。一般来说,100%的Quality值略高,我们推荐70%。注意,使用该种设置添加两个以上的音乐或环境声剪辑时会大量消耗CPU。
音效通常是短或中等长度的音频剪辑,它们的播放频率可能很高,也可能很低。对于这些可能的情况,使用时请参考如下规则:
- 对于经常播放的短音频剪辑,使用载入时压缩(Decompress On Load)载入方式(Load Type),PCM或ADPCM压缩格式(Compression Format)。选择PCM时,播放无需解压,适用于短且使用频率高的音频剪辑。您也可以用ADPCM压缩格式,播放该格式需要解压缩,但解压缩ADPCM比Vorbis快很多。
- 对于经常播放的中等长度剪辑,使用压缩并存储至内存(Compressed In Memory)和ADPCM压缩格式(Compression Format)。原始PCM的大小大概是ADPCM的3.5倍,ADPCM的解压缩算法也比Vorbis解压缩算法占用更少CPU。
- 对于播放频率低的短音频剪辑,使用压缩并存储至内存(Compressed In Memory)和ADPCM压缩格式(Compression Format),理由同2。
- 对于播放频率低的中等长度剪辑,使用压缩并存储至内存(Compressed In Memory)和Vorbis压缩格式(Compression Format)。使用ADPCM处理该种声音效果(SFX)未免显得浪费了存储空间,况且播放的频率又很低,所以使用更多CPU资源解压缩还是可以接受的。
5.实现
using System;
using System.Collections.Generic;
using UnityEngine;
public class AudioManager : MonoBehaviour
{
private int mID = 0;
private GameObject mAudioListenerObj;
private AudioListener mAudioListener;
//AudioListener跟随对象
private GameObject mAudioListenerFollowObj;
//缓存当前帧数
private int mCurFrameCount;
//当前帧播放列表
private List<string> mCurFramePlayList = new List<string>();
//指定音频类型的音频数据
private Dictionary<AudioType, AudioTypeData> mAudioTypeDataDict = new Dictionary<AudioType, AudioTypeData>();
//缓存音频资源
//public Dictionary<string, AudioClip> mAudioClipDict = new Dictionary<string, AudioClip>();
private static AudioManager mInstance;
public static AudioManager Instance
{
get
{
if (mInstance == null)
{
GameObject obj = new GameObject();
obj.name = "AudioManager";
mInstance = obj.AddComponent<AudioManager>();
mInstance.Init();
DontDestroyOnLoad(obj);
}
return mInstance;
}
}
//不同需求都要新增一种类型
public enum AudioType
{
eNone,
eBGMusic, //背景音乐
eOtherMusic, //其余的音乐
eTalk, //对白语音
eBtnClick, //按钮点击
eSceneObj, //场景对象
eFight, //战斗
eStorySF, //剧情音效
eUiSF, //ui音效
//通用
eCommon,
eMax, //最大值
}
//空间类型
public enum AudioSpaceType
{
e2D,
e3D,
}
//创建类型
public enum AudioCreateType
{
eOnly, //使用唯一的播放器
eNew, //创建新的播放器
}
public class AudioData
{
//唯一id
public int mID;
//播放器本身
public AudioSource mAudioSource;
//类型
public AudioType mType;
//空间类型
public AudioSpaceType mSpaceType;
//创建类型
public AudioCreateType mCreateType;
//该播放跟随的对象(3D播放器才会有跟随对象)
public GameObject mFollowObj;
//计时器id
public int mTimerID;
//第几帧使用
public int mFrameCount;
//循环与否
public bool mLoop;
//最大距离
public float mMaxRange;
//当前播放器播放的音频名字
public string _PlayClipName
{
get
{
if(mAudioSource != null && mAudioSource.clip != null)
{
return mAudioSource.clip.name;
}
return "";
}
}
}
public class AudioTypeData
{
public AudioTypeData()
{
mAudioDataList = new List<AudioData>();
mAudioNameList = new List<string>();
mIsMute = false;
}
public List<AudioData> mAudioDataList;
public List<string> mAudioNameList;
//该类型是否静音
private bool mIsMute;
public bool _IsMute
{
get
{
return mIsMute;
}
set
{
mIsMute = value;
for (int i = 0; i < mAudioDataList.Count; i++)
{
mAudioDataList[i].mAudioSource.mute = mIsMute;
}
}
}
//该类型是否要停止
private bool mIsStop;
public bool _IsStop
{
get
{
return mIsStop;
}
set
{
mIsStop = value;
if(mIsStop)
{
for (int i = 0; i < mAudioDataList.Count; i++)
{
//延时的播放要停止掉
if (mAudioDataList[i].mTimerID != 0)
{
#if !PROJECT_PACKAGE
TimerManager.instance.RemoveTimer(mAudioDataList[i].mTimerID);
mAudioDataList[i].mTimerID = 0;
#endif
}
mAudioDataList[i].mAudioSource.Stop();
}
}
}
}
//该类型的音量
private float mVolume;
public float _Volume
{
get
{
return mVolume;
}
set
{
mVolume = Mathf.Clamp(value, 0.0f, 1.0f);
for (int i = 0; i < mAudioDataList.Count; i++)
{
mAudioDataList[i].mAudioSource.volume = Mathf.Clamp(mVolume, 0.0f, 1.0f);
}
}
}
}
private void Init()
{
mAudioListenerObj = new GameObject();
mAudioListenerObj.name = "AudioListener";
mAudioListenerObj.transform.SetParent(transform, false);
mAudioListener = mAudioListenerObj.AddComponent<AudioListener>();
}
//设置listern的跟随的对象
public void SetListenerFollow(GameObject obj)
{
mAudioListenerFollowObj = obj;
}
//创建播放器(新增类型要修改这里)
private AudioData CreateAudioData(AudioType type, AudioSpaceType spaceType, AudioCreateType createType, AudioTypeData data, GameObject targetObj, float maxRange, bool isLoop)
{
AudioData audioData = new AudioData();
audioData.mID = mID++;
audioData.mFollowObj = targetObj;
audioData.mTimerID = 0;
audioData.mFrameCount = Time.frameCount;
audioData.mType = type;
audioData.mSpaceType = spaceType;
audioData.mCreateType = createType;
audioData.mLoop = isLoop;
audioData.mMaxRange = maxRange;
GameObject obj = new GameObject();
obj.transform.SetParent(transform, false);
obj.name = type.ToString() + audioData.mID;
audioData.mAudioSource = obj.AddComponent<AudioSource>();
audioData.mAudioSource.mute = data._IsMute;
audioData.mAudioSource.volume = data._Volume;
if(spaceType == AudioSpaceType.e2D)
{
audioData.mAudioSource.spatialBlend = 0.0f;
audioData.mAudioSource.loop = isLoop;
}
else if(spaceType == AudioSpaceType.e3D)
{
audioData.mAudioSource.spatialBlend = 1.0f;
audioData.mAudioSource.maxDistance = maxRange;
audioData.mAudioSource.minDistance = 0;
audioData.mAudioSource.loop = isLoop;
audioData.mAudioSource.rolloffMode = AudioRolloffMode.Linear;
if (targetObj != null)
obj.transform.position = targetObj.transform.position;
}
return audioData;
}
private AudioTypeData GetAudioTypeData(AudioType type)
{
AudioTypeData data = null;
if (mAudioTypeDataDict.ContainsKey(type))
{
data = mAudioTypeDataDict[type];
}
else
{
mAudioTypeDataDict[type] = new AudioTypeData();
data = mAudioTypeDataDict[type];
}
return data;
}
private AudioData GetAudioData(int id)
{
for (int i = 0; i < (int)AudioType.eMax; i++)
{
AudioTypeData data = GetAudioTypeData((AudioType)i);
for (int j = 0; j < data.mAudioDataList.Count; j++)
{
if (data.mAudioDataList[j].mID == id)
{
return data.mAudioDataList[j];
}
}
}
return null;
}
//获取播放器(新增类型要修改这里)
private AudioData GetAudioSource(AudioType type, AudioSpaceType spaceType, AudioCreateType createType, GameObject targetObj, float maxRange, bool isLoop)
{
//获取指定type的数据
AudioTypeData data = GetAudioTypeData(type);
//获取播放器
AudioData audioData = null;
//寻找空闲播放器
bool bFree = false;
for (int i = 0; i < data.mAudioDataList.Count; i++)
{
if(createType == AudioCreateType.eOnly)
{
if(data.mAudioDataList[i].mCreateType == createType)
{
bFree = true;
audioData = data.mAudioDataList[i];
}
}
else if(createType == AudioCreateType.eNew)
{
//没有播放并且不是延时使用且不是同一帧(因为同一帧isPlaying判断不了)
//并且各个参数相同,表明可以复用
if (!data.mAudioDataList[i].mAudioSource.isPlaying &&
data.mAudioDataList[i].mTimerID == 0 &&
Time.frameCount != data.mAudioDataList[i].mFrameCount &&
data.mAudioDataList[i].mSpaceType == spaceType &&
data.mAudioDataList[i].mCreateType == createType)
{
bFree = true;
audioData = data.mAudioDataList[i];
audioData.mFollowObj = targetObj;
audioData.mAudioSource.loop = isLoop;
audioData.mAudioSource.maxDistance = maxRange;
break;
}
}
}
//没有空闲,创建新的
if (!bFree)
{
audioData = CreateAudioData(type, spaceType, createType, data, targetObj, maxRange, isLoop);
data.mAudioDataList.Add(audioData);
}
return audioData;
}
//获取音频资源
private void GetAudioClip(string name, Action<AudioClip> onComplete)
{
if (string.IsNullOrEmpty(name))
{
onComplete(null);
return;
}
#if !PROJECT_PACKAGE
ResourceManager.Instance.LoadCommonRes<AudioClip>(name.ToLower(), name, (resName, audioClip) =>
{
onComplete(audioClip);
}, (resName)=>
{
onComplete(null);
});
#endif
}
//判断音频文件是否已经被使用了
private bool IsAudioClipUsed(AudioType type, string name)
{
for (int i = 0; i < (int)AudioType.eMax; i++)
{
if(i != (int)type)
{
AudioTypeData data = GetAudioTypeData((AudioType)i);
List<AudioData> list = data.mAudioDataList.FindAll((item) => { return item._PlayClipName == name && item.mAudioSource.isPlaying; });
if(list.Count > 0)
{
return true;
}
}
}
return false;
}
//判断音频文件是否已经被使用了
private bool IsAudioClipUsed(int id, string name)
{
for (int i = 0; i < (int)AudioType.eMax; i++)
{
AudioTypeData data = GetAudioTypeData((AudioType)i);
List<AudioData> list = data.mAudioDataList.FindAll((item) => { return item._PlayClipName == name && item.mID != id && item.mAudioSource.isPlaying; });
if (list.Count > 0)
{
return true;
}
}
return false;
}
//释放指定名字的音频资源
private void Dispose(string name)
{
//if (mAudioClipDict.ContainsKey(name))
{
#if !PROJECT_PACKAGE
AssetBundles.AssetManager.UnloadAssetBundle(name.ToLower(), true);
//mAudioClipDict.Remove(name);
#endif
}
}
//释放特定类型的所有音频资源
public void Dispose(AudioType type)
{
AudioTypeData data = GetAudioTypeData(type);
for (int i = 0; i < data.mAudioNameList.Count;)
{
string name = data.mAudioNameList[i];
if (!IsAudioClipUsed(type, name))
{
//Debug.LogWarning("@@@@@@@@@@@@ release + " + type + " + " + name);
data.mAudioNameList.RemoveAt(i);
Dispose(name);
}
else
{
i++;
}
}
}
public void Dispose(int id)
{
AudioData data = GetAudioData(id);
if(data != null)
{
if (!IsAudioClipUsed(id, data._PlayClipName))
{
Dispose(name);
}
}
}
//停止并且释放指定类型的音频
public void StopAndDisposeAudio(AudioType type)
{
StopAudio(type);
Dispose(type);
}
public void StopAndDisposeAudio(int id)
{
StopAudio(id);
Dispose(id);
}
//停止并且释放所有音频
public void StopAndDisposeAll()
{
for(int i = 0; i < (int)AudioType.eMax; i++)
{
StopAndDisposeAudio((AudioType)i);
}
}
//停止指定类型的播放器
public void StopAudio(AudioType type)
{
AudioTypeData data = GetAudioTypeData(type);
data._IsStop = true;
}
//停止指定id的播放器
public void StopAudio(int id)
{
AudioData data = GetAudioData(id);
data.mAudioSource.Stop();
}
//静音
public void MuteAudio(AudioType type, bool bMute)
{
//如果设置里面静音了,那么bmute失效
if(SettingProxy.Instance._IsMute)
{
bMute = true;
}
AudioTypeData data = GetAudioTypeData(type);
data._IsMute = bMute;
}
//静音所有
public void MuteAudioAll(bool bMute)
{
for (int i = 0; i < (int)AudioType.eMax; i++)
{
MuteAudio((AudioType)i, bMute);
}
}
//设置指定类型音频的音量
public void SetAudioVolume(AudioType type, float volume)
{
AudioTypeData data = GetAudioTypeData(type);
data._Volume = volume;
}
//获取音频的长度
public void GetAudioClipLength(string name, Action<float> onComplete)
{
GetAudioClip(name, (audioClip) =>
{
AssetBundles.AssetManager.UnloadAssetBundle(name.ToLower(), true);
float length = 0;
if (audioClip != null)
{
length = audioClip.length;
}
onComplete(length);
});
}
//会过滤掉同一帧重复播放的音乐
public void PlayAudioFilter(AudioType type, string name, AudioSpaceType spaceType = AudioSpaceType.e2D,
AudioCreateType createType = AudioCreateType.eOnly,
bool isLoop = false,
float delayTime = 0.0f, GameObject obj = null, float maxRange = 0.0f)
{
if (mCurFrameCount != Time.frameCount)
{
mCurFrameCount = Time.frameCount;
mCurFramePlayList.Clear();
}
else
{
//阻止同一帧多次播放相同的音频
int index = mCurFramePlayList.FindIndex((item) => { return item == name; });
if (index != -1)
{
return;
}
}
PlayAudio(type, name, spaceType, createType, isLoop, delayTime, obj, maxRange);
}
//播放音效
//type 音频类型
//spaceType 空间类型(2D或者3D)
//createType 创建类型(eOnly:唯一播放器 eNew : 多个播放器)
//name 音频名字
//delayTime 延时时间
//obj 3d音效需要指定obj对象
//maxRange 3D音效的最大范围
//isLoop 是否循环
public int PlayAudio(AudioType type, string name,
AudioSpaceType spaceType = AudioSpaceType.e2D,
AudioCreateType createType = AudioCreateType.eOnly,
bool isLoop = false,
float delayTime = 0.0f, GameObject obj = null, float maxRange = 0.0f)
{
if (string.IsNullOrEmpty(name))
{
return -1;
}
//Debug.LogWarning("@@@@@@@@@@@@ + " + type + " + " + name);
//重置状态和缓存名字
AudioTypeData audioTypeData = GetAudioTypeData(type);
audioTypeData._IsStop = false;
audioTypeData.mAudioNameList.Add(name);
//获取播放器
AudioData audioData = GetAudioSource(type, spaceType, createType, obj, maxRange, isLoop);
//加载音频,然后播放音频
GetAudioClip(name, (audioClip) =>
{
if(audioClip == null)
{
return;
}
//判断播放前,是否外部已经停止该类型音乐
if(audioTypeData._IsStop)
{
return;
}
audioData.mAudioSource.clip = audioClip;
audioData.mAudioSource.playOnAwake = false;
if (Mathf.Abs(delayTime) < Mathf.Epsilon)
{
audioData.mAudioSource.Play();
}
else
{
#if !PROJECT_PACKAGE
audioData.mTimerID = TimerManager.instance.AddTimerAction(delayTime, (t) =>
{
if(audioTypeData != null && audioTypeData._IsStop)
{
return;
}
audioData.mAudioSource.Play();
audioData.mTimerID = 0;
}, null);
#endif
}
});
return audioData.mID;
}
//更新
private void Update()
{
if (mAudioListenerFollowObj != null)
{
mAudioListener.transform.position = mAudioListenerFollowObj.transform.position;
}
//进行同步坐标
foreach(var item in mAudioTypeDataDict)
{
List<AudioData> audioDataList = item.Value.mAudioDataList;
int count = audioDataList.Count;
for (int i = 0; i < count; i++)
{
//跟随对象由于是外部传入,可能已经被销毁了,所以要判空
if (audioDataList[i].mFollowObj != null)
{
audioDataList[i].mAudioSource.transform.position = audioDataList[i].mFollowObj.transform.position;
}
}
}
}
private void OnApplicationPause(bool state)
{
mAudioListenerObj.gameObject.SetActive(!state);
}
}