Unity 音频插件 - MasterAudio 实现音频管理系统

这篇具有很好参考价值的文章主要介绍了Unity 音频插件 - MasterAudio 实现音频管理系统。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

插件介绍:

Master Audio的是一个整体解决方案,所有的丰富的游戏音频需求。内置的音乐闪避,手动和自动的声音触发真正的随机声音变化,音频汇集全3D声音功能。支持所有出口的手机游戏平台,具有一流的性能。

主音频在线帮助网站可在此处找到:

Table of Contents

完整的主音频 API 文档可在此处找到:

Master Audio - AAA Sound Solution!: Main Page

实现功能:

  1. 音效要能与场景物件绑定,要能单独配置音量,支持相关配置方式
  2. 检查多个音效同时出现时的声音混合是否正常
  3. 提供动态调整某一大类音效音量的接口
  4. 脚步声配置

代码实现

        这里不细说实现中使用MasterAudio插件的API了(可以直接去翻一下插件文档)。

  1. 动态创建音频组,提供音频的播放、停止接口
  2. 提供调节一组音频的音量大小
  3. 音效与场景物件绑定工具
  4. 脚步声配置工具

AudioSystem:

using System;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using DarkTonic.MasterAudio;
using System.Linq;
using CheapUtil;

struct PlayAudioArgs
{
    public MasterAudioGroup group;
    public string path;
    public bool isLoop;
    public string callBack;
    public Vector3 position;
    public float volume;
    public float speed;
    public float startTime;
}

public class AudioSystem : MonoBehaviour
{
    struct MasterAudioGroupConfig
    {
        public AudioType audioType;
        public string name;
        public int audioLimit;
        public bool is2DSound;

        public MasterAudioGroupConfig(AudioType audioType, string name, int audioLimit, bool is2DSound = true)
        {
            this.audioType = audioType;
            this.name = name;
            this.audioLimit = audioLimit;
            this.is2DSound = is2DSound;
        }
    }

    #region 属性

    private static AudioSystem instance = null;
    public static AudioSystem Instance => instance;
    private float unloadTime = 5f;
    private MasterAudio masterAudio;
    private static readonly string groupTemplate = "Audio/DefaultSingle";
    private static readonly string audioSourceTemplate = "Audio/TemplatesDistance10";

    private Dictionary<AudioType, MasterAudioGroup>
        _masterAudioGroupDic = new Dictionary<AudioType, MasterAudioGroup>();

    private MasterAudioGroupConfig[] _masterAudioGroupConfig = new[]
    {
        new MasterAudioGroupConfig(AudioType.BGM, "BGM_SoundGroup", 0),
        new MasterAudioGroupConfig(AudioType.SE_2D, "2D_SoundGroup", 9),
        new MasterAudioGroupConfig(AudioType.SE_3D, "3D_SoundGroup", 9, false),
        new MasterAudioGroupConfig(AudioType.Voice, "Voice_SoundGroup", 0),
        new MasterAudioGroupConfig(AudioType.Footstep, "Footstep_SoundGroup", 9),
    };

    // 淡出携程,仅支持BGM和Voice(同时唯一播放)
    private Coroutine audioFadeOutCorBGM = null;
    private static PlayAudioArgs waitingBGM = new PlayAudioArgs();
    private Coroutine audioFadeOutCorVoice = null;
    private static PlayAudioArgs waitingVoice = new PlayAudioArgs();

    // 正在播放的音效数据
    private static List<SoundGroupVariation> sgvList = new List<SoundGroupVariation>();

    #endregion

    #region 初始化相关

    //初始化使用到的MasterAudio音乐组件
    public void Awake()
    {
        GameObject Masterad = new GameObject("MasterAudio");
        if (Masterad != null)
        {
            DontDestroyOnLoad(Masterad);
        } //加载时不销毁此GameObject

        masterAudio = Masterad.AddComponent<MasterAudio>();

        //设置MasterAudio的参数值
        masterAudio.useGroupTemplates = true;
        GameObject groupTemplateObj = GameAssetProxy.Load(GameAssetType.DataPrefab, groupTemplate, false) as GameObject;
        masterAudio.groupTemplates.Add(groupTemplateObj);
        masterAudio.soundGroupTemplate = groupTemplateObj.transform;

        GameObject audioSourceTemplateObj =
            GameAssetProxy.Load(GameAssetType.DataPrefab, audioSourceTemplate, false) as GameObject;
        masterAudio.audioSourceTemplates.Add(audioSourceTemplateObj);
        masterAudio.soundGroupVariationTemplate = audioSourceTemplateObj.transform;

        masterAudio.musicSpatialBlendType = MasterAudio.AllMusicSpatialBlendType.AllowDifferentPerController;
        masterAudio.mixerSpatialBlendType = MasterAudio.AllMixerSpatialBlendType.AllowDifferentPerGroup;
        masterAudio.newGroupSpatialType = MasterAudio.ItemSpatialBlendType.UseCurveFromAudioSource;

    }

    private void Start()
    {
        InitAudioGroups();
        instance = this;
    }

    private void InitAudioGroups()
    {
        for (int i = 0; i < _masterAudioGroupConfig.Length; i++)
        {
            var cfg = _masterAudioGroupConfig[i];
            MasterAudioGroup group = CreateSoundGroup(cfg.audioType, cfg.name, cfg.audioLimit, cfg.is2DSound);
            if (_masterAudioGroupDic.ContainsKey(cfg.audioType))
            {
                Debug.LogError("配置了相同类型的Group,请检查配置");
            }
            else
            {
                _masterAudioGroupDic.Add(cfg.audioType, group);
            }
        }
    }

