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
实现功能:
- 音效要能与场景物件绑定,要能单独配置音量,支持相关配置方式
- 检查多个音效同时出现时的声音混合是否正常
- 提供动态调整某一大类音效音量的接口
- 脚步声配置
代码实现
这里不细说实现中使用MasterAudio插件的API了(可以直接去翻一下插件文档)。
- 动态创建音频组,提供音频的播放、停止接口
- 提供调节一组音频的音量大小
- 音效与场景物件绑定工具
- 脚步声配置工具
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 实现音频管理系统的主要内容,如果未能解决你的问题,请参考以下文章
slua,ulua,nlua,unilua这几种unity3D的lua插件各有啥优劣
Google Resonance + Unity 2017.3.1f1 + GearVR(Galaxy 8)