您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
554 行
19 KiB
554 行
19 KiB
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Unity.DebugDisplay;
|
|
using Unity.Sample.Core;
|
|
using UnityEngine;
|
|
using UnityEngine.Audio;
|
|
using UnityEngine.Profiling;
|
|
using Random = UnityEngine.Random;
|
|
|
|
|
|
public static 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 = "0", 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 emitter_idx;
|
|
public int seq;
|
|
|
|
public bool IsNull() { return emitter_idx == 0 && seq == 0; }
|
|
|
|
public SoundHandle(int emitter_idx, int seq)
|
|
{
|
|
this.emitter_idx = emitter_idx;
|
|
this.seq = seq;
|
|
}
|
|
}
|
|
|
|
public static ISoundSystem Instance
|
|
{
|
|
get { return m_instance; }
|
|
}
|
|
|
|
public static void Initialize(ISoundSystem soundSystem)
|
|
{
|
|
m_instance = soundSystem;
|
|
}
|
|
|
|
private static ISoundSystem m_instance;
|
|
}
|
|
|
|
public class SoundSystemNull : ISoundSystem
|
|
{
|
|
public void Init(AudioMixer mixer) {}
|
|
|
|
public bool IsValid(ref SoundSystem.SoundHandle handle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public void MountBank(SoundBank bank) {}
|
|
|
|
public SoundSystem.SoundHandle Play(SoundDef soundDef)
|
|
{
|
|
return default(SoundSystem.SoundHandle);
|
|
}
|
|
|
|
public SoundSystem.SoundHandle Play(SoundDef soundDef, Transform parent)
|
|
{
|
|
return default(SoundSystem.SoundHandle);
|
|
}
|
|
|
|
public SoundSystem.SoundHandle Play(SoundDef soundDef, Vector3 position)
|
|
{
|
|
return default(SoundSystem.SoundHandle);
|
|
}
|
|
|
|
public SoundSystem.SoundHandle Play(WeakAssetReference weakSoundDef, Vector3 position)
|
|
{
|
|
return default(SoundSystem.SoundHandle);
|
|
}
|
|
|
|
public void SetCurrentListener(AudioListener audioListener) {}
|
|
|
|
public void SetRegistry(SoundRegistry registry) { }
|
|
|
|
public void Stop(SoundSystem.SoundHandle sh, float fadeOutTime = 0) {}
|
|
|
|
public void UnmountBank(SoundBank bank) {}
|
|
|
|
public void Update() {}
|
|
|
|
public void UpdatePosition(ref SoundSystem.SoundHandle handle, Vector3 position) { }
|
|
}
|
|
|
|
public class SoundSystemBase : ISoundSystem
|
|
{
|
|
|
|
public bool IsValid(ref SoundSystem.SoundHandle handle)
|
|
{
|
|
return m_Emitters[handle.emitter_idx] != null && m_Emitters[handle.emitter_idx].seqId == handle.seq;
|
|
}
|
|
|
|
// 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 = 1;
|
|
|
|
// Create pool of emitters
|
|
m_Emitters = new SoundEmitter[SoundSystem.soundNumEmitters.IntValue];
|
|
for (var i = 0; i < SoundSystem.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;
|
|
}
|
|
|
|
int AllocEmitter()
|
|
{
|
|
// Look for unused emitter
|
|
for(int i = 0; i < m_Emitters.Length; ++i)
|
|
{
|
|
var e = m_Emitters[i];
|
|
if (!e.playing)
|
|
{
|
|
e.seqId = m_SequenceId++;
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// Hunt down one emitter to kill
|
|
int emitter_idx = -1;
|
|
float distance = float.MinValue;
|
|
var listenerPos = m_CurrentListener != null ? m_CurrentListener.transform.position : Vector3.zero;
|
|
for(var i = 0; i < m_Emitters.Length; ++i)
|
|
//foreach (var e in m_Emitters)
|
|
{
|
|
var e = m_Emitters[i];
|
|
|
|
var s = e.source;
|
|
|
|
if (s == 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;
|
|
s = e.source;
|
|
}
|
|
|
|
// Skip destroyed sources and looping sources
|
|
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_idx = i;
|
|
}
|
|
}
|
|
if (emitter_idx != -1)
|
|
{
|
|
var e = m_Emitters[emitter_idx];
|
|
e.Kill();
|
|
e.seqId = m_SequenceId++;
|
|
return emitter_idx;
|
|
}
|
|
GameDebug.Log("Unable to allocate sound emitter!");
|
|
return -1;
|
|
}
|
|
|
|
public SoundSystem.SoundHandle Play(SoundDef soundDef)
|
|
{
|
|
if(soundDef == null)
|
|
{
|
|
GameDebug.LogWarning("Trying to play null soundDef");
|
|
return new SoundSystem.SoundHandle();
|
|
}
|
|
|
|
var emitter_idx = AllocEmitter();
|
|
|
|
if (emitter_idx < 0)
|
|
return new SoundSystem.SoundHandle();
|
|
|
|
var e = m_Emitters[emitter_idx];
|
|
|
|
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 SoundSystem.SoundHandle(emitter_idx, e.seqId);
|
|
}
|
|
|
|
public SoundSystem.SoundHandle Play(SoundDef soundDef, Vector3 position)
|
|
{
|
|
var emitter_idx = AllocEmitter();
|
|
|
|
if (emitter_idx < 0)
|
|
return new SoundSystem.SoundHandle();
|
|
|
|
var e = m_Emitters[emitter_idx];
|
|
|
|
e.source.transform.position = position;
|
|
e.repeatCount = Random.Range(soundDef.repeatMin, soundDef.repeatMax);
|
|
e.playing = true;
|
|
e.soundDef = soundDef;
|
|
StartEmitter(e);
|
|
return new SoundSystem.SoundHandle(emitter_idx, e.seqId);
|
|
}
|
|
|
|
public void UpdatePosition(ref SoundSystem.SoundHandle sh, Vector3 position)
|
|
{
|
|
if(!IsValid(ref sh))
|
|
{
|
|
GameDebug.LogWarning("Trying to reposition invalid soundhandle");
|
|
return;
|
|
}
|
|
m_Emitters[sh.emitter_idx].source.transform.position = position;
|
|
}
|
|
|
|
public SoundSystem.SoundHandle Play(SoundDef soundDef, Transform parent)
|
|
{
|
|
Profiler.BeginSample("SoundSystem.AllocEmitter");
|
|
var emitter_idx = AllocEmitter();
|
|
Profiler.EndSample();
|
|
|
|
if (emitter_idx < 0)
|
|
return new SoundSystem.SoundHandle();
|
|
|
|
var e = m_Emitters[emitter_idx];
|
|
|
|
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 SoundSystem.SoundHandle(emitter_idx, e.seqId);
|
|
}
|
|
|
|
public void Stop(SoundSystem.SoundHandle sh, float fadeOutTime = 0.0f)
|
|
{
|
|
if (!IsValid(ref sh))
|
|
{
|
|
GameDebug.LogWarning("SoundSystem.Stop(): invalid SoundHandle");
|
|
return;
|
|
}
|
|
var emitter = m_Emitters[sh.emitter_idx];
|
|
if (fadeOutTime == 0.0f)
|
|
emitter.fadeToKill.SetValue(0.0f);
|
|
else
|
|
{
|
|
emitter.fadeToKill.SetValue(1.0f);
|
|
emitter.fadeToKill.MoveTo(0.0f, fadeOutTime);
|
|
}
|
|
}
|
|
|
|
bool focus = false;
|
|
public void Update()
|
|
{
|
|
if (focus != Application.isFocused)
|
|
{
|
|
focus = Application.isFocused;
|
|
if (SoundSystem.soundMute.IntValue == -1)
|
|
m_MasterVolume.MoveTo(focus ? 1.0f : 0.0f, 0.5f);
|
|
}
|
|
|
|
var masterVolume = m_MasterVolume.GetValue();
|
|
|
|
if (SoundSystem.soundMute.IntValue == 0)
|
|
{
|
|
masterVolume = 0.0f;
|
|
Overlay.Managed.Write(Overlay.Color.Red, Overlay.Managed.CellsWide - 10, 2, "{0}", "AUDIO MUTED");
|
|
}
|
|
else if (SoundSystem.soundMute.IntValue == 1)
|
|
{
|
|
masterVolume = 1.0f;
|
|
Overlay.Managed.Write(Overlay.Color.Green, Overlay.Managed.CellsWide - 10, 2, "{0}", "AUDIO PLAYING");
|
|
}
|
|
|
|
m_AudioMixer.SetFloat("MasterVolume", DecibelFromAmplitude(Mathf.Clamp(SoundSystem.soundMasterVol.FloatValue, 0.0f, 1.0f) * masterVolume));
|
|
m_AudioMixer.SetFloat("MusicVolume", DecibelFromAmplitude(Mathf.Clamp(SoundSystem.soundMusicVol.FloatValue, 0.0f, 1.0f)));
|
|
m_AudioMixer.SetFloat("SFXVolume", DecibelFromAmplitude(Mathf.Clamp(SoundSystem.soundSFXVol.FloatValue, 0.0f, 1.0f)));
|
|
m_AudioMixer.SetFloat("MenuVolume", DecibelFromAmplitude(Mathf.Clamp(SoundSystem.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.seqId = -1; // make handles invalid
|
|
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 (SoundSystem.soundDebug.IntValue > 0)
|
|
{
|
|
// Overlay.Managed.Write(30, 1, "Mixer: {0} {1}", m_AudioMixer.GetInstanceID(), Game.game.audioMixer.GetInstanceID());
|
|
int ii = 4;
|
|
foreach (var o in GameObject.FindObjectsOfType<AudioMixerGroup>())
|
|
{
|
|
Overlay.Managed.Write(30, ii++, "group: {0} {1}", o.name, o.GetInstanceID());
|
|
}
|
|
Overlay.Managed.Write(1, 1, "Num audios {0}", count);
|
|
for (int i = 0, c = m_Emitters.Length; i < c; ++i)
|
|
{
|
|
var e = m_Emitters[i];
|
|
Overlay.Managed.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)
|
|
{
|
|
Overlay.Managed.Write(Overlay.Managed.CellsWide / 2 - 5, Overlay.Managed.CellsTall, "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);
|
|
int x = (int)Mathf.Lerp(e.source.panStereo * 10.0f, Mathf.Clamp(locpos.x, -10, 10), s);;
|
|
int z = (int)Mathf.Lerp(-10.0f, Mathf.Clamp(locpos.z, -10, 10), s);
|
|
Overlay.Managed.Write(s < 0.5 ? Overlay.Color.Green : Overlay.Color.Blue, Overlay.Managed.CellsWide / 2 + x, Overlay.Managed.CellsTall / 2 - z, "{0} ({1:##.#})", e.soundDef.name, locpos.magnitude);
|
|
}
|
|
}
|
|
}
|
|
|
|
void StartEmitter(SoundEmitter emitter)
|
|
{
|
|
var soundDef = emitter.soundDef;
|
|
var source = emitter.source;
|
|
|
|
StartSource(source, soundDef);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public static void StartSource(AudioSource source, SoundDef soundDef)
|
|
#else
|
|
static void StartSource(AudioSource source, SoundDef soundDef)
|
|
#endif
|
|
{
|
|
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 (SoundSystem.soundSpatialize != null && SoundSystem.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 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);
|
|
}
|
|
|
|
public void SetRegistry(SoundRegistry registry)
|
|
{
|
|
m_SoundRegistry = registry;
|
|
}
|
|
|
|
public SoundSystem.SoundHandle Play(WeakAssetReference weakSoundDef, Vector3 position)
|
|
{
|
|
var soundDef = m_SoundRegistry.GetSoundDef(weakSoundDef);
|
|
if (soundDef == null)
|
|
{
|
|
GameDebug.LogWarning("Trying to play sound with asset ref " + weakSoundDef.ToGuidStr() + " but it is not in the registry");
|
|
return new SoundSystem.SoundHandle();
|
|
}
|
|
return Play(soundDef, position);
|
|
}
|
|
|
|
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);
|
|
private SoundRegistry m_SoundRegistry;
|
|
}
|