    private MasterAudioGroup CreateSoundGroup(AudioType audioType, string rootName, int variationNum, bool is2DSound)
    {
        // string rootName = GetAudioGroupName(audioType);
        Transform trans =
            MasterAudio.CreateSoundGroup(rootName, MasterAudio.Instance.gameObject, variationNum, is2DSound);
        MasterAudioGroup audioGroup = trans.GetComponent<MasterAudioGroup>();
        if (audioGroup == null)
        {
            audioGroup = trans.gameObject.AddComponent<MasterAudioGroup>();
        }

        audioGroup.groupMasterVolume = GetAudioGroupVolume(audioType);
        return audioGroup;
    }

    float deltaTime = 0f;

    public void LateUpdate()
    {
        deltaTime += Time.deltaTime;
        if (deltaTime >= unloadTime)
        {
            deltaTime = 0f;
            TryUnloadUselessClips();
        }
    }

    #endregion

    #region 开放方法

    /// <summary>
    /// 播放音效
    /// </summary>
    /// <param name="audioType">音效组类型</param>
    /// <param name="path">音效路径</param>
    /// <param name="volume">音量[0, 1] (仅该音效的音量,不指音效组总音量)</param>
    /// <param name="isLoop">是否循环播放</param>
    /// <param name="callBack">播放完毕回调</param>
    /// <param name="speed">播放速度</param>
    /// <param name="isFadeOut">是否淡出</param>
    /// <param name="fadeOutTime">淡出时长</param>
    /// <param name="posX">3D音效的位置</param>
    /// <param name="posY">3D音效的位置</param>
    /// <param name="posZ">3D音效的位置</param>
    public void PlayAudio(AudioType audioType, string path, float volume = 1f, bool isLoop = false,
        bool isFadeOut = false, float fadeOutTime = 1f,
        string callBack = null, float speed = 1f, float startTime = 0, float posX = 0f, float posY = 0f,
        float posZ = 0f)
    {
        MasterAudioGroup group = GetAudioGroup(audioType);
        if (!TryLoadAudio(group, path)) return;
        if (volume > 1f) volume = 1f;
        if (volume < 0f) volume = 0f;

        bool canPlay = true;
        Coroutine curCor = GetAudioFadeOutCor(audioType);
        if (curCor != null)
        {
            canPlay = false;
        }
        else
        {
            if (isFadeOut && AllowFadeOut(audioType))
            {
                SetAudioFadeOutCor(audioType, StartCoroutine(FadeOutAndPlayNext(fadeOutTime, audioType)));
                canPlay = false;
            }
        }

        if (canPlay)
        {
            RealPlayAudio(group, path, isLoop, callBack, new Vector3(posX, posY, posZ), volume, speed, startTime);
        }
        else
        {
            if (audioType == AudioType.BGM)
            {
                waitingBGM.group = group;
                waitingBGM.path = path;
                waitingBGM.isLoop = isLoop;
                waitingBGM.callBack = callBack;
                waitingBGM.position = new Vector3(posX, posY, posZ);
                waitingBGM.volume = volume;
                waitingBGM.speed = speed;
                waitingBGM.startTime = startTime;
            }
            else if (audioType == AudioType.Voice)
            {
                waitingVoice.group = group;
                waitingVoice.path = path;
                waitingVoice.isLoop = isLoop;
                waitingVoice.callBack = callBack;
                waitingVoice.position = new Vector3(posX, posY, posZ);
                waitingVoice.volume = volume;
                waitingVoice.speed = speed;
                waitingVoice.startTime = startTime;
            }
        }
    }


    public void PlaySound3DAtVector3(AudioType audioType, string path, Vector3 pos, float volume = 1f,
        bool isLoop = false,float pitch = 1,float delayTime = 0, string callBack = null, float startTime = 0)
    {
        MasterAudioGroup group = GetAudioGroup(audioType);
        if (!TryLoadAudio(group, path)) return;
        if (volume > 1f) volume = 1f;
        if (volume < 0f) volume = 0f;
        
        PlaySoundResult result = MasterAudio.PlaySound3DAtVector3(group.name,path,pos, volume, pitch, delayTime);        
        SetPlaySoundResult(result,path,isLoop,callBack,startTime);
    }
    
    public void PlaySound3DAtTransform(AudioType audioType, string path, Transform trans, float volume = 1f,
        bool isLoop = false,float pitch = 1,float delayTime = 0, string callBack = null, float startTime = 0)
    {
        MasterAudioGroup group = GetAudioGroup(audioType);
        if (!TryLoadAudio(group, path)) return;
        if (volume > 1f) volume = 1f;
        if (volume < 0f) volume = 0f;
        
        PlaySoundResult result = MasterAudio.PlaySound3DAtTransform(group.name,path, trans, volume, pitch, delayTime);        
        SetPlaySoundResult(result,path,isLoop,callBack,startTime);
    }
    
    public void PlaySound3DFollowTransform(AudioType audioType, string path, Transform trans, float volume = 1f,
        bool isLoop = false,float pitch = 1,float delayTime = 0, string callBack = null, float startTime = 0)
    {
        MasterAudioGroup group = GetAudioGroup(audioType);
        if (!TryLoadAudio(group, path)) return;
        if (volume > 1f) volume = 1f;
        if (volume < 0f) volume = 0f;
        PlaySoundResult result = MasterAudio.PlaySound3DFollowTransform(group.name,path, trans, volume, pitch, delayTime);        
        SetPlaySoundResult(result,path,isLoop,callBack,startTime);
    }

    public void StopAudio(AudioType audioType, string path)
    {
        MasterAudioGroup group = GetAudioGroup(audioType);

        // 无法真正停止播放 
        // foreach(SoundGroupVariation var in group.groupVariations)
        // {
        //     if (var.IsPlaying && var.resourceFileName == path)
        //     {
        //         MasterAudio.StopVariationInSoundGroup(group.GameObjectName, var.name);
        //     }
        // }

        for (int i = 0; i < sgvList.Count; i++)
        {
            var sgv = sgvList[i];
            if (sgv.IsPlaying)
                MasterAudio.StopVariationInSoundGroup(group.GameObjectName, sgv.name);
        }

        sgvList.RemoveAll(item => (item.IsPlaying == false || item.VarAudio.clip == null));
    }

