该项目的目的是同时测试和演示来自 Unity DOTS 技术堆栈的多个新包。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

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