您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
461 行
16 KiB
461 行
16 KiB
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Audio;
|
|
using UnityEngine.Profiling;
|
|
using Random = UnityEngine.Random;
|
|
|
|
public class SoundSystem
|
|
{
|
|
[ConfigVar(Name = "sound.debug", DefaultValue = "0", Description = "Enable sound debug overlay")]
|
|
public static ConfigVar soundDebug;
|
|
|
|
[ConfigVar(Name = "sound.numemitters", DefaultValue = "48", Description = "Number of sound emitters")]
|
|
public static ConfigVar soundNumEmitters;
|
|
|
|
[ConfigVar(Name = "sound.spatialize", DefaultValue = "1", Description = "Use spatializer")]
|
|
public static ConfigVar soundSpatialize;
|
|
|
|
[ConfigVar(Name = "sound.mute", DefaultValue = "-1", Description = "Is audio enabled. -1 causes default behavior (on when window has focus)", Flags = ConfigVar.Flags.None)]
|
|
public static ConfigVar soundMute;
|
|
|
|
// Debugging only
|
|
[ConfigVar(Name = "sound.mastervol", DefaultValue = "1", Description = "Master volume", Flags = ConfigVar.Flags.None)]
|
|
public static ConfigVar soundMasterVol;
|
|
|
|
// Exposed in options menu
|
|
[ConfigVar(Name = "sound.menuvol", DefaultValue = "1", Description = "Menu volume", Flags = ConfigVar.Flags.None)]
|
|
public static ConfigVar soundMenuVol;
|
|
[ConfigVar(Name = "sound.sfxvol", DefaultValue = "1", Description = "SFX volume", Flags = ConfigVar.Flags.None)]
|
|
public static ConfigVar soundSFXVol;
|
|
[ConfigVar(Name = "sound.musicvol", DefaultValue = "1", Description = "Music volume", Flags = ConfigVar.Flags.None)]
|
|
public static ConfigVar soundMusicVol;
|
|
|
|
// These are passed to the game code
|
|
public struct SoundHandle
|
|
{
|
|
public SoundEmitter emitter;
|
|
public int seq;
|
|
public bool IsValid() { return emitter != null && emitter.seqId == seq; }
|
|
public SoundHandle(SoundEmitter e)
|
|
{
|
|
emitter = e;
|
|
seq = e != null ? e.seqId : -1;
|
|
}
|
|
}
|
|
|
|
// These are internal to the SoundSystem
|
|
public class SoundEmitter
|
|
{
|
|
public AudioSource source;
|
|
public SoundDef soundDef;
|
|
public bool playing;
|
|
public int repeatCount;
|
|
public Interpolator fadeToKill;
|
|
public int seqId;
|
|
|
|
internal void Kill()
|
|
{
|
|
source.Stop();
|
|
repeatCount = 0;
|
|
}
|
|
};
|
|
|
|
AudioSource MakeAudioSource()
|
|
{
|
|
var go = new GameObject("SoundSystemSource");
|
|
go.transform.parent = m_SourceHolder.transform;
|
|
return go.AddComponent<AudioSource>();
|
|
}
|
|
|
|
static AudioMixerGroup[] s_MixerGroups;
|
|
public void Init(AudioMixer mixer)
|
|
{
|
|
m_SourceHolder = new GameObject("SoundSystemSources");
|
|
GameObject.DontDestroyOnLoad(m_SourceHolder);
|
|
m_AudioMixer = mixer;
|
|
GameDebug.Log("SoundSystem using mixer: " + m_AudioMixer.name);
|
|
m_SequenceId = 0;
|
|
|
|
// Create pool of emitters
|
|
m_Emitters = new SoundEmitter[soundNumEmitters.IntValue];
|
|
for (var i = 0; i < soundNumEmitters.IntValue; i++)
|
|
{
|
|
var emitter = new SoundEmitter();
|
|
emitter.source = MakeAudioSource();
|
|
emitter.fadeToKill = new Interpolator(1.0f, Interpolator.CurveType.Linear);
|
|
m_Emitters[i] = emitter;
|
|
}
|
|
|
|
// Set up mixer groups
|
|
s_MixerGroups = new AudioMixerGroup[(int)SoundMixerGroup._Count];
|
|
s_MixerGroups[(int)SoundMixerGroup.Menu] = m_AudioMixer.FindMatchingGroups("Menu")[0];
|
|
s_MixerGroups[(int)SoundMixerGroup.Music] = m_AudioMixer.FindMatchingGroups("Music")[0];
|
|
s_MixerGroups[(int)SoundMixerGroup.SFX] = m_AudioMixer.FindMatchingGroups("SFX")[0];
|
|
}
|
|
|
|
public struct SoundReq
|
|
{
|
|
public SoundDef def;
|
|
public bool usePos;
|
|
public Vector3 pos;
|
|
public Transform transform;
|
|
}
|
|
|
|
SoundEmitter AllocEmitter()
|
|
{
|
|
// Look for unused emitter
|
|
foreach (var e in m_Emitters)
|
|
{
|
|
if (!e.playing)
|
|
{
|
|
e.seqId = m_SequenceId++;
|
|
return e;
|
|
}
|
|
}
|
|
|
|
// Hunt down one emitter to kill
|
|
SoundEmitter emitter = null;
|
|
float distance = float.MinValue;
|
|
var listenerPos = m_CurrentListener != null ? m_CurrentListener.transform.position : Vector3.zero;
|
|
foreach(var e in m_Emitters)
|
|
{
|
|
var s = e.source;
|
|
|
|
// Skip looping
|
|
if (s.loop)
|
|
continue;
|
|
|
|
// Pick closest; assuming 2d sounds very close!
|
|
var dist = 0.0f;
|
|
if(s.spatialBlend > 0.0f)
|
|
{
|
|
dist = (s.transform.position - listenerPos).magnitude;
|
|
|
|
// if tracking another object assume closer
|
|
var t = s.transform;
|
|
if (t.parent != m_SourceHolder.transform)
|
|
dist *= 0.5f;
|
|
}
|
|
|
|
if(dist > distance)
|
|
{
|
|
distance = dist;
|
|
emitter = e;
|
|
}
|
|
|
|
}
|
|
if(emitter != null)
|
|
{
|
|
emitter.Kill();
|
|
emitter.seqId = m_SequenceId++;
|
|
return emitter;
|
|
}
|
|
GameDebug.Log("Unable to allocate sound emitter!");
|
|
return null;
|
|
}
|
|
|
|
public SoundHandle Play(WeakSoundDef weakSoundDef)
|
|
{
|
|
SoundDef def;
|
|
return m_SoundDefs.TryGetValue(weakSoundDef.guid , out def) ? Play(def) : new SoundHandle(null);
|
|
}
|
|
public SoundHandle Play(SoundDef soundDef)
|
|
{
|
|
GameDebug.Assert(soundDef != null);
|
|
|
|
var e = AllocEmitter();
|
|
|
|
if (e == null)
|
|
return new SoundHandle(null);
|
|
|
|
if (soundDef.spatialBlend > 0.0f)
|
|
GameDebug.LogWarning(string.Format("Playing 3d {0} sound at 0,0,0", soundDef.name));
|
|
|
|
e.source.transform.position = new Vector3(0, 0, 0);
|
|
e.repeatCount = Random.Range(soundDef.repeatMin, soundDef.repeatMax);
|
|
e.playing = true;
|
|
e.soundDef = soundDef;
|
|
StartEmitter(e);
|
|
return new SoundHandle(e);
|
|
}
|
|
|
|
public SoundHandle Play(SoundDef soundDef, Vector3 position)
|
|
{
|
|
var e = AllocEmitter();
|
|
|
|
if (e == null)
|
|
return new SoundHandle(null);
|
|
|
|
e.source.transform.position = position;
|
|
e.repeatCount = Random.Range(soundDef.repeatMin, soundDef.repeatMax);
|
|
e.playing = true;
|
|
e.soundDef = soundDef;
|
|
StartEmitter(e);
|
|
return new SoundHandle(e);
|
|
}
|
|
|
|
/*
|
|
public SoundHandle Play(WeakSoundDef weakSoundDef, Transform parent)
|
|
{
|
|
SoundDef def;
|
|
return m_SoundDefs.TryGetValue(weakSoundDef.guid, out def) ? Play(def, parent) : new SoundHandle(null);
|
|
}
|
|
*/
|
|
|
|
public SoundHandle Play(SoundDef soundDef, Transform parent)
|
|
{
|
|
Profiler.BeginSample("SoundSystem.AllocEmitter");
|
|
var e = AllocEmitter();
|
|
Profiler.EndSample();
|
|
|
|
if (e == null)
|
|
return new SoundHandle(null);
|
|
|
|
e.source.transform.parent = parent;
|
|
e.source.transform.localPosition = Vector3.zero;
|
|
e.repeatCount = Random.Range(soundDef.repeatMin, soundDef.repeatMax);
|
|
e.playing = true;
|
|
e.soundDef = soundDef;
|
|
Profiler.BeginSample("SoundSystem.StartEmitter");
|
|
StartEmitter(e);
|
|
Profiler.EndSample();
|
|
return new SoundHandle(e);
|
|
}
|
|
|
|
public void Stop(SoundHandle sh, float fadeOutTime = 0.0f)
|
|
{
|
|
if (!sh.IsValid())
|
|
{
|
|
GameDebug.LogWarning("SoundSystem.Stop(): invalid SoundHandle");
|
|
return;
|
|
}
|
|
if (fadeOutTime == 0.0f)
|
|
sh.emitter.fadeToKill.SetValue(0.0f);
|
|
else
|
|
{
|
|
sh.emitter.fadeToKill.SetValue(1.0f);
|
|
sh.emitter.fadeToKill.MoveTo(0.0f, fadeOutTime);
|
|
}
|
|
}
|
|
|
|
bool focus = false;
|
|
public void Update()
|
|
{
|
|
if(focus != Application.isFocused)
|
|
{
|
|
focus = Application.isFocused;
|
|
if(soundMute.IntValue == -1)
|
|
m_MasterVolume.MoveTo(focus ? 1.0f : 0.0f, 0.5f);
|
|
}
|
|
|
|
var masterVolume = m_MasterVolume.GetValue();
|
|
|
|
if (soundMute.IntValue == 0)
|
|
{
|
|
masterVolume = 0.0f;
|
|
DebugOverlay.Write(Color.red, DebugOverlay.Width - 10, 2, "{0}", "AUDIO MUTED");
|
|
}
|
|
else if (soundMute.IntValue == 1)
|
|
{
|
|
masterVolume = 1.0f;
|
|
DebugOverlay.Write(Color.green, DebugOverlay.Width - 10, 2, "{0}", "AUDIO PLAYING");
|
|
}
|
|
|
|
m_AudioMixer.SetFloat("MasterVolume", DecibelFromAmplitude(Mathf.Clamp(soundMasterVol.FloatValue, 0.0f, 1.0f) * masterVolume));
|
|
m_AudioMixer.SetFloat("MusicVolume", DecibelFromAmplitude(Mathf.Clamp(soundMusicVol.FloatValue, 0.0f, 1.0f)));
|
|
m_AudioMixer.SetFloat("SFXVolume", DecibelFromAmplitude(Mathf.Clamp(soundSFXVol.FloatValue, 0.0f, 1.0f)));
|
|
m_AudioMixer.SetFloat("MenuVolume", DecibelFromAmplitude(Mathf.Clamp(soundMenuVol.FloatValue, 0.0f, 1.0f)));
|
|
|
|
// Update running sounds
|
|
int count = 0;
|
|
foreach (var e in m_Emitters)
|
|
{
|
|
if (!e.playing)
|
|
continue;
|
|
if (e.source == null)
|
|
{
|
|
// Could happen if parent was killed. Not good, but fixable:
|
|
GameDebug.LogWarning("Soundemitter had its audiosource destroyed. Making a new.");
|
|
e.source = MakeAudioSource();
|
|
e.repeatCount = 0;
|
|
}
|
|
if (e.fadeToKill.IsMoving())
|
|
{
|
|
e.source.volume = AmplitudeFromDecibel(e.soundDef.volume) * e.fadeToKill.GetValue();
|
|
}
|
|
else if (e.fadeToKill.GetValue() == 0.0f)
|
|
{
|
|
// kill no matter what
|
|
e.Kill();
|
|
}
|
|
if (e.source.isPlaying)
|
|
{
|
|
count++;
|
|
continue;
|
|
}
|
|
if (e.repeatCount > 1)
|
|
{
|
|
e.repeatCount--;
|
|
StartEmitter(e);
|
|
continue;
|
|
}
|
|
// Reset for reuse
|
|
e.playing = false;
|
|
e.source.transform.parent = m_SourceHolder.transform;
|
|
e.source.enabled = true;
|
|
e.source.gameObject.SetActive(true);
|
|
e.source.transform.position = Vector3.zero;
|
|
e.fadeToKill.SetValue(1.0f);
|
|
}
|
|
if (soundDebug.IntValue > 0)
|
|
{
|
|
DebugOverlay.Write(30, 1, "Mixer: {0} {1}", m_AudioMixer.GetInstanceID(), Game.game.audioMixer.GetInstanceID());
|
|
int ii = 4;
|
|
foreach(var o in GameObject.FindObjectsOfType<AudioMixerGroup>())
|
|
{
|
|
DebugOverlay.Write(30, ii++, "group: {0} {1}", o.name, o.GetInstanceID());
|
|
}
|
|
DebugOverlay.Write(1, 1, "Num audios {0}", count);
|
|
for (int i = 0, c = m_Emitters.Length; i < c; ++i)
|
|
{
|
|
var e = m_Emitters[i];
|
|
DebugOverlay.Write(1, 3 + i, "Emitter {0:##} {1} {2} {3}", i, e.playing ? e.soundDef.name : "<n/a>", e.source.gameObject.activeInHierarchy ? "act":"nact", e.playing ? "Mixer: " +e.source.outputAudioMixerGroup.audioMixer.GetInstanceID() : "");
|
|
}
|
|
if(m_CurrentListener == null)
|
|
{
|
|
DebugOverlay.Write(DebugOverlay.Width / 2 - 5, DebugOverlay.Height, "No AudioListener?");
|
|
return;
|
|
}
|
|
for (int i = 0, c = m_Emitters.Length; i < c; ++i)
|
|
{
|
|
var e = m_Emitters[i];
|
|
if (!e.playing)
|
|
continue;
|
|
var s = e.source.spatialBlend;
|
|
Vector3 locpos = m_CurrentListener.transform.InverseTransformPoint(e.source.transform.position);
|
|
float x = Mathf.Lerp(e.source.panStereo*10.0f, Mathf.Clamp(locpos.x, -10, 10), s); ;
|
|
float z = Mathf.Lerp(-10.0f, Mathf.Clamp(locpos.z, -10, 10), s);
|
|
DebugOverlay.Write(Color.Lerp(Color.green, Color.blue, s), DebugOverlay.Width / 2 + x, DebugOverlay.Height / 2 - z, "{0} ({1:##.#})", e.soundDef.name, locpos.magnitude);
|
|
}
|
|
}
|
|
}
|
|
|
|
void StartEmitter(SoundEmitter emitter)
|
|
{
|
|
var soundDef = emitter.soundDef;
|
|
var source = emitter.source;
|
|
|
|
StartSource(source, soundDef);
|
|
}
|
|
|
|
public static void StartSource(AudioSource source, SoundDef soundDef)
|
|
{
|
|
Profiler.BeginSample(".Set source clip");
|
|
source.clip = soundDef.clips[Random.Range(0, soundDef.clips.Count)];
|
|
Profiler.EndSample();
|
|
|
|
Profiler.BeginSample(".Setup source");
|
|
// Map from halftone space to linear playback multiplier
|
|
source.pitch = Mathf.Pow(2.0f, Random.Range(soundDef.pitchMin, soundDef.pitchMax) / 12.0f);
|
|
source.minDistance = soundDef.distMin;
|
|
source.maxDistance = soundDef.distMax;
|
|
source.volume = AmplitudeFromDecibel(soundDef.volume);
|
|
source.loop = soundDef.loopCount < 1 ? true : false;
|
|
source.rolloffMode = soundDef.rolloffMode;
|
|
float delay = Random.Range(soundDef.delayMin, soundDef.delayMax);
|
|
if(s_MixerGroups != null)
|
|
source.outputAudioMixerGroup = s_MixerGroups[(int)soundDef.soundGroup];
|
|
source.spatialBlend = soundDef.spatialBlend;
|
|
source.panStereo = Random.Range(soundDef.panMin, soundDef.panMax);
|
|
Profiler.EndSample();
|
|
|
|
// soundSpatialize can be null as this is run from editor too
|
|
Profiler.BeginSample(".Setup spatializer");
|
|
if(soundSpatialize != null && soundSpatialize.IntValue > 0 && soundDef.spatialBlend > 0.5f)
|
|
{
|
|
source.spatialize = true;
|
|
source.SetSpatializerFloat(0, 8.0f);
|
|
source.SetSpatializerFloat(1, 0.0f);
|
|
//source.SetSpatializerFloat(2, soundDef.distMin);
|
|
//source.SetSpatializerFloat(3, soundDef.distMax);
|
|
source.SetSpatializerFloat(4, 0.0f);
|
|
source.SetSpatializerFloat(5, 0.0f);
|
|
source.spatializePostEffects = false;
|
|
//source.rolloffMode = source.spatialize ? AudioRolloffMode.Linear : source.rolloffMode;
|
|
}
|
|
else
|
|
{
|
|
source.spatialize = false;
|
|
}
|
|
Profiler.EndSample();
|
|
|
|
// TODO (petera) can we remove this? -- should never be needed due to re-enabling code in main update loop
|
|
if (!source.enabled)
|
|
{
|
|
GameDebug.Log("Fixing disabled soundsource");
|
|
source.enabled = true;
|
|
}
|
|
|
|
Profiler.BeginSample("AudioSource.Play");
|
|
if (delay > 0.0f)
|
|
source.PlayDelayed(delay);
|
|
else
|
|
source.Play();
|
|
Profiler.EndSample();
|
|
}
|
|
|
|
public void MountBank(SoundBank bank)
|
|
{
|
|
GameDebug.Assert(bank.soundDefs.Count == bank.soundDefGuids.Count);
|
|
for (int i = 0, c = bank.soundDefGuids.Count; i < c; ++i)
|
|
{
|
|
m_SoundDefs[bank.soundDefGuids[i]] = bank.soundDefs[i];
|
|
}
|
|
GameDebug.Log("Mounted soundbank: " + bank.name + " with " + bank.soundDefGuids.Count + " sounds");
|
|
}
|
|
|
|
public void UnmountBank(SoundBank bank)
|
|
{
|
|
GameDebug.Assert(bank.soundDefs.Count == bank.soundDefGuids.Count);
|
|
for (int i = 0, c = bank.soundDefGuids.Count; i < c; ++i)
|
|
{
|
|
m_SoundDefs.Remove(bank.soundDefGuids[i]);
|
|
}
|
|
GameDebug.Log("Unmounted soundbank: " + bank.name + " with " + bank.soundDefGuids.Count + " sounds");
|
|
}
|
|
|
|
internal void SetCurrentListener(AudioListener audioListener)
|
|
{
|
|
m_CurrentListener = audioListener;
|
|
}
|
|
|
|
public static float SOUND_VOL_CUTOFF = -60.0f;
|
|
public static float SOUND_AMP_CUTOFF = Mathf.Pow(2.0f, SOUND_VOL_CUTOFF / 6.0f);
|
|
|
|
public static float DecibelFromAmplitude(float amplitude)
|
|
{
|
|
if (amplitude < SOUND_AMP_CUTOFF)
|
|
return -60.0f;
|
|
return 6.0f * Mathf.Log(amplitude) / Mathf.Log(2.0f);
|
|
}
|
|
|
|
public static float AmplitudeFromDecibel(float decibel)
|
|
{
|
|
if (decibel <= SOUND_VOL_CUTOFF)
|
|
{
|
|
return 0;
|
|
}
|
|
return Mathf.Pow(2.0f, decibel / 6.0f);
|
|
}
|
|
|
|
Dictionary<string, SoundDef> m_SoundDefs = new Dictionary<string, SoundDef>();
|
|
AudioMixer m_AudioMixer;
|
|
int m_SequenceId;
|
|
SoundEmitter[] m_Emitters;
|
|
GameObject m_SourceHolder;
|
|
AudioListener m_CurrentListener;
|
|
Interpolator m_MasterVolume = new Interpolator(1.0f, Interpolator.CurveType.SmoothStep);
|
|
|
|
}
|