    public void StopAudioGroup(AudioType audioType, bool isFadeOut = false, float fadeOutTime = 1f)
    {
        string curPath = GetPlayingPaths(audioType);
        if (curPath.Equals(""))
            return;
        MasterAudioGroup group = GetAudioGroup(audioType);
        if (audioType == AudioType.BGM)
        {
            waitingBGM.path = null;
        }
        else if (audioType == AudioType.Voice)
        {
            waitingVoice.path = null;
        }

        if (isFadeOut)
        {
            Coroutine curCor = GetAudioFadeOutCor(audioType);
            if (curCor == null)
            {
                SoundGroupVariation curSGV = GetAudioCurVariation(audioType);
                SetAudioFadeOutCor(audioType, StartCoroutine(FadeOutAndPlayNext(fadeOutTime, audioType)));
            }
            else
            {
                RealStopAudioGroup(audioType);
            }
        }
        else
        {
            RealStopAudioGroup(audioType);
        }
    }

    // todo
    public void PauseAudio(AudioType audioType, string path)
    {
    }

    public void PauseAudioGroup(AudioType audioType)
    {
    }

    public void ResumeAudio(AudioType audioType, string path)
    {
    }

    public void ResumeAudioGroup(AudioType audioType)
    {
    }

    public void SilentAudioGroup(AudioType audioType, bool bMute)
    {
        MasterAudioGroup group = GetAudioGroup(audioType);
        if (bMute)
            MasterAudio.MuteGroup(group.GameObjectName);
        else
            MasterAudio.UnmuteGroup(group.GameObjectName);
    }

    public void SetAudioGroupVolume(AudioType audioType, float volume, bool isGradual = false, float time = 1f)
    {
        if (volume > 1f) volume = 1f;
        if (volume < 0f) volume = 0f;
        MasterAudioGroup group = GetAudioGroup(audioType);
        if (!isGradual)
            MasterAudio.SetGroupVolume(group.GameObjectName, volume);
        else
            MasterAudio.FadeSoundGroupToVolume(group.GameObjectName, volume, time);
        
        SaveAudioGroupVolume(audioType,volume);
    }

    public float GetAudioPlayTime(string path)
    {
        foreach (SoundGroupVariation sgv in sgvList)
        {
            if (sgv.GetCurClipName() == path)
                return sgv.VarAudio.time;
        }

        return -1;
    }

    public float GetAudioLength(string path)
    {
        foreach (SoundGroupVariation sgv in sgvList)
        {
            if (sgv.GetCurClipName() == path)
                return sgv.VarAudio.clip.length;
        }

        return -1;
    }

    /// <returns name="path">如有多个,以逗号分隔</returns>
    public string GetPlayingPaths(AudioType audioType)
    {
        string paths = "";
        foreach (SoundGroupVariation sgv in sgvList)
        {
            if (sgv.ParentGroup == GetAudioGroup(audioType))
            {
                if (!paths.Equals(""))
                    paths += ",";
                paths += sgv.GetCurClipName();
            }
        }

        return paths;
    }

    #endregion

    #region private

    private bool TryLoadAudio(MasterAudioGroup group, string path)
    {
        if (path == null)
        {
            Debug.LogError("传入的文件路径为空");
            return false;
        }

        if (group == null)
        {
            Debug.LogError("当前没有创建相关的MasterAudioGroup");
            return false;
        }

        if (!LoadAudio(group, path))
        {
            Debug.LogError("加载 " + path + " 路径下的资源失败");
            return false;
        }

        return true;
    }

    private bool LoadAudio(MasterAudioGroup audioGroup, string clipPath)
    {
        if (!audioGroup.hasReadyClip(clipPath))
        {
            AudioClip cp = GameAssetProxy.Load<AudioClip>(GameAssetType.Audio, clipPath);
            cp.name = clipPath; // 音频这边以path为key
            if (cp == null)
                return false;
            audioGroup.ReadyAudioClips.Add(clipPath, cp);
        }

        return true;
    }

    private void RealPlayAudio(MasterAudioGroup group, string path, bool isloop, string callBack, Vector3 position,
        float volume = 1f, float speed = 1f, float startTime = 0)
    {
        PlaySoundResult result;
        if (position != null && !position.Equals(Vector3.zero))
            // 对Z轴距离无效
            result = MasterAudio.PlaySound3DAtVector3(group.GameObjectName, path, position, volume, speed);
        else
            result = MasterAudio.PlaySound(group.GameObjectName, path, volume, speed);
        if (result != null)
        {
            SoundGroupVariation sgv = result.ActingVariation;
            sgv.VarAudio.loop = isloop;
            sgv.JumpToTime(startTime);
            sgvList.Add(sgv);
            if (callBack != null)
                StartCoroutine(PlayEndCallBack(sgv, path, callBack));
        }
        else
        {
            Debug.LogError("播放音效失败!");
            return;
        }
    }

    private void RealPlayAudio(MasterAudioGroup group, string path, bool isloop, string callBack, Transform trans,
        float volume = 1f, float speed = 1f, float startTime = 0)
    {
        PlaySoundResult result;
        if (trans != null)
            // 对Z轴有效
            result = MasterAudio.PlaySound3DFollowTransform(group.GameObjectName, path, trans, volume, 1, 0);
        else
            result = MasterAudio.PlaySound(group.GameObjectName, path, volume, speed);

        if (result != null)
        {
            SetPlaySoundResult(result,path,isloop,callBack,startTime);
        }
        else
        {
            Debug.LogError("播放音效失败!");
            return;
        }
    }

