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

Posted t_z_l

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

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

/*
 * 物体与声音绑定配置表
 * 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播放不同的音效

/*
 * 脚步声单元
 * 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);
    

使用 Unity3d 制作 VST

【中文标题】使用 Unity3d 制作 VST【英文标题】:Make a VST with Unity3d 【发布时间】:2015-11-30 15:14:10 【问题描述】:

是否可以使用 Unity3d 构建 VST?

似乎可以统一构建音频应用程序。

但我想知道是否可以将 DLL 构建为 unityapp?是否可以以某种方式将 VST sdk 与最终构建集成?

【问题讨论】:

【参考方案1】:

Unity 可以在大多数架构上使用本机插件。看起来 VST 是一个原生的 windows dll,你可以用DllImport 运行这些。

在 Unity 中,所有音频都存储在 AudioClips 中,您可以使用 Create、Set 和 Get 修改其数据。您还可以将脚本与编辑器中的音频导入挂钩,以便在加载时使用 AssetPostprocessor.OnPostprocessAudio 更改它。

因此,您必须使用 VST 过滤器对 AudioClip 进行某种类型的 AudioClip 过滤器。

使用 Unity 的一个建议 - 精确的时间安排很困难,因为一切都与主游戏循环相关联,并且它(理想情况下)以显示器刷新的频率运行。如果你想要更多的实时性,你需要将你自己的线程放在后台——它的工作方式与网络上的常见建议相反,只要记住 Unity 中几乎没有线程安全的,所以要更新游戏状态或 AudioClips,您需要在游戏循环中执行此操作。

【讨论】:

这是一个很好的答案,所以把它写成正确的。如果您知道是否可以导出 dll 文件(或用于 mac 的 .vst),将不胜感激。我只能在导出器中看到几个选项。还是我必须以某种方式使用 VisualStudio 等构建 dll? 我不认为这回答了问题,这是关于你是否可以使用 Unity 制作 VST,而不是你是否可以在 Unity 中使用 VST 插件【参考方案2】:

据我所知,至少开箱即用,您不能将导出的 Unity 代码包装在 VST 所需的框架中。这是妨碍在插件主机(例如:DAW)中使用 Unity 接口的步骤。

另一种方法是创建一个 VST/AU 插件,该插件与在插件主机之外运行的 Unity 应用程序通信。

【讨论】:

以上是关于Unity 音频插件 - MasterAudio 实现音频管理系统的主要内容,如果未能解决你的问题,请参考以下文章

unity中如何播放音频的指定区间?

slua,ulua,nlua,unilua这几种unity3D的lua插件各有啥优劣

Google Resonance + Unity 2017.3.1f1 + GearVR(Galaxy 8)

Unity3.5 导入音频文件

Unity 之 加载工程卡在音频处不动(Unity识别不出音频文件)

Unity - 简单实现音频管理系统