插件介绍:
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文件夹下的编辑器脚本:文章来源:https://www.toymoban.com/news/detail-409077.html
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播放不同的音效
文章来源地址https://www.toymoban.com/news/detail-409077.html
/*
* 脚步声单元
* 2022.11.9
* linYiShan
*/
using System;
using System.Collections.Generic;
using DarkTonic.MasterAudio;
using UnityEngine;
public class FootstepSoundsUnit:MonoBehaviour
{
public enum SoundSpawnLocationMode {
MasterAudioLocation, // 主音频位置:任何声音组都将从主音频的位置发出
CallerLocation, // 呼叫者位置:这将从该游戏对象的位置触发声音组
AttachToCaller // 附加到呼叫者:默认值。这实际上不会重新设置Variation游戏对象,但它将遵循具有 Event Sounds 脚本的游戏对象的位置。这样,当物体消失或被场景更改破坏时,声音不会被切断或变化对象被破坏。
}
public enum FootstepTriggerMode {
None,
OnCollisionEnter,
OnCollisionExit,
OnTriggerEnter,
OnTriggerExit,
OnCollision2D,
OnTriggerEnter2D
}
[Header("声音生成模式")]
public SoundSpawnLocationMode soundSpawnMode = SoundSpawnLocationMode.CallerLocation;
[Header("脚步声触发事件")]
public FootstepTriggerMode footstepEvent = FootstepTriggerMode.OnTriggerEnter;
[Header("限制触发间距模式")]
public EventSounds.RetriggerLimMode retriggerLimitMode = EventSounds.RetriggerLimMode.FrameBased;
[Header("显示帧数")]
public int limitPerXFrm = 10000;
[Header("限制秒数")]
public float limitPerXSec = 0f;
[HideInInspector]
public int triggeredLastFrame = -100;
[HideInInspector]
public float triggeredLastTime = -100f;
// ReSharper restore InconsistentNaming
[HideInInspector,Header("使用层筛选")]
public bool useLayerFilter = true;
[HideInInspector,Header("使用标签筛选")] // 暂不使用
public bool userTagFilter = false;
[Header("脚步声音频")]
public List<FootStepAudioInfo> footstepAudioInfoList = new List<FootStepAudioInfo>();
private Transform _trans;
private void Start()
{
}
private double t = 0;
// ReSharper disable once UnusedMember.Local
private void OnTriggerEnter(Collider other) {
if (footstepEvent != FootstepTriggerMode.OnTriggerEnter) {
return;
}
PlaySoundsIfMatch(other.gameObject);
}
private void OnTriggerExit(Collider other)
{
if (footstepEvent != FootstepTriggerMode.OnTriggerExit) {
return;
}
PlaySoundsIfMatch(other.gameObject);
}
// ReSharper disable once UnusedMember.Local
private void OnCollisionEnter(Collision collision) {
if (footstepEvent != FootstepTriggerMode.OnCollisionEnter) {
return;
}
PlaySoundsIfMatch(collision.gameObject);
}
private void OnCollisionExit(Collision collision)
{
if (footstepEvent != FootstepTriggerMode.OnCollisionExit) {
return;
}
PlaySoundsIfMatch(collision.gameObject);
}
// ReSharper disable once UnusedMember.Local
private void OnCollisionEnter2D(Collision2D collision) {
if (footstepEvent != FootstepTriggerMode.OnCollision2D) {
return;
}
PlaySoundsIfMatch(collision.gameObject);
}
// ReSharper disable once UnusedMember.Local
private void OnTriggerEnter2D(Collider2D other) {
if (footstepEvent != FootstepTriggerMode.OnTriggerEnter2D) {
return;
}
PlaySoundsIfMatch(other.gameObject);
}
private bool CheckForRetriggerLimit() {
// check for limiting restraints
switch (retriggerLimitMode) {
case EventSounds.RetriggerLimMode.FrameBased:
if (triggeredLastFrame > 0 && AudioUtil.FrameCount - triggeredLastFrame < limitPerXFrm) {
return false;
}
break;
case EventSounds.RetriggerLimMode.TimeBased:
if (triggeredLastTime > 0 && AudioUtil.Time - triggeredLastTime < limitPerXSec) {
return false;
}
break;
}
return true;
}
private void PlaySoundsIfMatch(GameObject go) {
if (!CheckForRetriggerLimit()) {
return;
}
// set the last triggered time or frame
switch (retriggerLimitMode) {
case EventSounds.RetriggerLimMode.FrameBased:
triggeredLastFrame = AudioUtil.FrameCount;
break;
case EventSounds.RetriggerLimMode.TimeBased:
triggeredLastTime = AudioUtil.Time;
break;
}
// ReSharper disable once ForCanBeConvertedToForeach
for (var i = 0; i < footstepAudioInfoList.Count; i++) {
var aGroup = footstepAudioInfoList[i];
// check filters for matches if turned on
if (useLayerFilter && !aGroup.layerMatchs.Contains(go.layer)) {
continue;
}
if (userTagFilter && !aGroup.tagMatchs.Contains(go.tag)) {
continue;
}
var volume = aGroup.volume;
float pitch = aGroup.pitch;
switch (soundSpawnMode) {
case SoundSpawnLocationMode.CallerLocation:
AudioSystem.Instance.PlaySound3DAtTransform(AudioType.Footstep,aGroup.path,Trans,volume,false,pitch);
break;
case SoundSpawnLocationMode.AttachToCaller:
AudioSystem.Instance.PlaySound3DFollowTransform(AudioType.Footstep,aGroup.path,Trans,volume,false,pitch);
break;
case SoundSpawnLocationMode.MasterAudioLocation:
AudioSystem.Instance.PlayAudio(AudioType.Footstep,aGroup.path,volume,false);
break;
}
}
}
private Transform Trans {
get {
if (_trans != null) {
return _trans;
}
_trans = transform;
return _trans;
}
}
}
[Serializable]
public class FootStepAudioInfo
{
[HideInInspector]
public string name = "";
public AudioClip clip = null;
[Tooltip("路径")]
public string path = "";
[Tooltip("音量"),Range(0,1)]
public float volume = 1;
[Tooltip("音高"),Range(0,3)]
public float pitch = 1;
[Header("层筛选"), LayerPropertyAttribute]
public List<int> layerMatchs = null;
[HideInInspector,Header("标签筛选"), TagPropertyAttribute]
public List<string> tagMatchs = null;
public void Reset()
{
name = string.Empty;
clip = null;
path = "";
volume = 1;
pitch = 1;
layerMatchs = null;
tagMatchs = null;
}
}
public class LayerPropertyAttribute:PropertyAttribute {}
public class TagPropertyAttribute:PropertyAttribute {}
Editor文件夹下的编辑器脚本:
FootstepSoundsUnitEditor:
using System;
using System.Collections.Generic;
using System.IO;
using DarkTonic.MasterAudio;
using UnityEditor;
[CustomEditor(typeof(FootstepSoundsUnit))]
public class FootstepSoundsUnitEditor:Editor
{
private List<FootStepAudioInfo> footStepAudioInfoList;
private void Awake()
{
FootstepSoundsUnit config = target as FootstepSoundsUnit;
footStepAudioInfoList = config.footstepAudioInfoList;
}
public override void OnInspectorGUI()
{
for (int i = 0; i < footStepAudioInfoList.Count; i++)
{
var config = footStepAudioInfoList[i];
if (config.clip == null)
{
config.Reset();
}
string path = config.clip == null ? string.Empty : AssetDatabase.GetAssetPath(config.clip);
config.path = path == string.Empty ? string.Empty : GetAudioPath(path);
config.name = path == string.Empty ? string.Empty : Path.GetFileNameWithoutExtension(path);
}
base.OnInspectorGUI();
}
private string GetAudioPath(string path)
{
string _path = path.Replace(Path.GetExtension(path), String.Empty);
return _path.Replace("Assets/Resources/", string.Empty);
}
}
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(LayerPropertyAttribute))]
public class LayerPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// LayerPropertyAttribute info = base.attribute as LayerPropertyAttribute;
property.intValue = EditorGUI.LayerField(position,"layer Match ", property.intValue);
}
}
[CustomPropertyDrawer(typeof(TagPropertyAttribute))]
public class TagPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// TagPropertyAttribute info = base.attribute as TagPropertyAttribute;
property.stringValue = EditorGUI.TagField(position,"tag Match ", property.stringValue);
}
}
到了这里,关于Unity 音频插件 - MasterAudio 实现音频管理系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!