    private void SetPlaySoundResult(PlaySoundResult result,string path,bool isloop,string callBack,float startTime = 0)
    {
        if (result != null)
        {
            SoundGroupVariation sgv = result.ActingVariation;
            sgv.VarAudio.loop = isloop;
            sgv.JumpToTime(startTime);
            sgvList.Add(sgv);
            if (callBack != null)
                StartCoroutine(PlayEndCallBack(sgv, path, callBack)); 
        }
    }

    public MasterAudioGroup GetAudioGroup(AudioType audioType)
    {
        MasterAudioGroup group = null;
        if (!_masterAudioGroupDic.TryGetValue(audioType, out group))
        {
            Debug.LogError($"not find group,audioType:{audioType.ToString()}");
        }
        return group;
    }

    private SoundGroupVariation GetAudioCurVariation(AudioType audioType)
    {
        foreach (SoundGroupVariation sgv in sgvList)
        {
            if (sgv.ParentGroup.name == GetAudioGroup(audioType).name)
                return sgv;
        }
        return null;
    }

    private Coroutine GetAudioFadeOutCor(AudioType audioType)
    {
        switch (audioType)
        {
            case AudioType.BGM:
                return audioFadeOutCorBGM;
            case AudioType.Voice:
                return audioFadeOutCorVoice;
        }
        return null;
    }

    private void SetAudioFadeOutCor(AudioType audioType, Coroutine cor)
    {
        switch (audioType)
        {
            case AudioType.BGM:
                audioFadeOutCorBGM = cor;
                break;
            case AudioType.Voice:
                audioFadeOutCorVoice = cor;
                break;
        }
    }

    private void RealStopAudioGroup(AudioType audioType)
    {
        MasterAudioGroup group = GetAudioGroup(audioType);
        MasterAudio.StopAllOfSound(group.GameObjectName);
    }

    private PlayAudioArgs GetWaitingArgs(AudioType audioType)
    {
        if (audioType == AudioType.BGM)
            return waitingBGM;
        else if (audioType == AudioType.Voice)
            return waitingVoice;
        return new PlayAudioArgs();
    }
    private bool AllowFadeOut(AudioType audioType)
    {
        if (audioType == AudioType.BGM || audioType == AudioType.Voice)
            return true;
        return false;
    }

    private IEnumerator FadeOutAndPlayNext(float fadeOutTime, AudioType audioType)
    {
        SoundGroupVariation curSGV = GetAudioCurVariation(audioType);
        if (curSGV != null)
        {
            curSGV.FadeOutNow(fadeOutTime);
            yield return new WaitForSeconds(fadeOutTime);
        }
        else
        {
            yield return new WaitForSeconds(0.01f);     // 此行仅仅是为了代码方便好写
        }
        SetAudioFadeOutCor(audioType, null);
        RealStopAudioGroup(audioType);
        if (AllowFadeOut(audioType)){
            PlayAudioArgs waitingArgs = GetWaitingArgs(audioType);
            if (waitingArgs.path != null){
                RealPlayAudio(waitingArgs.group, 
                    waitingArgs.path, 
                    waitingArgs.isLoop, 
                    waitingArgs.callBack, 
                    waitingArgs.position, 
                    waitingArgs.volume, 
                    waitingArgs.speed, 
                    waitingArgs.startTime);
            }
        }
    }

    private IEnumerator PlayEndCallBack(SoundGroupVariation sgv, string path, string callBack)
    {
        while (sgv != null && sgv.IsPlaying)
            yield return null;
        LuaClient.GetMainState().CallLuaFunction(callBack, path);
    }

    private void TryUnloadUselessClips()
    {
        // todo: 怀疑这里会有"在卸载的同一帧播放音频时播不出来"的bug,但暂时没空自测了
        List<string> unloadClipNames = new List<string>();
        foreach (SoundGroupVariation sgv in sgvList)
        {
            if (!sgv.IsPlaying)
            {
                string name = sgv.VarAudio.clip.name;
                if (!unloadClipNames.Contains(name))
                    unloadClipNames.Add(sgv.VarAudio.clip.name);
            }
        }
        foreach (string clipName in unloadClipNames)
        {
            bool canRemove = true;
            if (clipName == waitingBGM.path || clipName == waitingVoice.path)
                canRemove = false;
            if (canRemove)
            {
                foreach (SoundGroupVariation sgv in sgvList)
                {
                    if (sgv.VarAudio.isPlaying && sgv.VarAudio.clip && sgv.VarAudio.clip.name == clipName)
                    {
                        canRemove = false;
                        break;
                    }
                }
            }
            if (canRemove)
            {
                List<MasterAudioGroup> audioGroups = new List<MasterAudioGroup>();
                foreach (SoundGroupVariation sgv in sgvList)
                {
                    if (sgv.VarAudio.clip && sgv.VarAudio.clip.name == clipName)
                    {
                        sgv.VarAudio.clip = null;
                        if (!audioGroups.Contains(sgv.ParentGroup))
                            audioGroups.Add(sgv.ParentGroup);
                    }
                }
                foreach (MasterAudioGroup audioGroup in audioGroups)
                {
                    AudioClip clip = audioGroup.ReadyAudioClips[clipName];
                    audioGroup.ReadyAudioClips.Remove(clipName);
                    GameAssetProxy.Unload(GameAssetType.Audio, clip);
                    clip = null;
                }
            }
        }
        sgvList.RemoveAll(item => (item.IsPlaying == false || item.VarAudio.clip == null));
    }
    #endregion

    #region 调整某一大类音效音量

    private void SaveAudioGroupVolume(AudioType audioType,float volume)
    {
        PlayerPrefs.SetFloat(audioType.ToString(), volume);
    }
    
    public float GetAudioGroupVolume(AudioType audioType)
    {
        if(PlayerPrefs.HasKey(audioType.ToString()))
            return PlayerPrefs.GetFloat(audioType.ToString());

        return 1;
    }

    #endregion
}

音频与场景物体绑定工具:

Unity 音频插件 - MasterAudio 实现音频管理系统

