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

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Master Audio 为你带来超卓的易用性、速度、功能性和灵活性,实力远超任何竞争对手。为什么要使用其他性能不足的解决方案,浪费自己写代码的时间?无论是简单还是复杂的场景,我们都准备好了代码,让你高枕无忧!如果你没有 Playmaker,我们甚至还有“零代码”基于事件的脚本来处理声音。 :: 选项丰富的音频遮挡功能! :: 真正随机*加权* 的声音变化。 :: 网格/样条音频 - 将音源放置在最接近碰撞器的部分 - 河流和其他环境音频的完美选择! :: 多种类型的高级功能,例如语音限制和限时的重新触发。 :: 节省内存!自动从资源文件加载和卸载音频,包括超易用的多语言支持! :: 身临其境! 设置带有交叉淡化或无缝过渡的多层次音乐! :: 节省时间!实时游戏时进行混合,还可以选择在停止时保留这些更改! :: 简单!设置每个场景或全局性声音和音轨,无需编写任何代码! :: 无需编写代码!强大而灵活的基于事件的脚本,没有代码也能控制声音和播放列表! :: 爆炸效果!内置按声音区分的音乐骤降! :: 力道十足!支持 Unity 音频过滤器效果。Master Audio 专属功能! :: 欢乐有趣!点唱机能够实时测试控制你的播放列表! :: 播放时不会实例化!在所有平台上的表现无可匹敌,同时具有超低的分配单元! :: 完全控制!通过专业混音器控件获得精细化控制,包括总线和静音/独奏开关! :: 现在还免费赠送 Bulk Audio Importer! :: 全 5 星好评,请查看评论区的用户反馈! 支持所有导出平台,为移动游戏提供超卓性能。 设置极其简单快速,即插即用。直观的控制界面意味着快捷的调控和优化的工作流程,让你有更多时间来做更重要的事情。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值