Unity的音频

 最近做了项目的音频的播放,总结一下。

主要要熟悉三个东西:

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);
    }
}

猜你喜欢

转载自blog.csdn.net/qweewqpkn/article/details/80373740