/*
 * 物体与声音绑定配置表
 * 2022.11.9
 * linYiShan
 */
using System;
using System.Collections;
using System.Collections.Generic;
using DarkTonic.MasterAudio;
using MainCameraSpace;
using UnityEditor;
using UnityEngine;

[Serializable]
public class AudioClipConfig
{
    [HideInInspector]
    public string name = "";
    public AudioClip clip;
    public AudioType audioType = AudioType.SE_3D;
    [Tooltip("路径")] 
    public string path = "";
    [Tooltip("音量"),Range(0,1)] 
    public float volume = 1;
    [Tooltip("音高"),Range(0,3)] 
    public float pitch = 1;
    [Tooltip("播放速度")] 
    public float speed = 1;
    [Tooltip("是否循环")]
    public bool loop = false;
    [Tooltip("显示时播放")]
    public bool playOnLoad = false;
    [Tooltip("范围")]
    public float range = 30;

    [HideInInspector] public bool isPlaying = false;
    [HideInInspector] public SoundGroupVariation variation = null;

    public void Reset()
    {
        name = string.Empty;
        clip = null;
        audioType = AudioType.SE_3D;
        path = "";
        volume = 1;
        speed = 1;
        pitch = 1;
        playOnLoad = false;
        range = 30;
    }
}

public class GameObjectAudioConfig : MonoBehaviour
{
    // 编辑器范围显示
    public static bool ShowRange = false;
    [Header("隐藏时停止播放")]
    public bool hideStopAudio = true;
    [Header("声音曲线")]
    public AnimationCurve audioVolumeCurve = AnimationCurve.Linear(0, 0, 1, 1);
    [Header("声音列表")]
    public List<AudioClipConfig> audioClipConfigList = new List<AudioClipConfig>();

    private void Start()
    {
        CreateRangeObj();
    }

    // Start is called before the first frame update
    void OnEnable()
    {
        StartCoroutine(_PlayOnLoad());
    }
    
    private void OnDisable()
    {
        if(hideStopAudio)
            Stop();
    }

    private void Update()
    {
        DetectionDistance();
        UpdateVolume();
    }

    public void Play()
    {
        if (AudioSystem.Instance == null)
        {
            Debug.LogError("AudioSystem not init");
            return;
        }
        for (int i = 0; i < audioClipConfigList.Count; i++)
        {
            AudioClipConfig config = audioClipConfigList[i];
            _Play(config);
        }
    }
    
    public void Stop()
    {
        if (AudioSystem.Instance == null)
        {
            return;
        }
        
        for (int i = 0; i < audioClipConfigList.Count; i++)
        {
            AudioClipConfig config = audioClipConfigList[i];
            _Stop(config);
        }
    }

    private void _Stop(AudioClipConfig config)
    {
        if (config.isPlaying)
        {
            if (config.variation != null)
            {
                AudioSystem.Instance.StopAudioBySgvName(config.audioType,config.variation.name);
                config.isPlaying = false;
                config.variation = null;   
            }
            // else
            // {
            //     AudioSystem.Instance.StopAudio(config.audioType,config.path);
            //     Debug.Log("stop audio: " + config.path);
            //     config.isPlaying = false;
            // }
        }
    }

    private IEnumerator _PlayOnLoad()
    {
        while (AudioSystem.Instance == null)
        {
            yield return null;
        }

        for (int i = 0; i < audioClipConfigList.Count; i++)
        {
            AudioClipConfig config = audioClipConfigList[i];
            if(config.audioType == AudioType.SE_3D) continue; 
            if (config.playOnLoad)
            {
                _Play(config);
            }
        }
    }

    private void _Play(AudioClipConfig config)
    {
        if (config.isPlaying) return;
        
        // if (config.audioType == AudioType.SE_3D)
        // {
            config.variation = AudioSystem.Instance.PlayGameObjectConfigAudio(config.audioType, config.path,1, config.loop);
            if(config.variation == null) return;
            config.isPlaying = true;       
        // }
        // else
        // {
        //     AudioSystem.Instance.PlayAudio(config.audioType,config.path,config.volume,config.loop); 
        //     config.isPlaying = true;
        //     Debug.Log("play audio: " + config.path);
        // }
        // AudioSystem.Instance.PlaySound3DFollowTransform(config.audioType, config.path, transform, config.volume, config.loop);
    }
    
    private bool CheckInRange(float range)
    {
        // var p = GetCameraLookAtGroundPos();
        var p = AudioSystem.Instance.Scene3DTriggerTrans.position;
        if (Vector3.Distance(p, transform.position) >= range) return false;
        return true;    
    }

    private void DetectionDistance()
    {
        if (AudioSystem.Instance == null) return;
        if (AudioSystem.Instance.Scene3DTriggerTrans == null) return;
        if(audioClipConfigList == null) return;

        for (int i = 0; i < audioClipConfigList.Count; i++)
        {
            var cfg = audioClipConfigList[i];
            if(cfg.audioType != AudioType.SE_3D) continue;
            
            if (CheckInRange(cfg.range))
            {
                if (!cfg.isPlaying)
                {
                    _Play(cfg);
                }
            }
            else
            {
                if (cfg.isPlaying)
                {
                    _Stop(cfg);
                }
            }
        }
    }

    public Vector3 GetCameraLookAtGroundPos()
    {
        Transform camera = MainCameraController.GetInstance().GetMainCameraTransform();
        Vector3 p = GetIntersectWithLineAndPlane(camera.position,camera.forward,new Vector3(0,1,0), new Vector3(0,transform.position.y,9));
        return p;
    }

    /// <summary>
    /// 计算直线与平面的交点
    /// </summary>
    /// <param name="point">直线上某一点</param>
    /// <param name="direct">直线的方向</param>
    /// <param name="planeNormal">垂直于平面的的向量</param>
    /// <param name="planePoint">平面上的任意一点</param>
    /// <returns></returns>
    private Vector3 GetIntersectWithLineAndPlane(Vector3 point, Vector3 direct, Vector3 planeNormal, Vector3 planePoint)
    {
        float d = Vector3.Dot(planePoint - point, planeNormal) / Vector3.Dot(direct.normalized, planeNormal);
        return d * direct.normalized + point;
    }

    private void UpdateVolume()
    {
        if (AudioSystem.Instance == null) return;
        if (AudioSystem.Instance.Scene3DTriggerTrans == null) return;
        
        for (int i = 0; i < audioClipConfigList.Count; i++)
        {
            AudioClipConfig config = audioClipConfigList[i];
            if(config.audioType != AudioType.SE_3D) continue;

            if (config.isPlaying)
            {
                float dis = Vector3.Distance(AudioSystem.Instance.Scene3DTriggerTrans.position, transform.position) / (config.range);
                dis = 1 - Math.Min(dis, 1);
                float volume = audioVolumeCurve.Evaluate(dis);
                volume = volume * config.volume;
                // MasterAudioGroup group = AudioSystem.Instance.GetAudioGroup(config.audioType);
                if (config.variation != null)
                {
                    // MasterAudio.ChangeVariationVolume(group.GameObjectName,true,config.variation.name,volume);
                    config.variation.VarAudio.volume = volume;
                }
            }
        }
    }
    
    public void CreateRangeObj()
    {
        #if UNITY_EDITOR
        if(!GameObjectAudioConfig.ShowRange) return;

        Transform audioTrans = transform.Find("AuidoRange");
        if (audioTrans == null)
        {
            GameObject obj = new GameObject("AuidoRange");
            obj.transform.SetParent(transform);
            audioTrans = obj.transform;
            audioTrans.localScale = new Vector3(1,1,1);
            audioTrans.localPosition = new Vector3();
        }
        var spherePrefab = EditorGUIUtility.LoadRequired("Assets/Resources/Editor/Audio/Sphere.prefab") as GameObject;
        for (int i = 0; i < audioClipConfigList.Count; i++)
        {
            var config = audioClipConfigList[i];
            if(config.clip == null) continue;
            Transform child = audioTrans.Find(config.name);
            if (child == null)
            {
                var obj = Instantiate(spherePrefab, audioTrans);
                obj.name = config.name;
                child = obj.transform;
            }
            float radius = config.range * 2;
            child.localScale = new Vector3(radius, radius, radius);
            child.localPosition = new Vector3();
        }
        #endif
    }
}

Editor文件夹下的编辑器脚本:


using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;

[CustomEditor(typeof(GameObjectAudioConfig))]
public class GameObjectAudioConfigEditor:Editor
{
    private List<AudioClipConfig> audioClipConfigList;
    private void Awake()
    {
        GameObjectAudioConfig config = target as GameObjectAudioConfig;
        audioClipConfigList = config.audioClipConfigList;
    }

    public override void OnInspectorGUI()
    {
        for (int i = 0; i < audioClipConfigList.Count; i++)
        {
            var config = audioClipConfigList[i];
            if (config.clip == null)
            {
                config.Reset();
            }
            string path = config.clip == null ? string.Empty : AssetDatabase.GetAssetPath(config.clip);
            config.path = path == string.Empty ? string.Empty : GetAudioPath(path);
            config.name = path == string.Empty ? string.Empty : Path.GetFileNameWithoutExtension(path);
        }
        base.OnInspectorGUI();
    }

    private string GetAudioPath(string path)
    {
        string _path = path.Replace(Path.GetExtension(path), String.Empty);
        return _path.Replace("Assets/Resources/", string.Empty);
    }
}

脚步声配置工具:这里使用不用的Layer播放不同的音效

Unity 音频插件 - MasterAudio 实现音频管理系统文章来源地址https://www.toymoban.com/news/detail-409077.html

/*
 * 脚步声单元
 * 2022.11.9
 * linYiShan
 */
using System;
using System.Collections.Generic;
using DarkTonic.MasterAudio;
using UnityEngine;

public class FootstepSoundsUnit:MonoBehaviour
{
    public enum SoundSpawnLocationMode {
        MasterAudioLocation,    // 主音频位置:任何声音组都将从主音频的位置发出
        CallerLocation,         // 呼叫者位置:这将从该游戏对象的位置触发声音组
        AttachToCaller          // 附加到呼叫者:默认值。这实际上不会重新设置Variation游戏对象,但它将遵循具有 Event Sounds 脚本的游戏对象的位置。这样,当物体消失或被场景更改破坏时,声音不会被切断或变化对象被破坏。
    }
    
    public enum FootstepTriggerMode {
        None,
        OnCollisionEnter,
        OnCollisionExit,
        OnTriggerEnter,
        OnTriggerExit,
        OnCollision2D,
        OnTriggerEnter2D
    }
    
    [Header("声音生成模式")]
    public SoundSpawnLocationMode soundSpawnMode = SoundSpawnLocationMode.CallerLocation;
    [Header("脚步声触发事件")]
    public FootstepTriggerMode footstepEvent = FootstepTriggerMode.OnTriggerEnter;

    [Header("限制触发间距模式")]
    public EventSounds.RetriggerLimMode retriggerLimitMode = EventSounds.RetriggerLimMode.FrameBased;
    [Header("显示帧数")]
    public int limitPerXFrm = 10000;
    [Header("限制秒数")]
    public float limitPerXSec = 0f;
    [HideInInspector]
    public int triggeredLastFrame = -100;
    [HideInInspector]
    public float triggeredLastTime = -100f;
    // ReSharper restore InconsistentNaming

    [HideInInspector,Header("使用层筛选")]
    public bool useLayerFilter = true;
    [HideInInspector,Header("使用标签筛选")]    // 暂不使用
    public bool userTagFilter = false;
    
    [Header("脚步声音频")]
    public List<FootStepAudioInfo> footstepAudioInfoList = new List<FootStepAudioInfo>();

    private Transform _trans;

    private void Start()
    {
        
    }

    private double t = 0;
    // ReSharper disable once UnusedMember.Local
    private void OnTriggerEnter(Collider other) {
        if (footstepEvent != FootstepTriggerMode.OnTriggerEnter) {
            return;
        }
        PlaySoundsIfMatch(other.gameObject);
    }

    private void OnTriggerExit(Collider other)
    {
        if (footstepEvent != FootstepTriggerMode.OnTriggerExit) {
            return;
        }
        PlaySoundsIfMatch(other.gameObject);
    }

    // ReSharper disable once UnusedMember.Local
    private void OnCollisionEnter(Collision collision) {
        if (footstepEvent != FootstepTriggerMode.OnCollisionEnter) {
            return;
        }

        PlaySoundsIfMatch(collision.gameObject);
    }

    private void OnCollisionExit(Collision collision)
    {
        if (footstepEvent != FootstepTriggerMode.OnCollisionExit) {
            return;
        }
        PlaySoundsIfMatch(collision.gameObject);
    }

    // ReSharper disable once UnusedMember.Local
    private void OnCollisionEnter2D(Collision2D collision) {
        if (footstepEvent != FootstepTriggerMode.OnCollision2D) {
            return;
        }

        PlaySoundsIfMatch(collision.gameObject);
    }

    // ReSharper disable once UnusedMember.Local
    private void OnTriggerEnter2D(Collider2D other) {
        if (footstepEvent != FootstepTriggerMode.OnTriggerEnter2D) {
            return;
        }

        PlaySoundsIfMatch(other.gameObject);
    }

    private bool CheckForRetriggerLimit() {
        // check for limiting restraints
        switch (retriggerLimitMode) {
            case EventSounds.RetriggerLimMode.FrameBased:
                if (triggeredLastFrame > 0 && AudioUtil.FrameCount - triggeredLastFrame < limitPerXFrm) {
                    return false;
                }
                break;
            case EventSounds.RetriggerLimMode.TimeBased:
                if (triggeredLastTime > 0 && AudioUtil.Time - triggeredLastTime < limitPerXSec) {
                    return false;
                }
                break;
        }

        return true;
    }

    private void PlaySoundsIfMatch(GameObject go) {
        if (!CheckForRetriggerLimit()) {
            return;
        }

        // set the last triggered time or frame
        switch (retriggerLimitMode) {
            case EventSounds.RetriggerLimMode.FrameBased:
                triggeredLastFrame = AudioUtil.FrameCount;
                break;
            case EventSounds.RetriggerLimMode.TimeBased:
                triggeredLastTime = AudioUtil.Time;
                break;
        }

        // ReSharper disable once ForCanBeConvertedToForeach
        for (var i = 0; i < footstepAudioInfoList.Count; i++) {
            var aGroup = footstepAudioInfoList[i];

            // check filters for matches if turned on
            if (useLayerFilter && !aGroup.layerMatchs.Contains(go.layer)) {
                continue;
            }
            if (userTagFilter && !aGroup.tagMatchs.Contains(go.tag)) {
                continue;
            }

            var volume = aGroup.volume;
            float pitch = aGroup.pitch;

            switch (soundSpawnMode) {
                case SoundSpawnLocationMode.CallerLocation:
                    AudioSystem.Instance.PlaySound3DAtTransform(AudioType.Footstep,aGroup.path,Trans,volume,false,pitch);
                    break;
                case SoundSpawnLocationMode.AttachToCaller:
                    AudioSystem.Instance.PlaySound3DFollowTransform(AudioType.Footstep,aGroup.path,Trans,volume,false,pitch);
                    break;
                case SoundSpawnLocationMode.MasterAudioLocation:
                    AudioSystem.Instance.PlayAudio(AudioType.Footstep,aGroup.path,volume,false);
                    break;
            }
        }
    }

    private Transform Trans {
        get {
            if (_trans != null) {
                return _trans;
            }

            _trans = transform;

            return _trans;
        }
    }
}

[Serializable]
public class FootStepAudioInfo
{
    [HideInInspector]
    public string name = "";
    public AudioClip clip = null;
    [Tooltip("路径")] 
    public string path = "";
    [Tooltip("音量"),Range(0,1)] 
    public float volume = 1;
    [Tooltip("音高"),Range(0,3)] 
    public float pitch = 1;
    [Header("层筛选"), LayerPropertyAttribute]
    public List<int> layerMatchs = null;
    [HideInInspector,Header("标签筛选"), TagPropertyAttribute]
    public List<string> tagMatchs = null;
    public void Reset()
    {
        name = string.Empty;
        clip = null;
        path = "";
        volume = 1;
        pitch = 1;
        layerMatchs = null;
        tagMatchs = null;
    }
}

public class LayerPropertyAttribute:PropertyAttribute {}
public class TagPropertyAttribute:PropertyAttribute {}

Editor文件夹下的编辑器脚本:

FootstepSoundsUnitEditor:
using System;
using System.Collections.Generic;
using System.IO;
using DarkTonic.MasterAudio;
using UnityEditor;

[CustomEditor(typeof(FootstepSoundsUnit))]
public class FootstepSoundsUnitEditor:Editor
{
    private List<FootStepAudioInfo> footStepAudioInfoList;
    private void Awake()
    {
        FootstepSoundsUnit config = target as FootstepSoundsUnit;
        footStepAudioInfoList = config.footstepAudioInfoList;
    }

    public override void OnInspectorGUI()
    {
        for (int i = 0; i < footStepAudioInfoList.Count; i++)
        {
            var config = footStepAudioInfoList[i];
            if (config.clip == null)
            {
                config.Reset();
            }
            string path = config.clip == null ? string.Empty : AssetDatabase.GetAssetPath(config.clip);
            config.path = path == string.Empty ? string.Empty : GetAudioPath(path);
            config.name = path == string.Empty ? string.Empty : Path.GetFileNameWithoutExtension(path);
        }
        base.OnInspectorGUI();
    }

    private string GetAudioPath(string path)
    {
        string _path = path.Replace(Path.GetExtension(path), String.Empty);
        return _path.Replace("Assets/Resources/", string.Empty);
    }
}
    
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(LayerPropertyAttribute))]
public class LayerPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // LayerPropertyAttribute info = base.attribute as LayerPropertyAttribute;
        property.intValue = EditorGUI.LayerField(position,"layer Match ", property.intValue);
    }
}

[CustomPropertyDrawer(typeof(TagPropertyAttribute))]
public class TagPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // TagPropertyAttribute info = base.attribute as TagPropertyAttribute;
        property.stringValue = EditorGUI.TagField(position,"tag Match ", property.stringValue);
    }
}

到了这里,关于Unity 音频插件 - MasterAudio 实现音频管理系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Taurus .Net Core 微服务开源框架:Admin 插件【2】 - 系统环境信息管理 - 【OS、Assembly】

    继上篇:Taurus .Net Core 微服务开源框架:Admin 插件【1】 - 微服务节点管理 本篇继续介绍下一个内容:  基本信息如上图,重点的几个参数: 其它就不另外单独翻译了。 默认 NetCore 整体程序集加载的有点多,达100多个。 默认网关的应用程序的程序集是3个,其余全是公共运行

    2024年02月11日
    浏览(43)
  • SkeyeVSS综合安防监控Onvif、RTSP、GB28181视频云无插件直播点播解决方案之系统参数配置日志管理

    SkeyeVSS综合安防监控Onvif、RTSP、GB28181视频云无插件直播点播解决方案之系统参数配置日志管理 1、系统参数配置 SkeyeVSS系统参数配置页面如下图所示: 其中包括: 服务器名称 标识为当前SkeyeVSS视频云系统平台的名称,一般以中心管理服务名称命名,也可以自定义设置名称 唯

    2023年04月22日
    浏览(47)
  • 驾校管理系统的设计与实现/驾校信息管理系统

    摘  要随着社会的发展,车辆也越来越多,计算机的优势和普及使得驾校管理系统的开发成为必需。驾校管理系统主要是借助计算机,通过对信息进行管理。减少管理员的工作,同时也方便广大学员对个人所需信息的及时查询以及管理,其次是大量信息的管理,最后是高度安

    2024年02月07日
    浏览(42)
  • 超市商品信息管理系统/超市管理系统的设计与实现

    摘  要 随着现在网络的快速发展,网上管理系统也逐渐快速发展起来,网上管理模式很快融入到了许多国家的之中,随之就产生了“超市商品信息管理系统”,这样就让超市商品信息管理系统更加方便简单。 对于本超市商品信息管理系统的设计来说,系统开发主要是采用j

    2024年02月02日
    浏览(40)
  • qt实现信息管理系统(学生信息管理系统)功能更完善

    信息系统代码地址:https://gitee.com/dxl96/StdMsgSystem 本学生信息管理系统同升级改造的幅度较大,涉及到的东西对于初学者来说,可能稍显复杂,可以先移步到 https://blog.csdn.net/IT_CREATE/article/details/82027462 查看简易的系统。 本系统引入日志管理,数据库选择支持sqllite、mysql,自

    2024年02月13日
    浏览(44)
  • 停车场收费管理系统/停车场管理系统的设计与实现

    摘  要 ABSTRACT 目   录 第 1 章 绪论 1.1背景及意义 1.2 国内外研究概况 1.3 研究的内容 第2章 相关技术 2.1 JSP技术介绍 2.2 SSM三大框架 2.3 MyEclipse开发环境 2.4 Tomcat服务器 2.5 MySQL数据库 第3章 系统分析 3.1 需求分析 3.2 系统可行性分析 3.2.1技术可行性:技术背景 3.2.2经济可行性

    2024年02月12日
    浏览(50)
  • 毕业论文答辩管理系统/毕业论文管理系统的设计与实现

    毕业论文答辩管理系统 摘要 毕业论文答辩管理 是为 学生 提供 毕业 服务的系统,是信息时代 学校 通过联网向外界提供 论文答辩 服务的内容,是 学校 进行 论文答辩 改革,尤其是服务改革的重要举措,该 毕业论文答辩 的运用可以促进 学生 、 教师交流 问题的解决或缓解。节省

    2024年02月05日
    浏览(43)
  • 基于Web小区物业管理系统/物业管理系统的设计与实现

    目    录 第一章 绪论 1.1 研究背景 1.2 主要研究内容 第二章 相关技术 2.1 MySQL技术介绍 2.2 eclipse编译器介绍 2.3 Spring框架介绍 2.4 MyBatis 框架介绍 2.5 Spring MVC框架简介 第三章 系统分析 3.1 可行性分析 3.1.1  技术可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.2 系统性能分析 3.3系统需

    2024年02月06日
    浏览(44)
  • 基于python的停车场管理系统的设计与实现/智能停车管理系统

    车位信息 是 停车场供应用户 必不可少的一个部分。在 停车场发展 的整个过程中, 车位信息 担负着最重要的角色。为满足如今日益复杂的管理需求,各类 系统管理 程序也在不断改进。本课题所设计的 停车场管理系统 , 使用 Django 框架 , Python语言 进行开发,它的优点代

    2024年02月10日
    浏览(40)
  • 智慧社区管理系统助力实现社区数字化管理

    社区是人们生活的重要场所,传统社区的运营及管理方式已经无法满足人们日益增长的物质和文化生活需要。社区的通讯、安全和社区居民信息的收集、处理及共享等问题都成为阻碍社区服务进一步发展的瓶颈。 智慧社区是利用物联网、云计算、移动互联网、信息智能终端等

    2024年02月11日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包