Insanely huge initial commit

This commit is contained in:
2026-02-21 16:40:15 -08:00
parent 208d626100
commit f74c547a13
33825 changed files with 5213498 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f37c0bbfe26a0bf4082b2f19419b044e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,474 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MoreMountains.Tools;
using System;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
namespace MoreMountains.Tools
{
/// <summary>
/// A static class used to save / load peaks once they've been computed
/// </summary>
public static class PeaksSaver
{
public static float[] Peaks;
}
/// <summary>
/// An event you can listen to that will get automatically triggered for every remapped beat
/// </summary>
public struct MMBeatEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(string name, float value);
static public void Trigger(string name, float value)
{
OnEvent?.Invoke(name, value);
}
}
[Serializable]
public class Beat
{
public string Name = "Beat";
public enum Modes { Raw, Normalized, BufferedRaw, BufferedNormalized, Amplitude, NormalizedAmplitude, AmplitudeBuffered, NormalizedAmplitudeBuffered }
// remapped will send beat events when a threshold is passed, live just updates the value with whatever value is reading right now
public enum BeatValueModes { Remapped, Live }
public Modes Mode = Modes.BufferedNormalized;
public BeatValueModes BeatValueMode = BeatValueModes.Remapped;
[MMEnumCondition("Mode", (int)Modes.Raw, (int)Modes.Normalized, (int)Modes.BufferedRaw, (int)Modes.BufferedNormalized)]
public Color BeatColor = Color.cyan;
public int BandID = 0;
public float Threshold = 0.5f;
public float MinimumTimeBetweenBeats = 0.25f;
[MMEnumCondition("BeatValueMode", (int)BeatValueModes.Remapped)]
public float RemappedAttack = 0.05f;
[MMEnumCondition("BeatValueMode", (int)BeatValueModes.Remapped)]
public float RemappedDecay = 0.2f;
[MMReadOnly]
public bool BeatThisFrame;
[MMReadOnly]
public float CurrentValue;
[HideInInspector]
public float _previousValue;
[HideInInspector]
public float _lastBeatAt;
[HideInInspector]
public float _lastBeatValue;
[HideInInspector]
public bool _initialized = false;
public UnityEvent OnBeat;
public void InitializeIfNeeded(int id, int bandID)
{
if (!_initialized)
{
Mode = Modes.Normalized;
BeatValueMode = BeatValueModes.Remapped;
Name = "Beat " + id;
BeatColor = MMColors.RandomColor();
BandID = bandID;
Threshold = 0.3f + id * 0.02f;
if (Threshold > 0.6f) { Threshold -= 0.5f; }
Threshold = Threshold % 1f;
MinimumTimeBetweenBeats = 0.25f + id * 0.02f;
RemappedAttack = 0.05f + id * 0.01f;
RemappedDecay = 0.2f + id * 0.01f;
_initialized = true;
}
}
}
/// <summary>
/// This component lets you pick an audio source (either global : the whole scene's audio, a unique source, or the
/// microphone), and will cut it into chunks that you can then use to emit beat events, that other objects can consume and act upon.
/// The sample interval is the frequency at which sound will be analyzed, the amount of spectrum samples will determine the
/// accuracy of the sampling, the window defines the method used to reduce leakage, and the number of bands
/// will determine in how many bands you want to cut the sound. The more bands, the more levers you'll have to play with afterwards.
/// In general, for all of these settings, higher values mean better quality and lower performance. The buffer speed determines how
/// fast buffered band levels readjust.
/// </summary>
[AddComponentMenu("More Mountains/Tools/Audio/MMAudioAnalyzer")]
public class MMAudioAnalyzer : MonoBehaviour
{
public enum Modes { Global, AudioSource, Microphone }
[Header("Source")]
[MMInformation("This component lets you pick an audio source (either global : the whole scene's audio, a unique source, or the " +
"microphone), and will cut it into chunks that you can then use to emit beat events, that other objects can consume and act upon. " +
"The sample interval is the frequency at which sound will be analyzed, the amount of spectrum samples will determine the " +
"accuracy of the sampling, the window defines the method used to reduce leakage, and the number of bands " +
"will determine in how many bands you want to cut the sound. The more bands, the more levers you'll have to play with afterwards." +
"In general, for all of these settings, higher values mean better quality and lower performance. The buffer speed determines how " +
"fast buffered band levels readjust.", MoreMountains.Tools.MMInformationAttribute.InformationType.Info, false)]
[MMReadOnlyWhenPlaying]
public Modes Mode = Modes.Global;
[MMEnumCondition("Mode", (int)Modes.AudioSource)]
[MMReadOnlyWhenPlaying]
public AudioSource TargetAudioSource;
[MMEnumCondition("Mode", (int)Modes.Microphone)]
public int MicrophoneID = 0;
[Header("Sampling")]
[MMReadOnlyWhenPlaying]
public float SampleInterval = 0.02f;
[MMDropdown(2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192)]
[MMReadOnlyWhenPlaying]
public int SpectrumSamples = 1024;
[MMReadOnlyWhenPlaying]
public FFTWindow Window = FFTWindow.Rectangular;
[Range(1, 64)]
[MMReadOnlyWhenPlaying]
public int NumberOfBands = 8;
public float BufferSpeed = 2f;
[Header("Beat Events")]
public Beat[] Beats;
[HideInInspector]
public float[] RawSpectrum;
[HideInInspector]
public float[] BandLevels;
[HideInInspector]
public float[] BufferedBandLevels;
[HideInInspector]
public float[] BandPeaks;
[HideInInspector]
public float[] LastPeaksAt;
[HideInInspector]
public float[] NormalizedBandLevels;
[HideInInspector]
public float[] NormalizedBufferedBandLevels;
[HideInInspector]
public float Amplitude;
[HideInInspector]
public float NormalizedAmplitude;
[HideInInspector]
public float BufferedAmplitude;
[HideInInspector]
public float NormalizedBufferedAmplitude;
[HideInInspector]
public bool Active = false;
[HideInInspector]
public bool PeaksPasted = false;
protected const int _microphoneDuration = 5;
protected string _microphone;
protected float _microphoneStartedAt = 0f;
protected const float _microphoneDelay = 0.030f;
protected const float _microphoneFrequency = 24000f;
protected WaitForSeconds _sampleIntervalWaitForSeconds;
protected int _cachedNumberOfBands;
public virtual void FindPeaks()
{
float time = 0f;
while (time < TargetAudioSource.clip.length)
{
TargetAudioSource.time = time;
TargetAudioSource.GetSpectrumData(RawSpectrum, 0, Window);
time += SampleInterval;
ComputeBandLevels();
PeaksSaver.Peaks = BandPeaks;
}
}
public virtual void PastePeaks()
{
BandPeaks = PeaksSaver.Peaks;
PeaksSaver.Peaks = null;
PeaksPasted = true;
}
public virtual void ClearPeaks()
{
BandPeaks = null;
PeaksSaver.Peaks = null;
PeaksPasted = false;
}
protected virtual void Awake()
{
Initialization();
}
public virtual void Initialization()
{
_cachedNumberOfBands = NumberOfBands;
RawSpectrum = new float[SpectrumSamples];
BandLevels = new float[_cachedNumberOfBands];
BufferedBandLevels = new float[_cachedNumberOfBands];
// we make sure our peaks match our bands
if ((BandPeaks == null) || (BandPeaks.Length == 0))
{
BandPeaks = new float[_cachedNumberOfBands];
PeaksPasted = false;
}
if (BandPeaks.Length != BandLevels.Length)
{
BandPeaks = new float[_cachedNumberOfBands];
PeaksPasted = false;
}
LastPeaksAt = new float[_cachedNumberOfBands];
NormalizedBandLevels = new float[_cachedNumberOfBands];
NormalizedBufferedBandLevels = new float[_cachedNumberOfBands];
if ((Mode == Modes.AudioSource) && (TargetAudioSource == null))
{
Debug.LogError(this.name + " : this MMAudioAnalyzer needs a target audio source to operate.");
return;
}
if (Mode == Modes.Microphone)
{
#if !UNITY_WEBGL
GameObject audioSourceGo = new GameObject("Microphone");
SceneManager.MoveGameObjectToScene(audioSourceGo, this.gameObject.scene);
audioSourceGo.transform.SetParent(this.gameObject.transform);
TargetAudioSource = audioSourceGo.AddComponent<AudioSource>();
//UNCOMMENT_MICROPHONE string _microphone = Microphone.devices[MicrophoneID].ToString();
//UNCOMMENT_MICROPHONE TargetAudioSource.clip = Microphone.Start(_microphone, true, _microphoneDuration, (int)_microphoneFrequency);
//UNCOMMENT_MICROPHONE TargetAudioSource.Play();
_microphoneStartedAt = Time.time;
#endif
}
Active = true;
_sampleIntervalWaitForSeconds = new WaitForSeconds(SampleInterval);
StartCoroutine(Analyze());
}
protected virtual void Update()
{
HandleBuffer();
ComputeAmplitudes();
HandleBeats();
}
protected virtual IEnumerator Analyze()
{
while (true)
{
switch (Mode)
{
case Modes.AudioSource:
TargetAudioSource.GetSpectrumData(RawSpectrum, 0, Window);
break;
case Modes.Global:
AudioListener.GetSpectrumData(RawSpectrum, 0, Window);
break;
case Modes.Microphone:
#if !UNITY_WEBGL
int microphoneSamples = 0;
//UNCOMMENT_MICROPHONE microphoneSamples = Microphone.GetPosition(_microphone);
if (microphoneSamples / _microphoneFrequency > _microphoneDelay)
{
if (!TargetAudioSource.isPlaying)
{
TargetAudioSource.timeSamples = (int)(microphoneSamples - (_microphoneDelay * _microphoneFrequency));
TargetAudioSource.Play();
}
_microphoneStartedAt = Time.time;
}
AudioListener.GetSpectrumData(RawSpectrum, 0, Window);
#endif
break;
}
ComputeBandLevels();
yield return _sampleIntervalWaitForSeconds;
}
}
protected virtual void HandleBuffer()
{
for (int i = 0; i < BandLevels.Length; i++)
{
BufferedBandLevels[i] = Mathf.Max(BufferedBandLevels[i] * Mathf.Exp(-BufferSpeed * Time.deltaTime), BandLevels[i]);
NormalizedBandLevels[i] = BandLevels[i] / BandPeaks[i];
NormalizedBufferedBandLevels[i] = BufferedBandLevels[i] / BandPeaks[i];
}
}
protected virtual void ComputeBandLevels()
{
float coefficient = Mathf.Log(RawSpectrum.Length);
int offset = 0;
for (int i = 0; i < BandLevels.Length; i++)
{
float savedSum = 0f;
float next = Mathf.Exp(coefficient / BandLevels.Length * (i + 1));
float weight = 1f / (next - offset);
for (float sum = 0f; offset < next; offset++)
{
sum += RawSpectrum[offset];
savedSum = sum;
}
BandLevels[i] = Mathf.Sqrt(weight * savedSum);
if (BandLevels[i] > BandPeaks[i])
{
BandPeaks[i] = BandLevels[i];
LastPeaksAt[i] = Time.time;
}
}
}
protected virtual void ComputeAmplitudes()
{
Amplitude = 0f;
BufferedAmplitude = 0f;
NormalizedAmplitude = 0f;
NormalizedBufferedAmplitude = 0f;
for (int i = 0; i < _cachedNumberOfBands; i++)
{
Amplitude += BandLevels[i];
BufferedAmplitude += BufferedBandLevels[i];
NormalizedAmplitude += NormalizedBandLevels[i];
NormalizedBufferedAmplitude += NormalizedBufferedBandLevels[i];
}
Amplitude = Amplitude / _cachedNumberOfBands;
BufferedAmplitude = BufferedAmplitude / _cachedNumberOfBands;
NormalizedAmplitude = NormalizedAmplitude / _cachedNumberOfBands;
NormalizedBufferedAmplitude = NormalizedBufferedAmplitude / _cachedNumberOfBands;
}
protected virtual void HandleBeats()
{
if (Beats.Length <= 0)
{
return;
}
foreach (Beat beat in Beats)
{
float value = 0f;
beat.BeatThisFrame = false;
switch (beat.Mode)
{
case Beat.Modes.Amplitude:
value = Amplitude;
break;
case Beat.Modes.AmplitudeBuffered:
value = BufferedAmplitude;
break;
case Beat.Modes.BufferedNormalized:
value = NormalizedBufferedBandLevels[beat.BandID];
break;
case Beat.Modes.BufferedRaw:
value = BufferedBandLevels[beat.BandID];
break;
case Beat.Modes.Normalized:
value = NormalizedBandLevels[beat.BandID];
break;
case Beat.Modes.NormalizedAmplitude:
value = NormalizedAmplitude;
break;
case Beat.Modes.NormalizedAmplitudeBuffered:
value = NormalizedBufferedAmplitude;
break;
case Beat.Modes.Raw:
value = BandLevels[beat.BandID];
break;
}
if (beat.BeatValueMode == Beat.BeatValueModes.Live)
{
beat.CurrentValue = value;
}
else
{
// if audio value went below the bias during this frame
if ((beat._previousValue > beat.Threshold) && (value <= beat.Threshold))
{
// if minimum beat interval is reached
if (Time.time - beat._lastBeatAt > beat.MinimumTimeBetweenBeats)
{
OnBeat(beat, value);
}
}
// if audio value went above the bias during this frame
if ((beat._previousValue <= beat.Threshold) && (value > beat.Threshold))
{
// if minimum beat interval is reached
if (Time.time - beat._lastBeatAt > beat.MinimumTimeBetweenBeats)
{
OnBeat(beat, value);
}
}
beat._previousValue = value;
}
}
}
protected virtual void OnBeat(Beat beat, float rawValue)
{
beat._lastBeatAt = Time.time;
beat.BeatThisFrame = true;
if (beat.OnBeat != null)
{
beat.OnBeat.Invoke();
}
MMBeatEvent.Trigger(beat.Name, beat.CurrentValue);
StartCoroutine(RemapBeat(beat));
}
protected virtual IEnumerator RemapBeat(Beat beat)
{
float remapStartedAt = Time.time;
while (Time.time - remapStartedAt < beat.RemappedAttack + beat.RemappedDecay)
{
// attack
if (Time.time - remapStartedAt < beat.RemappedAttack)
{
beat.CurrentValue = Mathf.Lerp(0f, 1f, (Time.time - remapStartedAt) / beat.RemappedAttack);
}
if (Time.time - remapStartedAt > beat.RemappedAttack)
{
beat.CurrentValue = Mathf.Lerp(1f, 0f, (Time.time - remapStartedAt - beat.RemappedAttack) / beat.RemappedDecay);
}
yield return null;
}
beat.CurrentValue = 0f;
yield break;
}
protected virtual void OnValidate()
{
if ((Beats == null) || (Beats.Length == 0))
{
return;
}
int bandCounter = 0;
for (int i = 0; i < Beats.Length; i++)
{
if (bandCounter >= _cachedNumberOfBands)
{
bandCounter = 0;
}
Beats[i].InitializeIfNeeded(i, bandCounter);
bandCounter++;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5786831fccf809245bf95e1d459472ba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 298ae8aa2e4ddf849b8cb560e5aa5efb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
namespace MoreMountains.Tools
{
public class MMAudioEvents
{
}
/// <summary>
/// A struct used to trigger sounds
/// </summary>
public struct MMSfxEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(AudioClip clipToPlay, AudioMixerGroup audioGroup = null, float volume = 1f, float pitch = 1f, int priority = 128);
static public void Trigger(AudioClip clipToPlay, AudioMixerGroup audioGroup = null, float volume = 1f, float pitch = 1f, int priority = 128)
{
OnEvent?.Invoke(clipToPlay, audioGroup, volume, pitch, priority);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 72d51457cd4e8a5438095d1ce5ce5f6f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 034e3eb1df7d0af4bb6c2326534ca72f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
/// <summary>
/// A simple component that will ensure (if put on ALL audio listeners in your game)
/// that you never see a "There are two audio listeners in the scene" warning again.
/// </summary>
[RequireComponent(typeof(AudioListener))]
public class MMAudioListener : MonoBehaviour
{
protected AudioListener _audioListener;
protected AudioListener[] _otherListeners;
/// <summary>
/// On enable, disables other listeners if found
/// </summary>
protected virtual void OnEnable()
{
_audioListener = this.gameObject.GetComponent<AudioListener>();
_otherListeners = FindObjectsOfType(typeof(AudioListener)) as AudioListener[];
foreach (AudioListener audioListener in _otherListeners)
{
if ((audioListener != null) && (audioListener != _audioListener) )
{
audioListener.enabled = false;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0e8aa3525151bb343b66b059b6095946
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6a5a59d0755580a44ac9de2cae00eabf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,891 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
/// <summary>
/// This class stores all the info related to items in a playlist
/// </summary>
public struct MMPlaylistPlayEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel);
static public void Trigger(int channel)
{
OnEvent?.Invoke(channel);
}
}
public struct MMPlaylistStopEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel);
static public void Trigger(int channel)
{
OnEvent?.Invoke(channel);
}
}
public struct MMPlaylistPauseEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel);
static public void Trigger(int channel)
{
OnEvent?.Invoke(channel);
}
}
public struct MMPlaylistPlayNextEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel);
static public void Trigger(int channel)
{
OnEvent?.Invoke(channel);
}
}
public struct MMPlaylistPlayPreviousEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel);
static public void Trigger(int channel)
{
OnEvent?.Invoke(channel);
}
}
public struct MMPlaylistPlayIndexEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel, int index);
static public void Trigger(int channel, int index)
{
OnEvent?.Invoke(channel, index);
}
}
public struct MMPlaylistVolumeMultiplierEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel, float newVolumeMultiplier, bool applyVolumeMultiplierInstantly = false);
static public void Trigger(int channel, float newVolumeMultiplier, bool applyVolumeMultiplierInstantly = false)
{
OnEvent?.Invoke(channel, newVolumeMultiplier, applyVolumeMultiplierInstantly);
}
}
public struct MMPlaylistPitchMultiplierEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel, float newPitchMultiplier, bool applyPitchMultiplierInstantly = false);
static public void Trigger(int channel, float newPitchMultiplier, bool applyPitchMultiplierInstantly = false)
{
OnEvent?.Invoke(channel, newPitchMultiplier, applyPitchMultiplierInstantly);
}
}
public struct MMPlaylistChangeEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel, MMSMPlaylist newPlaylist, bool andPlay);
static public void Trigger(int channel, MMSMPlaylist newPlaylist, bool andPlay)
{
OnEvent?.Invoke(channel, newPlaylist, andPlay);
}
}
public struct MMPlaylistNewSongStartedEvent
{
static private event Delegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
static public void Register(Delegate callback) { OnEvent += callback; }
static public void Unregister(Delegate callback) { OnEvent -= callback; }
public delegate void Delegate(int channel);
static public void Trigger(int channel)
{
OnEvent?.Invoke(channel);
}
}
[System.Serializable]
public class MMPlaylistSong
{
/// the audiosource that contains the audio clip we want to play
public AudioSource TargetAudioSource;
/// the min (when it's off) and max (when it's playing) volume for this source
[MMVector("Min", "Max")]
public Vector2 Volume = new Vector2(0f, 1f);
/// a random delay in seconds to apply, between its RMin and RMax
[MMVector("RMin", "RMax")]
public Vector2 InitialDelay = Vector2.zero;
/// a random crossfade duration (in seconds) to apply when transitioning to this song, between its RMin and RMax
[MMVector("RMin", "RMax")]
public Vector2 CrossFadeDuration = new Vector2(2f, 2f);
/// a random pitch to apply to this song, between its RMin and RMax
[MMVector("RMin", "RMax")]
public Vector2 Pitch = Vector2.one;
/// the stereo pan for this song
[Range(-1f, 1f)]
public float StereoPan = 0f;
/// the spatial blend for this song (0 is 2D, 1 is 3D)
[Range(0f, 1f)]
public float SpatialBlend = 0f;
/// whether this song should loop or not
public bool Loop = false;
/// whether this song is playing right now or not
[MMReadOnly]
public bool Playing = false;
/// whether this song is fading right now or not
[MMReadOnly]
public bool Fading = false;
[MMHidden]
public bool _initialized;
public virtual void Initialization()
{
if (_initialized)
{
return;
}
this.Volume = new Vector2(0f, 1f);
this.InitialDelay = Vector2.zero;
this.CrossFadeDuration = new Vector2(2f, 2f);
this.Pitch = Vector2.one;
this.StereoPan = 0f;
this.SpatialBlend = 0f;
this.Loop = false;
this._initialized = true;
}
}
/// <summary>
/// Use this class to play audiosources (usually background music but feel free to use that for anything) in sequence, with optional crossfade between songs
/// </summary>
[AddComponentMenu("More Mountains/Tools/Audio/MMPlaylist")]
[MMRequiresConstantRepaint]
public class MMPlaylist : MMMonoBehaviour
{
/// the possible states this playlist can be in
public enum PlaylistStates
{
Idle,
Playing,
Paused
}
[MMInspectorGroup("Playlist Songs", true, 18)]
/// the channel on which to broadcast orders for this playlist
[Tooltip("the channel on which to broadcast orders for this playlist")]
public int Channel = 0;
/// the songs that this playlist will play
[Tooltip("the songs that this playlist will play")]
public List<MMPlaylistSong> Songs;
[MMInspectorGroup("Settings", true, 13)]
/// whether this should play in random order or not
[Tooltip("whether this should play in random order or not")]
public bool RandomOrder = false;
/// if this is true, random seed will be randomized by the system clock
[Tooltip("if this is true, random seed will be randomized by the system clock")]
[MMCondition("RandomOrder", true)]
public bool RandomizeOrderSeed = true;
/// whether this playlist should play and loop as a whole forever or not
[Tooltip("whether this playlist should play and loop as a whole forever or not")]
public bool Endless = true;
/// whether this playlist should auto play on start or not
[Tooltip("whether this playlist should auto play on start or not")]
public bool PlayOnStart = true;
/// a global volume multiplier to apply when playing a song
[Tooltip("a global volume multiplier to apply when playing a song")]
public float VolumeMultiplier = 1f;
/// if this is true, this playlist will automatically pause/resume OnApplicationPause, useful if you've prevented your game from running in the background
[Tooltip("if this is true, this playlist will automatically pause/resume OnApplicationPause, useful if you've prevented your game from running in the background")]
public bool AutoHandleApplicationPause = true;
[MMInspectorGroup("Persistence", true, 32)]
/// if this is true, this playlist will persist from scene to scene
[Tooltip("if this is true, this playlist will persist from scene to scene")]
public bool Persistent = false;
/// if this is true, this singleton will auto detach if it finds itself parented on awake
[Tooltip("if this is true, this singleton will auto detach if it finds itself parented on awake")]
[MMCondition("Persistent", true)]
public bool AutomaticallyUnparentOnAwake = true;
[MMInspectorGroup("Status", true, 14)]
/// the current state of the playlist, debug display only
[Tooltip("the current state of the playlist, debug display only")]
[MMReadOnly]
public PlaylistStates DebugCurrentState = PlaylistStates.Idle;
/// the index we're currently playing
[Tooltip("the index we're currently playing")]
[MMReadOnly]
public int CurrentlyPlayingIndex = -1;
/// the name of the song that is currently playing
[Tooltip("the name of the song that is currently playing")]
[MMReadOnly]
public string CurrentSongName;
/// the current state of this playlist
[MMReadOnly]
public MMStateMachine<MMPlaylist.PlaylistStates> PlaylistState;
[MMInspectorGroup("Tests", true, 15)]
/// a play test button
[MMInspectorButton("Play")]
public bool PlayButton;
/// a pause test button
[MMInspectorButton("Pause")]
public bool PauseButton;
/// a stop test button
[MMInspectorButton("Stop")]
public bool StopButton;
/// a next song test button
[MMInspectorButton("PlayNextSong")]
public bool NextButton;
/// the index of the song to play when pressing the PlayTargetSong button
[Tooltip("the index of the song to play when pressing the PlayTargetSong button")]
public int TargetSongIndex = 0;
/// a next song test button
[MMInspectorButton("PlayTargetSong")]
public bool TargetSongButton;
/// a next song test button
[MMInspectorButton("QueueTargetSong")]
public bool QueueTargetSongButton;
/// a next song test button
[MMInspectorButton("SetLoopTargetSong")]
public bool SetLoopTargetSongButton;
/// a next song test button
[MMInspectorButton("StopLoopTargetSong")]
public bool StopLoopTargetSongButton;
protected int _songsPlayedSoFar = 0;
protected int _songsPlayedThisCycle = 0;
protected Coroutine _coroutine;
protected bool _shouldResumeOnApplicationPause = false;
public static bool HasInstance => _instance != null;
public static MMPlaylist Current => _instance;
protected static MMPlaylist _instance;
protected bool _enabled;
protected int _queuedSong = -1;
/// <summary>
/// Singleton design pattern
/// </summary>
/// <value>The instance.</value>
public static MMPlaylist Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<MMPlaylist> ();
if (_instance == null)
{
GameObject obj = new GameObject ();
obj.name = typeof(MMPlaylist).Name + "_AutoCreated";
_instance = obj.AddComponent<MMPlaylist> ();
}
}
return _instance;
}
}
/// <summary>
/// On awake, we check if there's already a copy of the object in the scene. If there's one, we destroy it.
/// </summary>
protected virtual void Awake ()
{
InitializeSingleton();
}
/// <summary>
/// Initializes the singleton.
/// </summary>
protected virtual void InitializeSingleton()
{
if (!Application.isPlaying)
{
return;
}
if (!Persistent)
{
return;
}
if (AutomaticallyUnparentOnAwake)
{
this.transform.SetParent(null);
}
if (_instance == null)
{
//If I am the first instance, make me the Singleton
_instance = this;
DontDestroyOnLoad (transform.gameObject);
_enabled = true;
}
else
{
//If a Singleton already exists and you find
//another reference in scene, destroy it!
if(this != _instance)
{
Destroy(this.gameObject);
}
}
}
/// <summary>
/// On Start we initialize our playlist
/// </summary>
protected virtual void Start()
{
Initialization();
}
/// <summary>
/// On init we initialize our state machine and start playing if needed
/// </summary>
protected virtual void Initialization()
{
if (RandomOrder && RandomizeOrderSeed)
{
Random.InitState(System.Environment.TickCount);
}
_songsPlayedSoFar = 0;
PlaylistState = new MMStateMachine<MMPlaylist.PlaylistStates>(this.gameObject, true);
ChangePlaylistState(PlaylistStates.Idle);
if (Songs.Count == 0)
{
return;
}
if (PlayOnStart)
{
PlayFirstSong();
}
}
protected virtual void ChangePlaylistState(PlaylistStates newState)
{
PlaylistState.ChangeState(newState);
DebugCurrentState = newState;
}
/// <summary>
/// Picks and plays the first song
/// </summary>
protected virtual void PlayFirstSong()
{
_songsPlayedThisCycle = 0;
CurrentlyPlayingIndex = -1;
int newIndex = PickNextIndex();
_coroutine = StartCoroutine(PlaySong(newIndex));
}
/// <summary>
/// Plays a new song in the playlist, and stops / fades the previous one
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
protected virtual IEnumerator PlaySong(int index)
{
// if we don't have a song, we stop
if (Songs.Count == 0)
{
yield break;
}
// if we've played all our songs, we stop
if (!Endless && (_songsPlayedThisCycle > Songs.Count))
{
yield break;
}
if (_coroutine != null)
{
StopCoroutine(_coroutine);
}
// we stop our current song
if ((PlaylistState.CurrentState == PlaylistStates.Playing)
&& (index >= 0 && index < Songs.Count)
&& !Songs[index].TargetAudioSource.isPlaying)
{
StartCoroutine(Fade(CurrentlyPlayingIndex,
Random.Range(Songs[index].CrossFadeDuration.x, Songs[index].CrossFadeDuration.y),
Songs[CurrentlyPlayingIndex].Volume.y * VolumeMultiplier,
Songs[CurrentlyPlayingIndex].Volume.x * VolumeMultiplier,
true));
}
// we stop all other coroutines
if ((CurrentlyPlayingIndex >= 0) && (Songs.Count > CurrentlyPlayingIndex))
{
foreach (MMPlaylistSong song in Songs)
{
if (song != Songs[CurrentlyPlayingIndex])
{
song.Fading = false;
}
}
}
if (index < 0 || index >= Songs.Count)
{
yield break;
}
// initial delay
yield return MMCoroutine.WaitFor(Random.Range(Songs[index].InitialDelay.x, Songs[index].InitialDelay.y));
if (Songs[index].TargetAudioSource == null)
{
Debug.LogError(this.name + " : the playlist song you're trying to play is null");
yield break;
}
Songs[index].TargetAudioSource.pitch = Random.Range(Songs[index].Pitch.x, Songs[index].Pitch.y);
Songs[index].TargetAudioSource.panStereo = Songs[index].StereoPan;
Songs[index].TargetAudioSource.spatialBlend = Songs[index].SpatialBlend;
Songs[index].TargetAudioSource.loop = Songs[index].Loop;
// fades the new song's volume
StartCoroutine(Fade(index,
Random.Range(Songs[index].CrossFadeDuration.x, Songs[index].CrossFadeDuration.y),
Songs[index].Volume.x * VolumeMultiplier,
Songs[index].Volume.y * VolumeMultiplier,
false));
// starts the new song
Songs[index].TargetAudioSource.Play();
// updates our state
CurrentSongName = Songs[index].TargetAudioSource.clip.name;
ChangePlaylistState(PlaylistStates.Playing);
Songs[index].Playing = true;
CurrentlyPlayingIndex = index;
_songsPlayedSoFar++;
_songsPlayedThisCycle++;
while (Songs[index].TargetAudioSource.isPlaying || (PlaylistState.CurrentState == PlaylistStates.Paused) || _shouldResumeOnApplicationPause)
{
yield return null;
}
if (PlaylistState.CurrentState != PlaylistStates.Playing)
{
yield break;
}
if (_songsPlayedSoFar < Songs.Count)
{
_coroutine = StartCoroutine(PlaySong(PickNextIndex()));
}
else
{
if (Endless)
{
_coroutine = StartCoroutine(PlaySong(PickNextIndex()));
}
else
{
ChangePlaylistState(PlaylistStates.Idle);
}
}
}
/// <summary>
/// Fades an audiosource in or out, optionnally stopping it at the end
/// </summary>
/// <param name="source"></param>
/// <param name="duration"></param>
/// <param name="initialVolume"></param>
/// <param name="endVolume"></param>
/// <param name="stopAtTheEnd"></param>
/// <returns></returns>
protected virtual IEnumerator Fade(int index, float duration, float initialVolume, float endVolume, bool stopAtTheEnd)
{
if (index < 0 || index >= Songs.Count)
{
yield break;
}
float startTimestamp = Time.time;
float progress = 0f;
Songs[index].Fading = true;
while ((Time.time - startTimestamp < duration) && (Songs[index].Fading))
{
progress = MMMaths.Remap(Time.time - startTimestamp, 0f, duration, 0f, 1f);
Songs[index].TargetAudioSource.volume = Mathf.Lerp(initialVolume, endVolume, progress);
yield return null;
}
Songs[index].TargetAudioSource.volume = endVolume;
if (stopAtTheEnd)
{
Songs[index].TargetAudioSource.Stop();
Songs[index].Playing = false;
Songs[index].Fading = false;
}
}
/// <summary>
/// Picks the next song to play
/// </summary>
/// <returns></returns>
protected virtual int PickNextIndex()
{
if (Songs.Count == 0)
{
return -1;
}
if (_queuedSong != -1)
{
int newRequestedIndex = _queuedSong;
_queuedSong = -1;
return newRequestedIndex;
}
int newIndex = CurrentlyPlayingIndex;
if (RandomOrder)
{
while (newIndex == CurrentlyPlayingIndex)
{
newIndex = Random.Range(0, Songs.Count);
}
}
else
{
newIndex = (CurrentlyPlayingIndex + 1) % Songs.Count;
}
return newIndex;
}
/// <summary>
/// Picks the previous song to play
/// </summary>
/// <returns></returns>
protected virtual int PickPreviousIndex()
{
if (Songs.Count == 0)
{
return -1;
}
int newIndex = CurrentlyPlayingIndex;
if (RandomOrder)
{
while (newIndex == CurrentlyPlayingIndex)
{
newIndex = Random.Range(0, Songs.Count);
}
}
else
{
newIndex = (CurrentlyPlayingIndex - 1);
if (newIndex < 0)
{
newIndex = Songs.Count - 1;
}
}
return newIndex;
}
/// <summary>
/// Plays either the first song or resumes playing a paused one
/// </summary>
public virtual void Play()
{
switch (PlaylistState.CurrentState)
{
case PlaylistStates.Idle:
PlayFirstSong();
break;
case PlaylistStates.Paused:
Songs[CurrentlyPlayingIndex].TargetAudioSource.UnPause();
ChangePlaylistState(PlaylistStates.Playing);
break;
case PlaylistStates.Playing:
// do nothing
break;
}
}
public virtual void PlayAtIndex(int songIndex)
{
_coroutine = StartCoroutine(PlaySong(songIndex));
}
public virtual void QueueSongAtIndex(int songIndex)
{
_queuedSong = songIndex;
}
/// <summary>
/// Pauses the current song
/// </summary>
public virtual void Pause()
{
if (PlaylistState.CurrentState != PlaylistStates.Playing)
{
return;
}
Songs[CurrentlyPlayingIndex].TargetAudioSource.Pause();
ChangePlaylistState(PlaylistStates.Paused);
}
/// <summary>
/// Stops the playlist
/// </summary>
public virtual void Stop()
{
if (PlaylistState.CurrentState != PlaylistStates.Playing)
{
return;
}
Songs[CurrentlyPlayingIndex].TargetAudioSource.Stop();
Songs[CurrentlyPlayingIndex].Playing = false;
Songs[CurrentlyPlayingIndex].Fading = false;
CurrentlyPlayingIndex = -1;
ChangePlaylistState(PlaylistStates.Idle);
}
/// <summary>
/// Will change the current track loop status
/// </summary>
public virtual void SetLoop(bool loop)
{
Songs[CurrentlyPlayingIndex].TargetAudioSource.loop = loop;
}
/// <summary>
/// Plays the next song in the playlist
/// </summary>
public virtual void PlayNextSong()
{
int newIndex = PickNextIndex();
_coroutine = StartCoroutine(PlaySong(newIndex));
}
/// <summary>
/// Plays the previous song in the playlist
/// </summary>
public virtual void PlayPreviousSong()
{
int newIndex = PickPreviousIndex();
_coroutine = StartCoroutine(PlaySong(newIndex));
}
protected virtual void PlayTargetSong()
{
int newIndex = Mathf.Clamp(TargetSongIndex, 0, Songs.Count - 1);
PlayAtIndex(newIndex);
}
protected virtual void QueueTargetSong()
{
int newIndex = Mathf.Clamp(TargetSongIndex, 0, Songs.Count - 1);
QueueSongAtIndex(newIndex);
}
protected virtual void SetLoopTargetSong()
{
SetLoop(true);
}
protected virtual void StopLoopTargetSong()
{
SetLoop(false);
}
protected virtual void OnPlayEvent(int channel)
{
if (channel != Channel) { return; }
Play();
}
protected virtual void OnPauseEvent(int channel)
{
if (channel != Channel) { return; }
Pause();
}
protected virtual void OnStopEvent(int channel)
{
if (channel != Channel) { return; }
Stop();
}
protected virtual void OnPlayNextEvent(int channel)
{
if (channel != Channel) { return; }
PlayNextSong();
}
protected virtual void OnPlayPreviousEvent(int channel)
{
if (channel != Channel) { return; }
PlayPreviousSong();
}
protected virtual void OnPlayIndexEvent(int channel, int index)
{
if (channel != Channel) { return; }
_coroutine = StartCoroutine(PlaySong(index));
}
protected virtual void OnMMPlaylistVolumeMultiplierEvent(int channel, float newVolumeMultiplier, bool applyVolumeMultiplierInstantly = false)
{
if (channel != Channel) { return; }
VolumeMultiplier = newVolumeMultiplier;
if (applyVolumeMultiplierInstantly)
{
Songs[CurrentlyPlayingIndex].TargetAudioSource.volume = Songs[CurrentlyPlayingIndex].Volume.y * VolumeMultiplier;
}
}
/// <summary>
/// On enable, starts listening for playlist events
/// </summary>
protected virtual void OnEnable()
{
MMPlaylistPauseEvent.Register(OnPauseEvent);
MMPlaylistPlayEvent.Register(OnPlayEvent);
MMPlaylistPlayNextEvent.Register(OnPlayNextEvent);
MMPlaylistPlayPreviousEvent.Register(OnPlayPreviousEvent);
MMPlaylistStopEvent.Register(OnStopEvent);
MMPlaylistPlayIndexEvent.Register(OnPlayIndexEvent);
MMPlaylistVolumeMultiplierEvent.Register(OnMMPlaylistVolumeMultiplierEvent);
}
/// <summary>
/// On disable, stops listening for playlist events
/// </summary>
protected virtual void OnDisable()
{
MMPlaylistPauseEvent.Unregister(OnPauseEvent);
MMPlaylistPlayEvent.Unregister(OnPlayEvent);
MMPlaylistPlayNextEvent.Unregister(OnPlayNextEvent);
MMPlaylistPlayPreviousEvent.Unregister(OnPlayPreviousEvent);
MMPlaylistStopEvent.Unregister(OnStopEvent);
MMPlaylistPlayIndexEvent.Unregister(OnPlayIndexEvent);
MMPlaylistVolumeMultiplierEvent.Unregister(OnMMPlaylistVolumeMultiplierEvent);
}
protected bool _firstDeserialization = true;
protected int _listCount = 0;
/// <summary>
/// On Validate, we check if our array has changed and if yes we initialize our new elements
/// </summary>
protected virtual void OnValidate()
{
if (_firstDeserialization)
{
if (Songs == null)
{
_listCount = 0;
_firstDeserialization = false;
}
else
{
_listCount = Songs.Count;
_firstDeserialization = false;
}
}
else
{
if (Songs.Count != _listCount)
{
if (Songs.Count > _listCount)
{
foreach(MMPlaylistSong song in Songs)
{
song.Initialization();
}
}
_listCount = Songs.Count;
}
}
}
/// <summary>
/// On ApplicationPause, we pause the playlist and resume it afterwards
/// </summary>
/// <param name="pauseStatus"></param>
protected virtual void OnApplicationPause(bool pauseStatus)
{
if (!AutoHandleApplicationPause)
{
return;
}
if (pauseStatus && PlaylistState.CurrentState == PlaylistStates.Playing)
{
Pause();
_shouldResumeOnApplicationPause = true;
}
if (!pauseStatus && _shouldResumeOnApplicationPause)
{
_shouldResumeOnApplicationPause = false;
Play();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b0a35f7d8f5b3443a41bb74a444220e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,136 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
/// <summary>
/// A class used to pilot a MMPlaylist
/// </summary>
[AddComponentMenu("More Mountains/Tools/Audio/MMPlaylistRemote")]
public class MMPlaylistRemote : MonoBehaviour
{
public int Channel = 0;
/// The track to play when calling PlaySelectedTrack
public int TrackNumber = 0;
[Header("Triggers")]
/// if this is true, the selected track will be played on trigger enter (if you have a trigger collider on this)
public bool PlaySelectedTrackOnTriggerEnter = true;
/// if this is true, the selected track will be played on trigger exit (if you have a trigger collider on this)
public bool PlaySelectedTrackOnTriggerExit = false;
/// the tag to check for on trigger stuff
public string TriggerTag = "Player";
[Header("Test")]
/// a play test button
[MMInspectorButton("Play")]
public bool PlayButton;
/// a pause test button
[MMInspectorButton("Pause")]
public bool PauseButton;
/// a stop test button
[MMInspectorButton("Stop")]
public bool StopButton;
/// a next track test button
[MMInspectorButton("PlayNextTrack")]
public bool NextButton;
/// a selected track test button
[MMInspectorButton("PlaySelectedTrack")]
public bool SelectedTrackButton;
/// <summary>
/// Plays the playlist
/// </summary>
public virtual void Play()
{
MMPlaylistPlayEvent.Trigger(Channel);
}
/// <summary>
/// Pauses the current track
/// </summary>
public virtual void Pause()
{
MMPlaylistPauseEvent.Trigger(Channel);
}
/// <summary>
/// Stops the playlist
/// </summary>
public virtual void Stop()
{
MMPlaylistStopEvent.Trigger(Channel);
}
/// <summary>
/// Plays the next track in the playlist
/// </summary>
public virtual void PlayNextTrack()
{
MMPlaylistPlayNextEvent.Trigger(Channel);
}
/// <summary>
/// Plays the track selected in the inspector
/// </summary>
public virtual void PlaySelectedTrack()
{
MMPlaylistPlayIndexEvent.Trigger(Channel, TrackNumber);
}
/// <summary>
/// Plays the track set in parameters
/// </summary>
public virtual void PlayTrack(int trackIndex)
{
MMPlaylistPlayIndexEvent.Trigger(Channel, trackIndex);
}
/// <summary>
/// On trigger enter, we play the selected track if needed
/// </summary>
/// <param name="collider"></param>
protected virtual void OnTriggerEnter(Collider collider)
{
if (PlaySelectedTrackOnTriggerEnter && (collider.CompareTag(TriggerTag)))
{
PlaySelectedTrack();
}
}
/// <summary>
/// On trigger exit, we play the selected track if needed
/// </summary>
protected virtual void OnTriggerExit(Collider collider)
{
if (PlaySelectedTrackOnTriggerExit && (collider.CompareTag(TriggerTag)))
{
PlaySelectedTrack();
}
}
/// <summary>
/// On trigger enter 2D, we play the selected track if needed
/// </summary>
/// <param name="collider"></param>
protected virtual void OnTriggerEnter2D(Collider2D collider)
{
if (PlaySelectedTrackOnTriggerEnter && (collider.CompareTag(TriggerTag)))
{
PlaySelectedTrack();
}
}
/// <summary>
/// On trigger exit 2D, we play the selected track if needed
/// </summary>
protected virtual void OnTriggerExit2D(Collider2D collider)
{
if (PlaySelectedTrackOnTriggerExit && (collider.CompareTag(TriggerTag)))
{
PlaySelectedTrack();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7511c4dccd4cbd54ab12529a4389e81c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eb288b5f3412c824798dab9dbb7c7a9a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
namespace MoreMountains.Tools
{
/// <summary>
/// A definition of a song, a part of a MMSM Playlist
/// </summary>
[Serializable]
public class MMSMPlaylistSong
{
/// the name of the song, used only for organizational purposes in the inspector
[Tooltip("the name of the song, used only for organizational purposes in the inspector")]
public string Name;
/// the clip to play when this song plays
[Tooltip("the clip to play when this song plays")]
public AudioClip Clip;
/// the amount of time this song's been played
[Tooltip("the amount of time this song's been played")]
[MMReadOnly]
public int PlayCount;
/// the many options to control this song
[Tooltip("the many options to control this song")]
public MMSoundManagerPlayOptions Options;
/// <summary>
/// On init, we reset our play count
/// </summary>
public virtual void Initialization()
{
PlayCount = 0;
}
}
[CreateAssetMenu(menuName = "MoreMountains/Audio/MMSM Playlist")]
[Serializable]
public class MMSMPlaylist : ScriptableObject
{
public enum PlayModes { PlayForever, PlayOnce, PlayXTimes }
public enum PlayOrders { Normal, ReverseOrder, Random, RandomUnique }
[Header("Play Modes")]
/// the sound manager track on which to play this playlist's songs
[Tooltip("the sound manager track on which to play this playlist's songs")]
public MMSoundManager.MMSoundManagerTracks Track = MMSoundManager.MMSoundManagerTracks.Music;
/// the order in which to play songs (top to bottom, bottom to top, random, or random while trying to maintain playcount across songs
[Tooltip("the order in which to play songs (top to bottom, bottom to top, random, or random while trying to maintain playcount across songs")]
public PlayOrders PlayOrder = PlayOrders.Normal;
/// if this is true, random seed will be randomized by the system clock
[Tooltip("if this is true, random seed will be randomized by the system clock")]
[MMEnumCondition("PlayOrder", (int)PlayOrders.Random, (int)PlayOrders.RandomUnique)]
public bool RandomizeOrderSeed = true;
/// whether to play this playlist forever, only once, or play songs until total playcount reaches MaxAmountOfPlays
[Tooltip("whether to play this playlist forever, only once, or play songs until total playcount reaches MaxAmountOfPlays")]
public PlayModes PlayMode = PlayModes.PlayForever;
/// when in PlayXTimes mode, the max amount of plays before this playlist ends
[Tooltip("when in PlayXTimes mode, the max amount of plays before this playlist ends")]
[MMEnumCondition("PlayMode", (int)PlayModes.PlayXTimes)]
public int MaxAmountOfPlays = 10;
/// a playlist to switch to when reaching the end of this playlist
[Tooltip("a playlist to switch to when reaching the end of this playlist")]
[MMEnumCondition("PlayMode",(int)PlayModes.PlayOnce, (int)PlayModes.PlayXTimes)]
public MMSMPlaylist NextPlaylist;
/// the list of songs to play on this playlist
[Tooltip("the list of songs to play on this playlist")]
public List<MMSMPlaylistSong> Songs;
[Header("Debug")]
/// the total number of times songs in this playlist have been played
[Tooltip("the total number of times songs in this playlist have been played ")]
[MMReadOnly]
public int PlayCount;
protected List<int> _randomUniqueCandidates;
/// <summary>
/// On init, we initialize all our songs
/// </summary>
public virtual void Initialization()
{
PlayCount = 0;
_randomUniqueCandidates = new List<int>();
foreach (MMSMPlaylistSong song in Songs)
{
song.Initialization();
}
}
/// <summary>
/// Picks the index of the next song to play, returns the index of the song, or -2 if the end of the
/// playlist's been reached, and -1 if the player should go idle
/// </summary>
/// <param name="direction"></param>
/// <returns>
/// -2 : end of playlist
/// -1 : go to idle
/// 0+ : next index to play in the playlist
/// </returns>
public virtual int PickNextIndex(int direction, int currentSongIndex, ref int queuedSongIndex)
{
int newIndex = currentSongIndex;
if (Songs.Count == 0)
{
return -1;
}
if (queuedSongIndex != -1)
{
int newRequestedIndex = queuedSongIndex;
queuedSongIndex = -1;
return newRequestedIndex;
}
if ((PlayCount >= Songs.Count) && (PlayMode == PlayModes.PlayOnce))
{
return -2;
}
if ((PlayMode == PlayModes.PlayXTimes) && (PlayCount >= MaxAmountOfPlays))
{
return -2;
}
switch (PlayOrder)
{
case PlayOrders.Random:
while (newIndex == currentSongIndex)
{
newIndex = Random.Range(0, Songs.Count);
}
return newIndex;
case PlayOrders.RandomUnique:
bool allPlayed = true;
int lowestPlayCount = int.MaxValue;
_randomUniqueCandidates.Clear();
for (int i = 0; i < Songs.Count; i++)
{
if (Songs[i].PlayCount <= lowestPlayCount && i != currentSongIndex)
{
allPlayed = false;
lowestPlayCount = Songs[i].PlayCount;
_randomUniqueCandidates.Add(i);
}
}
if (allPlayed)
{
while (newIndex == currentSongIndex)
{
newIndex = Random.Range(0, Songs.Count);
}
}
else
{
int random = Random.Range(0, _randomUniqueCandidates.Count);
newIndex = _randomUniqueCandidates[random];
}
return newIndex;
case PlayOrders.Normal:
break;
case PlayOrders.ReverseOrder:
direction = -1;
break;
}
if (direction > 0)
{
newIndex = (currentSongIndex + 1) % Songs.Count;
}
else
{
newIndex = (currentSongIndex - 1);
if (newIndex < 0)
{
newIndex = Songs.Count - 1;
}
}
return newIndex;
}
/// <summary>
/// Resets the playlist's play count and the playcount of all songs
/// </summary>
public virtual void ResetPlayCount()
{
PlayCount = 0;
foreach (MMSMPlaylistSong song in Songs)
{
song.PlayCount = 0;
}
}
/// <summary>
/// On Validate we initialize our options
/// </summary>
protected virtual void OnValidate()
{
foreach (MMSMPlaylistSong song in Songs)
{
if (!song.Options.Initialized)
{
song.Options = MMSoundManagerPlayOptions.Default;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc7924747b6dade48ac9a15d72b5176b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,879 @@
using System;
using UnityEngine;
using UnityEngine.Serialization;
using Random = UnityEngine.Random;
namespace MoreMountains.Tools
{
public class MMSMPlaylistManager : MMMonoBehaviour
{
/// the possible states this playlist can be in
public enum PlaylistManagerStates
{
Idle,
Playing,
Paused
}
[MMInspectorGroup("Settings", true, 18)]
/// the channel used to target this playlist manager by playlist remote or playlist feedbacks
[Tooltip("the channel used to target this playlist manager by playlist remote or playlist feedbacks")]
public int Channel = 0;
/// the current playlist this manager will play
[Tooltip("the current playlist this manager will play")]
public MMSMPlaylist Playlist;
/// whether this playlist manager should auto play on start or not
[Tooltip("whether this playlist manager should auto play on start or not")]
public bool PlayOnStart = false;
/// a global volume multiplier to apply when playing a song
[Tooltip("a global volume multiplier to apply when playing a song")]
[Range(0f,1f)]
public float VolumeMultiplier = 1f;
/// a pitch multiplier to apply to all songs when playing them
[Tooltip("a pitch multiplier to apply to all songs when playing them")]
[Range(0f,20f)]
public float PitchMultiplier = 1f;
/// if this is true, this playlist manager will persist from scene to scene and will keep playing
[Tooltip("if this is true, this playlist manager will persist from scene to scene and will keep playing")]
public bool Persistent = false;
/// if this is true, this singleton will auto detach if it finds itself parented on awake
[Tooltip("if this is true, this singleton will auto detach if it finds itself parented on awake")]
[MMCondition("Persistent", true)]
public bool AutomaticallyUnparentOnAwake = true;
/// if this is true, this playlist will automatically pause/resume OnApplicationPause, useful if you've prevented your game from running in the background
[Tooltip("if this is true, this playlist will automatically pause/resume OnApplicationPause, useful if you've prevented your game from running in the background")]
public bool AutoHandleApplicationPause = true;
[MMInspectorGroup("Fade", true, 12)]
/// whether or not sounds should fade in when they start playing
[Tooltip("whether or not sounds should fade in when they start playing")]
public bool FadeIn;
/// whether or not sounds should fade out when they stop playing
[Tooltip("whether or not sounds should fade out when they stop playing")]
public bool FadeOut;
/// the duration of the fade, in seconds
[Tooltip("the duration of the fade, in seconds")]
public float FadeDuration = 1f;
/// the tween to use when fading the sound
[Tooltip("the tween to use when fading the sound")]
public MMTweenType FadeTween = new MMTweenType(MMTween.MMTweenCurve.EaseInCubic);
[MMInspectorGroup("Time", true, 20)]
/// whether or not the playlist manager should have its pitch multiplier value driven by the current timescale. If set to true, songs would appear to slow down when time is slowed down, and to speed up when time scale is higher than normal
[Tooltip("whether or not the playlist manager should have its pitch multiplier value driven by the current timescale. If set to true, songs would appear to slow down when time is slowed down, and to speed up when time scale is higher than normal")]
public bool BindPitchToTimeScale = false;
/// the values to remap timescale from (min and max) - when timescale is equal to TimescaleRemapFrom.x, the pitch multiplier will be TimescaleRemapTo.x
[Tooltip("the values to remap timescale from (min and max) - when timescale is equal to TimescaleRemapFrom.x, the pitch multiplier will be TimescaleRemapTo.x")]
[MMCondition("BindPitchToTimeScale", true)]
public Vector2 TimescaleRemapFrom = new Vector2(0f,2f);
/// the values to remap timescale to (min and max) - when timescale is equal to TimescaleRemapFrom.x, the pitch multiplier will be TimescaleRemapTo.x
[Tooltip("the values to remap timescale to (min and max) - when timescale is equal to TimescaleRemapFrom.x, the pitch multiplier will be TimescaleRemapTo.x")]
[MMCondition("BindPitchToTimeScale", true)]
public Vector2 TimescaleRemapTo = new Vector2(0.8f,1.2f);
[MMInspectorGroup("Status", true, 14)]
/// the current state of the playlist, debug display only
[Tooltip("the current state of the playlist, debug display only")]
[MMReadOnly]
public PlaylistManagerStates DebugCurrentManagerState = PlaylistManagerStates.Idle;
/// the index we're currently playing
[Tooltip("the index we're currently playing")]
[MMReadOnly]
public int CurrentSongIndex = -1;
/// the name of the song that is currently playing
[Tooltip("the name of the song that is currently playing")]
[MMReadOnly]
public string CurrentSongName;
/// the current state of this playlist
[MMReadOnly]
public MMStateMachine<PlaylistManagerStates> PlaylistManagerState;
/// the time of the currently playing song
[Tooltip("the time of the currently playing song")]
[MMReadOnly]
public float CurrentTime;
/// the time (in seconds) left on the song currently playing
[Tooltip("the time (in seconds) left on the song currently playing")]
[MMReadOnly]
public float CurrentTimeLeft;
/// the total duration of the song currently playing
[Tooltip("the total duration of the song currently playing")]
[MMReadOnly]
public float CurrentClipDuration;
/// the current normalized progress of the song currently playing
[Tooltip("the current normalized progress of the song currently playing")]
[Range(0f, 1f)]
public float CurrentProgress = 0;
[MMInspectorGroup("Test Controls", true, 15)]
/// a play test button
[MMInspectorButton("Play")]
public bool PlayButton;
/// a stop test button
[MMInspectorButton("Stop")]
public bool StopButton;
/// a pause test button
[MMInspectorButton("Pause")]
public bool PauseButton;
/// a next song test button
[MMInspectorButton("PlayPreviousSong")]
public bool PreviousButton;
/// a next song test button
[MMInspectorButton("PlayNextSong")]
public bool NextButton;
/// the index of the song to play when pressing the PlayTargetSong button
[Tooltip("the index of the song to play when pressing the PlayTargetSong button")]
public int TargetSongIndex = 0;
/// a next song test button
[MMInspectorButton("PlayTargetSong")]
public bool TargetSongButton;
/// a next song test button
[MMInspectorButton("QueueTargetSong")]
public bool QueueTargetSongButton;
/// a next song test button
[MMInspectorButton("SetCurrentSongToLoop")]
public bool SetLoopTargetSongButton;
/// a next song test button
[MMInspectorButton("StopCurrentSongFromLooping")]
public bool StopLoopTargetSongButton;
/// a playlist you can set to use with the SetTargetPlaylist and PlayTargetPlaylist buttons
[Tooltip("a playlist you can set to use with the SetTargetPlaylist and PlayTargetPlaylist buttons")]
public MMSMPlaylist TestPlaylist;
/// a test button used to set a new playlist
[MMInspectorButton("SetTargetPlaylist")]
public bool SetTargetPlaylistButton;
/// a test button used to play the target playlist
[MMInspectorButton("PlayTargetPlaylist")]
public bool PlayTargetPlaylistButton;
/// a test button used to reset the play count
[MMInspectorButton("ResetPlayCount")]
public bool ResetPlayCountButton;
/// a slider used to test volume control
[Tooltip("a slider used to test volume control")]
[Range(0f,2f)]
public float TestVolumeControl = 1f;
/// a slider used to test speed control
[Tooltip("a slider used to test speed control")]
[Range(0f,20f)]
public float TestPlaybackSpeedControl = 1f;
/// whether or not this playlist manager is currently playing
public virtual bool IsPlaying => (_currentlyPlayingAudioSource != null && _currentlyPlayingAudioSource.isPlaying);
/// a delegate used to trigger events along the lifecycle of the playlist manager
public delegate void PlaylistEvent();
/// an event that gets triggered when a song starts
public PlaylistEvent OnSongStart;
/// an event that gets triggered when a song ends
public PlaylistEvent OnSongEnd;
/// an event that gets triggered when the playlist gets paused
public PlaylistEvent OnPause;
/// an event that gets triggered when the playlist gets stopped
public PlaylistEvent OnStop;
/// an event that gets triggered when the playlist gets changed for another one
public PlaylistEvent OnPlaylistChange;
/// an event that gets triggered when a playlist ends
public PlaylistEvent OnPlaylistEnd;
protected bool _shouldResumeOnApplicationPause = false;
public static bool HasInstance => _instance != null;
public static MMSMPlaylistManager Current => _instance;
protected static MMSMPlaylistManager _instance;
protected int _queuedSongIndex = -1;
protected AudioSource _currentlyPlayingAudioSource;
protected MMSoundManagerPlayOptions _options;
protected float _lastTestVolumeControl = 1f;
protected float _lastTestPlaybackSpeedControl = 1f;
internal bool _listeningToEvents = false;
#region INITIALIZATION
/// <summary>
/// Singleton design pattern
/// </summary>
/// <value>The instance.</value>
public static MMSMPlaylistManager Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<MMSMPlaylistManager> ();
if (_instance == null)
{
GameObject obj = new GameObject ();
obj.name = typeof(MMPlaylist).Name + "_AutoCreated";
_instance = obj.AddComponent<MMSMPlaylistManager> ();
}
}
return _instance;
}
}
/// <summary>
/// On awake, we check if there's already a copy of the object in the scene. If there's one, we destroy it.
/// </summary>
protected virtual void Awake ()
{
InitializeSingleton();
}
/// <summary>
/// Initializes the singleton.
/// </summary>
protected virtual void InitializeSingleton()
{
if (!Application.isPlaying)
{
return;
}
if (!Persistent)
{
return;
}
if (AutomaticallyUnparentOnAwake)
{
this.transform.SetParent(null);
}
if (_instance == null)
{
//If I am the first instance, make me the Singleton
_instance = this;
DontDestroyOnLoad (transform.gameObject);
}
else
{
//If a Singleton already exists and you find
//another reference in scene, destroy it!
if(this != _instance)
{
Destroy(this.gameObject);
}
}
}
/// <summary>
/// On Start we initialize our playlist
/// </summary>
protected virtual void Start()
{
Initialization();
if (PlayOnStart)
{
PlayFirstSong();
}
if (!_listeningToEvents)
{
StartListening();
}
}
/// <summary>
/// On init we initialize our state machine and start playing if needed
/// </summary>
protected virtual void Initialization()
{
InitializeRandomSeed();
Playlist.Initialization();
InitializePlaylistManagerState();
}
/// <summary>
/// Initializes the random seed if needed
/// </summary>
protected virtual void InitializeRandomSeed()
{
if (
((Playlist.PlayOrder == MMSMPlaylist.PlayOrders.Random) || (Playlist.PlayOrder == MMSMPlaylist.PlayOrders.RandomUnique))
&& Playlist.RandomizeOrderSeed
)
{
Random.InitState(System.Environment.TickCount);
}
}
/// <summary>
/// Inits the state machine
/// </summary>
protected virtual void InitializePlaylistManagerState()
{
PlaylistManagerState = new MMStateMachine<PlaylistManagerStates>(this.gameObject, true);
ChangePlaylistManagerState(PlaylistManagerStates.Idle);
}
/// <summary>
/// a method used to update the state machine
/// </summary>
/// <param name="newManagerState"></param>
protected virtual void ChangePlaylistManagerState(PlaylistManagerStates newManagerState)
{
PlaylistManagerState.ChangeState(newManagerState);
#if UNITY_EDITOR
DebugCurrentManagerState = newManagerState;
#endif
}
#endregion
#region LIFECYCLE
/// <summary>
/// on update, self disables if needed,
/// </summary>
protected virtual void Update()
{
if (PlaylistManagerState.CurrentState == PlaylistManagerStates.Idle)
{
this.enabled = false;
return;
}
UpdateTimeAndProgress();
HandleTimescale();
HandleEndOfSong();
}
/// <summary>
/// On update, we update our pitch multiplier to match our timescale if necessary
/// </summary>
protected virtual void HandleTimescale()
{
if (BindPitchToTimeScale)
{
float remappedTimescale = MMMaths.Remap(Time.timeScale, TimescaleRemapFrom.x, TimescaleRemapFrom.y,
TimescaleRemapTo.x, TimescaleRemapTo.y);
SetPitchMultiplier(remappedTimescale);
}
}
/// <summary>
/// Updates the various time counters
/// </summary>
protected virtual void UpdateTimeAndProgress()
{
CurrentTime = _currentlyPlayingAudioSource.time;
CurrentTimeLeft = _currentlyPlayingAudioSource.clip.length - _currentlyPlayingAudioSource.time;
CurrentProgress = CurrentTime / CurrentClipDuration;
}
/// <summary>
/// Picks and plays the first song
/// </summary>
protected virtual void PlayFirstSong()
{
Playlist.Initialization();
CurrentSongIndex = -1;
HandleNextSong(1);
}
/// <summary>
/// Detects end of song and moves on to the next one
/// </summary>
protected virtual void HandleEndOfSong()
{
if (PlaylistManagerState.CurrentState != PlaylistManagerStates.Playing)
{
return;
}
if (_currentlyPlayingAudioSource.isPlaying)
{
if (FadeIn && FadeOut && (CurrentTimeLeft < FadeDuration))
{
HandleNextSong(1);
}
return;
}
HandleNextSong(1);
}
/// <summary>
/// Determines the next song to play and triggers the play
/// </summary>
/// <param name="direction"></param>
protected virtual void HandleNextSong(int direction)
{
if (IsPlaying)
{
OnSongEnd?.Invoke();
}
int newIndex = Playlist.PickNextIndex(direction, CurrentSongIndex, ref _queuedSongIndex);
if (newIndex == -1)
{
ChangePlaylistManagerState(PlaylistManagerStates.Idle);
}
if (newIndex == -2)
{
HandleEndOfPlaylist();
return;
}
if (newIndex >= 0 && newIndex < Playlist.Songs.Count)
{
PlaySongAt(newIndex);
}
}
/// <summary>
/// Handles the end of playlist, triggers a new one if needed
/// </summary>
protected virtual void HandleEndOfPlaylist()
{
OnPlaylistEnd?.Invoke();
if (Playlist.NextPlaylist != null)
{
ChangePlaylistAndPlay(Playlist.NextPlaylist);
return;
}
ChangePlaylistManagerState(PlaylistManagerStates.Idle);
}
#endregion
#region CONTROLS
/// <summary>
/// Use this method to either play the first song of the playlist, or resume after a pause
/// </summary>
public virtual void Play()
{
switch (PlaylistManagerState.CurrentState)
{
case PlaylistManagerStates.Idle:
PlayFirstSong();
break;
case PlaylistManagerStates.Paused:
MMSoundManager.Instance.ResumeSound(_currentlyPlayingAudioSource);
ChangePlaylistManagerState(PlaylistManagerStates.Playing);
break;
case PlaylistManagerStates.Playing:
// do nothing
break;
}
}
/// <summary>
/// Plays the song at the specified index
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public virtual void PlaySongAt(int songIndex)
{
this.enabled = true;
// if we don't have a song, we stop
if (Playlist.Songs.Count == 0)
{
return;
}
Stop();
// starts the new song
_options = Playlist.Songs[songIndex].Options;
_options.MmSoundManagerTrack = Playlist.Track;
_options.Volume *= VolumeMultiplier;
_options.Pitch *= PitchMultiplier;
_options.Persistent = Persistent;
_currentlyPlayingAudioSource = MMSoundManagerSoundPlayEvent.Trigger(Playlist.Songs[songIndex].Clip, _options);
OnSongStart?.Invoke();
if (FadeIn)
{
MMSoundManager.Instance.FadeSound(_currentlyPlayingAudioSource, FadeDuration,
0f, _currentlyPlayingAudioSource.volume, FadeTween, false);
}
// updates our state
ChangePlaylistManagerState(PlaylistManagerStates.Playing);
CurrentSongIndex = songIndex;
CurrentClipDuration = _currentlyPlayingAudioSource.clip.length;
CurrentSongName = Playlist.Songs[songIndex].Name;
Playlist.Songs[songIndex].PlayCount++;
Playlist.PlayCount++;
// we trigger an event for other classes to listen to, letting them know a new song has started
MMPlaylistNewSongStartedEvent.Trigger(Channel);
}
/// <summary>
/// Pauses the current song
/// </summary>
public virtual void Pause()
{
if (PlaylistManagerState.CurrentState != PlaylistManagerStates.Playing)
{
return;
}
MMSoundManager.Instance.PauseSound(_currentlyPlayingAudioSource);
ChangePlaylistManagerState(PlaylistManagerStates.Paused);
OnPause?.Invoke();
}
/// <summary>
/// Stops the song currently playing
/// </summary>
public virtual void Stop()
{
// we stop our current song
if ((_currentlyPlayingAudioSource == null) || !_currentlyPlayingAudioSource.isPlaying)
{
return;
}
if (FadeOut)
{
if ( MMSoundManager.Instance.SoundIsFadingOut(_currentlyPlayingAudioSource) )
{
return;
}
MMSoundManager.Instance.FadeSound(_currentlyPlayingAudioSource, FadeDuration,
_currentlyPlayingAudioSource.volume, 0f, FadeTween, true);
}
else
{
MMSoundManager.Instance.FreeSound(_currentlyPlayingAudioSource);
}
ChangePlaylistManagerState(PlaylistManagerStates.Idle);
OnStop?.Invoke();
}
/// <summary>
/// Stops the current song, lets you specify whether or not to fade it out
/// </summary>
public virtual void StopWithFade(bool withFade = true)
{
if (PlaylistManagerState.CurrentState == PlaylistManagerStates.Idle)
{
return;
}
if (!withFade)
{
MMSoundManager.Instance.FreeSound(_currentlyPlayingAudioSource);
OnStop?.Invoke();
}
else
{
Stop();
}
CurrentSongIndex = -1;
ChangePlaylistManagerState(PlaylistManagerStates.Idle);
}
/// <summary>
/// Will change the current song's loop status
/// </summary>
public virtual void SetCurrentSongLoop(bool loop)
{
_currentlyPlayingAudioSource.loop = loop;
}
/// <summary>
/// Plays the next song in the playlist
/// </summary>
public virtual void PlayNextSong()
{
Stop();
HandleNextSong(1);
}
/// <summary>
/// Plays the previous song in the playlist
/// </summary>
public virtual void PlayPreviousSong()
{
Stop();
HandleNextSong(-1);
}
/// <summary>
/// Queues the song at the specified index to play once the currently playing song finishes
/// </summary>
/// <param name="songIndex"></param>
public virtual void QueueSongAtIndex(int songIndex)
{
_queuedSongIndex = songIndex;
}
/// <summary>
/// Changes the playlist for the specified one, doesn't play a song there, it'll play once the song currently playing ends
/// </summary>
/// <param name="newPlaylist"></param>
public virtual void ChangePlaylist(MMSMPlaylist newPlaylist)
{
Playlist = newPlaylist;
Playlist.Initialization();
CurrentSongIndex = -1;
OnPlaylistChange?.Invoke();
}
/// <summary>
/// Changes the playlist for the specified one, and plays its first song
/// </summary>
/// <param name="newPlaylist"></param>
public virtual void ChangePlaylistAndPlay(MMSMPlaylist newPlaylist)
{
ChangePlaylist(newPlaylist);
PlaySongAt(0);
}
/// <summary>
/// resets all play counts (playlist and songs)
/// </summary>
public virtual void ResetPlayCount()
{
Playlist.ResetPlayCount();
}
/// <summary>
/// Sets a new volume multiplier
/// </summary>
/// <param name="newVolumeMultiplier"></param>
public virtual void SetVolumeMultiplier(float newVolumeMultiplier)
{
float newVolume = Mathf.Clamp(newVolumeMultiplier, 0f, 2f);
MMPlaylistVolumeMultiplierEvent.Trigger(Channel, newVolume, true);
}
/// <summary>
/// Sets a new pitch multiplier
/// </summary>
/// <param name="newPitchMultiplier"></param>
public virtual void SetPitchMultiplier(float newPitchMultiplier)
{
float newPitch = Mathf.Clamp(newPitchMultiplier, 0f, 20f);
MMPlaylistPitchMultiplierEvent.Trigger(Channel, newPitch, true);
}
#endregion
#region DEBUG_METHODS
/// <summary>
/// a debug method used by the inspector to set the target playlist
/// </summary>
protected virtual void SetTargetPlaylist()
{
ChangePlaylist(TestPlaylist);
}
/// <summary>
/// a debug method used by the inspector to play the target playlist
/// </summary>
protected virtual void PlayTargetPlaylist()
{
ChangePlaylistAndPlay(TestPlaylist);
}
/// <summary>
/// a debug method used by the inspector to queue the specified song
/// </summary>
protected virtual void QueueTargetSong()
{
int newIndex = Mathf.Clamp(TargetSongIndex, 0, Playlist.Songs.Count - 1);
QueueSongAtIndex(newIndex);
}
/// <summary>
/// a debug method used by the inspector to play the specified song
/// </summary>
protected virtual void PlayTargetSong()
{
int newIndex = Mathf.Clamp(TargetSongIndex, 0, Playlist.Songs.Count - 1);
PlaySongAt(newIndex);
}
/// <summary>
/// a test method used by the inspector debug button to force the current song from looping
/// </summary>
protected virtual void SetCurrentSongToLoop()
{
SetCurrentSongLoop(true);
}
/// <summary>
/// a test method used by the inspector debug button to prevent the current song from looping
/// </summary>
protected virtual void StopCurrentSongFromLooping()
{
SetCurrentSongLoop(false);
}
#endregion
#region EVENTS
protected virtual void OnPlayEvent(int channel)
{
if (channel != Channel) { return; }
Play();
}
protected virtual void OnPauseEvent(int channel)
{
if (channel != Channel) { return; }
Pause();
}
protected virtual void OnStopEvent(int channel)
{
if (channel != Channel) { return; }
Stop();
}
protected virtual void OnPlayNextEvent(int channel)
{
if (channel != Channel) { return; }
PlayNextSong();
}
protected virtual void OnPlayPreviousEvent(int channel)
{
if (channel != Channel) { return; }
PlayPreviousSong();
}
protected virtual void OnPlayIndexEvent(int channel, int index)
{
if (channel != Channel) { return; }
PlaySongAt(index);
}
protected virtual void OnMMPlaylistVolumeMultiplierEvent(int channel, float newVolumeMultiplier, bool applyVolumeMultiplierInstantly = false)
{
if (channel != Channel) { return; }
VolumeMultiplier = newVolumeMultiplier;
if (applyVolumeMultiplierInstantly)
{
_currentlyPlayingAudioSource.volume = Playlist.Songs[CurrentSongIndex].Options.Volume * VolumeMultiplier;
}
}
protected virtual void OnMMPlaylistPitchMultiplierEvent(int channel, float newPitchMultiplier, bool applyPitchMultiplierInstantly = false)
{
if (channel != Channel) { return; }
PitchMultiplier = newPitchMultiplier;
if (applyPitchMultiplierInstantly)
{
_currentlyPlayingAudioSource.pitch = Playlist.Songs[CurrentSongIndex].Options.Pitch * PitchMultiplier;
}
}
protected virtual void OnMMPlaylistChangeEvent(int channel, MMSMPlaylist newPlaylist, bool andPlay)
{
if (channel != Channel) { return; }
if (andPlay)
{
ChangePlaylistAndPlay(newPlaylist);
}
else
{
ChangePlaylist(newPlaylist);
}
}
/// <summary>
/// Starts listening for events
/// </summary>
public virtual void StartListening()
{
_listeningToEvents = true;
MMPlaylistPauseEvent.Register(OnPauseEvent);
MMPlaylistPlayEvent.Register(OnPlayEvent);
MMPlaylistPlayNextEvent.Register(OnPlayNextEvent);
MMPlaylistPlayPreviousEvent.Register(OnPlayPreviousEvent);
MMPlaylistStopEvent.Register(OnStopEvent);
MMPlaylistPlayIndexEvent.Register(OnPlayIndexEvent);
MMPlaylistVolumeMultiplierEvent.Register(OnMMPlaylistVolumeMultiplierEvent);
MMPlaylistPitchMultiplierEvent.Register(OnMMPlaylistPitchMultiplierEvent);
MMPlaylistChangeEvent.Register(OnMMPlaylistChangeEvent);
}
/// <summary>
/// Stops listening for events
/// </summary>
public virtual void StopListening()
{
_listeningToEvents = false;
MMPlaylistPauseEvent.Unregister(OnPauseEvent);
MMPlaylistPlayEvent.Unregister(OnPlayEvent);
MMPlaylistPlayNextEvent.Unregister(OnPlayNextEvent);
MMPlaylistPlayPreviousEvent.Unregister(OnPlayPreviousEvent);
MMPlaylistStopEvent.Unregister(OnStopEvent);
MMPlaylistPlayIndexEvent.Unregister(OnPlayIndexEvent);
MMPlaylistVolumeMultiplierEvent.Unregister(OnMMPlaylistVolumeMultiplierEvent);
MMPlaylistPitchMultiplierEvent.Unregister(OnMMPlaylistPitchMultiplierEvent);
MMPlaylistChangeEvent.Unregister(OnMMPlaylistChangeEvent);
}
/// <summary>
/// on destroy we stop listening for events
/// </summary>
protected virtual void OnDestroy()
{
StopListening();
}
/// <summary>
/// On ApplicationPause, we pause the playlist and resume it afterwards
/// </summary>
/// <param name="pauseStatus"></param>
protected virtual void OnApplicationPause(bool pauseStatus)
{
if (!AutoHandleApplicationPause)
{
return;
}
if (pauseStatus && PlaylistManagerState.CurrentState == PlaylistManagerStates.Playing)
{
Pause();
_shouldResumeOnApplicationPause = true;
}
if (!pauseStatus && _shouldResumeOnApplicationPause)
{
_shouldResumeOnApplicationPause = false;
Play();
}
}
#if UNITY_EDITOR
protected virtual void OnValidate()
{
if (_lastTestVolumeControl != TestVolumeControl)
{
MMPlaylistVolumeMultiplierEvent.Trigger(Channel, TestVolumeControl, true);
}
if (_lastTestPlaybackSpeedControl != TestPlaybackSpeedControl)
{
MMPlaylistPitchMultiplierEvent.Trigger(Channel, TestPlaybackSpeedControl, true);
}
}
#endif
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8f2b35cd0da71894d92b885cf5e55c51
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9a487ae3f9cad794ebd56ec92cc8e0c7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e27d0ce351322694c8fa535acdf6fdad
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
public enum MMSoundManagerAllSoundsControlEventTypes
{
Pause, Play, Stop, Free, FreeAllButPersistent, FreeAllLooping
}
/// <summary>
/// This event will let you pause/play/stop/free all sounds playing through the MMSoundManager at once
///
/// Example : MMSoundManagerAllSoundsControlEvent.Trigger(MMSoundManagerAllSoundsControlEventTypes.Stop);
/// will stop all sounds playing at once
/// </summary>
public struct MMSoundManagerAllSoundsControlEvent
{
public MMSoundManagerAllSoundsControlEventTypes EventType;
public MMSoundManagerAllSoundsControlEvent(MMSoundManagerAllSoundsControlEventTypes eventType)
{
EventType = eventType;
}
static MMSoundManagerAllSoundsControlEvent e;
public static void Trigger(MMSoundManagerAllSoundsControlEventTypes eventType)
{
e.EventType = eventType;
MMEventManager.TriggerEvent(e);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 934c751bdc1eca14bb13d3c6b311c60c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
public enum MMSoundManagerEventTypes
{
SaveSettings,
LoadSettings,
ResetSettings,
SettingsLoaded
}
/// <summary>
/// This event will let you trigger a save/load/reset on the MMSoundManager settings
///
/// Example : MMSoundManagerEvent.Trigger(MMSoundManagerEventTypes.SaveSettings);
/// will save settings.
/// </summary>
public struct MMSoundManagerEvent
{
public MMSoundManagerEventTypes EventType;
public MMSoundManagerEvent(MMSoundManagerEventTypes eventType)
{
EventType = eventType;
}
static MMSoundManagerEvent e;
public static void Trigger(MMSoundManagerEventTypes eventType)
{
e.EventType = eventType;
MMEventManager.TriggerEvent(e);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0f7449660378a3c4aa7ab771c5a336ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
public enum MMSoundManagerSoundControlEventTypes
{
Pause,
Resume,
Stop,
Free
}
/// <summary>
/// An event used to control a specific sound on the MMSoundManager.
/// You can either search for it by ID, or directly pass an audiosource if you have it.
///
/// Example : MMSoundManagerSoundControlEvent.Trigger(MMSoundManagerSoundControlEventTypes.Stop, 33);
/// will cause the sound(s) with an ID of 33 to stop playing
/// </summary>
public struct MMSoundManagerSoundControlEvent
{
/// the ID of the sound to control (has to match the one used to play it)
public int SoundID;
/// the control mode
public MMSoundManagerSoundControlEventTypes MMSoundManagerSoundControlEventType;
/// the audiosource to control (if specified)
public AudioSource TargetSource;
public MMSoundManagerSoundControlEvent(MMSoundManagerSoundControlEventTypes eventType, int soundID, AudioSource source = null)
{
SoundID = soundID;
TargetSource = source;
MMSoundManagerSoundControlEventType = eventType;
}
static MMSoundManagerSoundControlEvent e;
public static void Trigger(MMSoundManagerSoundControlEventTypes eventType, int soundID, AudioSource source = null)
{
e.SoundID = soundID;
e.TargetSource = source;
e.MMSoundManagerSoundControlEventType = eventType;
MMEventManager.TriggerEvent(e);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c47cf85e660a3714e98c5ac0a9c153cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
/// <summary>
/// This event will let you pause
///
/// Example : MMSoundManagerSoundFadeEvent.Trigger(33, 2f, 0.3f, new MMTweenType(MMTween.MMTweenCurve.EaseInElastic));
/// will fade the sound with an ID of 33 towards a volume of 0.3, over 2 seconds, on an elastic curve
/// </summary>
public struct MMSoundManagerSoundFadeEvent
{
public enum Modes { PlayFade, StopFade }
/// whether we are fading a sound, or stopping an existing fade
public Modes Mode;
/// the ID of the sound to fade
public int SoundID;
/// the duration of the fade (in seconds)
public float FadeDuration;
/// the volume towards which to fade this sound
public float FinalVolume;
/// the tween over which to fade this sound
public MMTweenType FadeTween;
public MMSoundManagerSoundFadeEvent(Modes mode, int soundID, float fadeDuration, float finalVolume, MMTweenType fadeTween)
{
Mode = mode;
SoundID = soundID;
FadeDuration = fadeDuration;
FinalVolume = finalVolume;
FadeTween = fadeTween;
}
static MMSoundManagerSoundFadeEvent e;
public static void Trigger(Modes mode, int soundID, float fadeDuration, float finalVolume, MMTweenType fadeTween)
{
e.Mode = mode;
e.SoundID = soundID;
e.FadeDuration = fadeDuration;
e.FinalVolume = finalVolume;
e.FadeTween = fadeTween;
MMEventManager.TriggerEvent(e);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b96a37932e03d84586c714caa05bf79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
namespace MoreMountains.Tools
{
/// <summary>
/// This event will let you play a sound on the MMSoundManager
///
/// Example : MMSoundManagerSoundPlayEvent.Trigger(ExplosionSfx, MMSoundManager.MMSoundManagerTracks.Sfx, this.transform.position);
/// will play a clip (here ours is called ExplosionSfx) on the SFX track, at the position of the object calling it
/// </summary>
public struct MMSoundManagerSoundPlayEvent
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; }
public delegate AudioSource Delegate(AudioClip clip, MMSoundManagerPlayOptions options);
static private event Delegate OnEvent;
static public void Register(Delegate callback)
{
OnEvent += callback;
}
static public void Unregister(Delegate callback)
{
OnEvent -= callback;
}
static public AudioSource Trigger(AudioClip clip, MMSoundManagerPlayOptions options)
{
return OnEvent?.Invoke(clip, options);
}
static public AudioSource Trigger(AudioClip audioClip, MMSoundManager.MMSoundManagerTracks mmSoundManagerTrack, Vector3 location,
bool loop = false, float volume = 1.0f, int ID = 0,
bool fade = false, float fadeInitialVolume = 0f, float fadeDuration = 1f, MMTweenType fadeTween = null,
bool persistent = false,
AudioSource recycleAudioSource = null, AudioMixerGroup audioGroup = null,
float pitch = 1f, float panStereo = 0f, float spatialBlend = 0.0f,
bool soloSingleTrack = false, bool soloAllTracks = false, bool autoUnSoloOnEnd = false,
bool bypassEffects = false, bool bypassListenerEffects = false, bool bypassReverbZones = false, int priority = 128, float reverbZoneMix = 1f,
float dopplerLevel = 1f, int spread = 0, AudioRolloffMode rolloffMode = AudioRolloffMode.Logarithmic, float minDistance = 1f, float maxDistance = 500f)
{
MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
options.MmSoundManagerTrack = mmSoundManagerTrack;
options.Location = location;
options.Loop = loop;
options.Volume = volume;
options.ID = ID;
options.Fade = fade;
options.FadeInitialVolume = fadeInitialVolume;
options.FadeDuration = fadeDuration;
options.FadeTween = fadeTween;
options.Persistent = persistent;
options.RecycleAudioSource = recycleAudioSource;
options.AudioGroup = audioGroup;
options.Pitch = pitch;
options.PanStereo = panStereo;
options.SpatialBlend = spatialBlend;
options.SoloSingleTrack = soloSingleTrack;
options.SoloAllTracks = soloAllTracks;
options.AutoUnSoloOnEnd = autoUnSoloOnEnd;
options.BypassEffects = bypassEffects;
options.BypassListenerEffects = bypassListenerEffects;
options.BypassReverbZones = bypassReverbZones;
options.Priority = priority;
options.ReverbZoneMix = reverbZoneMix;
options.DopplerLevel = dopplerLevel;
options.Spread = spread;
options.RolloffMode = rolloffMode;
options.MinDistance = minDistance;
options.MaxDistance = maxDistance;
return OnEvent?.Invoke(audioClip, options);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa5bc168399356b4e8b48a498dfed282
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
public enum MMSoundManagerTrackEventTypes
{
MuteTrack,
UnmuteTrack,
SetVolumeTrack,
PlayTrack,
PauseTrack,
StopTrack,
FreeTrack
}
/// <summary>
/// This feedback will let you mute, unmute, play, pause, stop, free or set the volume of a selected track
///
/// Example : MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.PauseTrack,MMSoundManager.MMSoundManagerTracks.UI);
/// will pause the entire UI track
/// </summary>
public struct MMSoundManagerTrackEvent
{
/// the order to pass to the track
public MMSoundManagerTrackEventTypes TrackEventType;
/// the track to pass the order to
public MMSoundManager.MMSoundManagerTracks Track;
/// if in SetVolume mode, the volume to which to set the track to
public float Volume;
public MMSoundManagerTrackEvent(MMSoundManagerTrackEventTypes trackEventType, MMSoundManager.MMSoundManagerTracks track = MMSoundManager.MMSoundManagerTracks.Master, float volume = 1f)
{
TrackEventType = trackEventType;
Track = track;
Volume = volume;
}
static MMSoundManagerTrackEvent e;
public static void Trigger(MMSoundManagerTrackEventTypes trackEventType, MMSoundManager.MMSoundManagerTracks track = MMSoundManager.MMSoundManagerTracks.Master, float volume = 1f)
{
e.TrackEventType = trackEventType;
e.Track = track;
e.Volume = volume;
MMEventManager.TriggerEvent(e);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bda4c1805f4bd2f49923dfac1e1e004b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
/// <summary>
/// This event will let you order the MMSoundManager to fade an entire track's sounds' volume towards the specified FinalVolume
///
/// Example : MMSoundManagerTrackFadeEvent.Trigger(MMSoundManager.MMSoundManagerTracks.Music, 2f, 0.5f, new MMTweenType(MMTween.MMTweenCurve.EaseInCubic));
/// will fade the volume of the music track towards 0.5, over 2 seconds, using an ease in cubic tween
/// </summary>
public struct MMSoundManagerTrackFadeEvent
{
public enum Modes { PlayFade, StopFade }
/// whether we are fading a sound, or stopping an existing fade
public Modes Mode;
/// the track to fade the volume of
public MMSoundManager.MMSoundManagerTracks Track;
/// the duration of the fade, in seconds
public float FadeDuration;
/// the final volume to fade towards
public float FinalVolume;
/// the tween to use when fading
public MMTweenType FadeTween;
public MMSoundManagerTrackFadeEvent(Modes mode, MMSoundManager.MMSoundManagerTracks track, float fadeDuration, float finalVolume, MMTweenType fadeTween)
{
Mode = mode;
Track = track;
FadeDuration = fadeDuration;
FinalVolume = finalVolume;
FadeTween = fadeTween;
}
static MMSoundManagerTrackFadeEvent e;
public static void Trigger(Modes mode, MMSoundManager.MMSoundManagerTracks track, float fadeDuration, float finalVolume, MMTweenType fadeTween)
{
e.Mode = mode;
e.Track = track;
e.FadeDuration = fadeDuration;
e.FinalVolume = finalVolume;
e.FadeTween = fadeTween;
MMEventManager.TriggerEvent(e);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 03dee039a0942664ba28a68a924bd105
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 614edb33c38bcf949846d5675f29f9fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- settingsSo: {fileID: 11400000, guid: 07ea3ff88e1ecb84d91f58aa804badc6, type: 2}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.SceneManagement;
namespace MoreMountains.Tools
{
/// <summary>
/// This class manages an object pool of audiosources
/// </summary>
[Serializable]
public class MMSoundManagerAudioPool
{
protected List<AudioSource> _pool;
/// <summary>
/// Fills the pool with ready-to-use audiosources
/// </summary>
/// <param name="poolSize"></param>
/// <param name="parent"></param>
public virtual void FillAudioSourcePool(int poolSize, Transform parent)
{
if (_pool == null)
{
_pool = new List<AudioSource>();
}
if ((poolSize <= 0) || (_pool.Count >= poolSize))
{
return;
}
foreach (AudioSource source in _pool)
{
UnityEngine.Object.Destroy(source.gameObject);
}
for (int i = 0; i < poolSize; i++)
{
GameObject temporaryAudioHost = new GameObject("MMAudioSourcePool_"+i);
SceneManager.MoveGameObjectToScene(temporaryAudioHost.gameObject, parent.gameObject.scene);
AudioSource tempSource = temporaryAudioHost.AddComponent<AudioSource>();
MMFollowTarget followTarget = temporaryAudioHost.AddComponent<MMFollowTarget>();
followTarget.enabled = false;
followTarget.DisableSelfOnSetActiveFalse = true;
temporaryAudioHost.transform.SetParent(parent);
temporaryAudioHost.SetActive(false);
_pool.Add(tempSource);
}
}
/// <summary>
/// Disables an audio source after it's done playing
/// </summary>
/// <param name="duration"></param>
/// <param name="targetObject"></param>
/// <returns></returns>
public virtual IEnumerator AutoDisableAudioSource(float duration, AudioSource source, AudioClip clip, bool doNotAutoRecycleIfNotDonePlaying, float playbackTime, float playbackDuration)
{
while (source.time == 0 && source.isPlaying)
{
yield return null;
}
float initialWait = (playbackDuration > 0) ? playbackDuration : duration;
yield return MMCoroutine.WaitForUnscaled(initialWait);
if (source.clip != clip)
{
yield break;
}
if (doNotAutoRecycleIfNotDonePlaying)
{
float maxTime = (playbackDuration > 0) ? playbackTime + playbackDuration : source.clip.length;
while ((source.time != 0) && (source.time <= maxTime))
{
yield return null;
}
}
source.gameObject.SetActive(false);
}
/// <summary>
/// Pulls an available audio source from the pool
/// </summary>
/// <param name="poolCanExpand"></param>
/// <param name="parent"></param>
/// <returns></returns>
public virtual AudioSource GetAvailableAudioSource(bool poolCanExpand, Transform parent)
{
foreach (AudioSource source in _pool)
{
if (!source.gameObject.activeInHierarchy)
{
source.gameObject.SetActive(true);
return source;
}
}
if (poolCanExpand)
{
GameObject temporaryAudioHost = new GameObject("MMAudioSourcePool_"+_pool.Count);
SceneManager.MoveGameObjectToScene(temporaryAudioHost.gameObject, parent.gameObject.scene);
AudioSource tempSource = temporaryAudioHost.AddComponent<AudioSource>();
temporaryAudioHost.transform.SetParent(parent);
temporaryAudioHost.SetActive(true);
_pool.Add(tempSource);
return tempSource;
}
return null;
}
/// <summary>
/// Stops an audiosource and returns it to the pool
/// </summary>
/// <param name="sourceToStop"></param>
/// <returns></returns>
public virtual bool FreeSound(AudioSource sourceToStop)
{
foreach (AudioSource source in _pool)
{
if (source == sourceToStop)
{
source.Stop();
source.gameObject.SetActive(false);
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6ca15a4a57ce0a947835411d7c771538
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,186 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
namespace MoreMountains.Tools
{
/// <summary>
/// A class used to store options for MMSoundManager play
/// </summary>
[Serializable]
public struct MMSoundManagerPlayOptions
{
[HideInInspector]
public bool Initialized;
[Header("Track")]
/// the track on which to play the sound
public MMSoundManager.MMSoundManagerTracks MmSoundManagerTrack;
/// an audiogroup to use if you don't want to play on any of the preset tracks
public AudioMixerGroup AudioGroup;
[Header("Sound")]
/// whether or not the sound should loop
public bool Loop;
/// the volume at which to play the sound
[Range(0f,2f)]
public float Volume;
/// The pitch of the audio source.
[Range(-3f,3f)]
public float Pitch;
/// the ID of the sound, useful to find that sound again later
public int ID;
[Header("Fade")]
/// whether or not to fade the sound when playing it
public bool Fade;
/// the initial volume of the sound, before the fade
[MMCondition("Fade", true)]
public float FadeInitialVolume;
/// the duration of the fade, in seconds
[MMCondition("Fade", true)]
public float FadeDuration;
/// the tween to use when fading the sound
[MMCondition("Fade", true)]
public MMTweenType FadeTween;
/// whether or not the sound should persist over scene transitions
public bool Persistent;
/// an AudioSource to use if you don't want to pick one from the pool
public AudioSource RecycleAudioSource;
[Header("Time")]
/// The time (in seconds) at which to start playing the sound
public float PlaybackTime;
/// The time (in seconds after which to stop playing the sound
public float PlaybackDuration;
[Header("Spatial Settings")]
/// Pans a playing sound in a stereo way (left or right). This only applies to sounds that are Mono or Stereo.
[Range(-1f,1f)]
public float PanStereo;
/// Sets how much this AudioSource is affected by 3D spatialisation calculations (attenuation, doppler etc). 0.0 makes the sound full 2D, 1.0 makes it full 3D.
[Range(0f,1f)]
public float SpatialBlend;
/// a Transform this sound can 'attach' to and follow it along as it plays
public Transform AttachToTransform;
[Header("Solo")]
/// whether or not this sound should play in solo mode over its destination track. If yes, all other sounds on that track will be muted when this sound starts playing
public bool SoloSingleTrack;
/// whether or not this sound should play in solo mode over all other tracks. If yes, all other tracks will be muted when this sound starts playing
public bool SoloAllTracks;
/// if in any of the solo modes, AutoUnSoloOnEnd will unmute the track(s) automatically once that sound stops playing
public bool AutoUnSoloOnEnd;
/// Bypass effects (Applied from filter components or global listener filters).
public bool BypassEffects;
/// When set global effects on the AudioListener will not be applied to the audio signal generated by the AudioSource. Does not apply if the AudioSource is playing into a mixer group.
public bool BypassListenerEffects;
/// When set doesn't route the signal from an AudioSource into the global reverb associated with reverb zones.
public bool BypassReverbZones;
/// Sets the priority of the AudioSource.
[Range(0, 256)]
public int Priority;
/// The amount by which the signal from the AudioSource will be mixed into the global reverb associated with the Reverb Zones.
[Range(0f,1.1f)]
public float ReverbZoneMix;
[Header("3D Sound Settings")]
/// Sets the Doppler scale for this AudioSource.
[Range(0f,5f)]
public float DopplerLevel;
/// the location at which to position the sound
public Vector3 Location;
/// Sets the spread angle (in degrees) of a 3d stereo or multichannel sound in speaker space.
[Range(0,360)]
public int Spread;
/// Sets/Gets how the AudioSource attenuates over distance.
public AudioRolloffMode RolloffMode;
/// Within the Min distance the AudioSource will cease to grow louder in volume.
public float MinDistance;
/// (Logarithmic rolloff) MaxDistance is the distance a sound stops attenuating at.
public float MaxDistance;
/// Whether or not the source should be auto recycled if not done playing
public bool DoNotAutoRecycleIfNotDonePlaying;
/// whether or not to use a custom curve for custom volume rolloff
public bool UseCustomRolloffCurve;
/// the curve to use for custom volume rolloff if UseCustomRolloffCurve is true
[MMCondition("UseCustomRolloffCurve", true)]
public AnimationCurve CustomRolloffCurve;
/// whether or not to use a custom curve for spatial blend
public bool UseSpatialBlendCurve;
/// the curve to use for custom spatial blend if UseSpatialBlendCurve is true
[MMCondition("UseSpatialBlendCurve", true)]
public AnimationCurve SpatialBlendCurve;
/// whether or not to use a custom curve for reverb zone mix
public bool UseReverbZoneMixCurve;
/// the curve to use for custom reverb zone mix if UseReverbZoneMixCurve is true
[MMCondition("UseReverbZoneMixCurve", true)]
public AnimationCurve ReverbZoneMixCurve;
/// whether or not to use a custom curve for spread
public bool UseSpreadCurve;
/// the curve to use for custom spread if UseSpreadCurve is true
[MMCondition("UseSpreadCurve", true)]
public AnimationCurve SpreadCurve;
/// <summary>
/// A default set of options, meant to suit most common cases.
/// When using options, it's a good idea to start with that and override only what you need to.
///
/// Example :
///
/// MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
/// options.Loop = Loop;
/// options.Location = Vector3.zero;
/// options.MmSoundManagerTrack = MMSoundManager.MMSoundManagerTracks.Music;
///
/// MMSoundManagerSoundPlayEvent.Trigger(SoundClip, options);
///
/// Here we initialize a new local options set, override its loop, location and track settings, and call a play event using it
///
/// </summary>
public static MMSoundManagerPlayOptions Default
{
get
{
MMSoundManagerPlayOptions defaultOptions = new MMSoundManagerPlayOptions();
defaultOptions.Initialized = true;
defaultOptions.MmSoundManagerTrack = MMSoundManager.MMSoundManagerTracks.Sfx;
defaultOptions.Location = Vector3.zero;
defaultOptions.Loop = false;
defaultOptions.Volume = 1.0f;
defaultOptions.ID = 0;
defaultOptions.Fade = false;
defaultOptions.FadeInitialVolume = 0f;
defaultOptions.FadeDuration = 1f;
defaultOptions.FadeTween = MMTweenType.DefaultEaseInCubic;
defaultOptions.Persistent = false;
defaultOptions.RecycleAudioSource = null;
defaultOptions.AudioGroup = null;
defaultOptions.Pitch = 1f;
defaultOptions.PanStereo = 0f;
defaultOptions.SpatialBlend = 0.0f;
defaultOptions.SoloSingleTrack = false;
defaultOptions.SoloAllTracks = false;
defaultOptions.AutoUnSoloOnEnd = false;
defaultOptions.BypassEffects = false;
defaultOptions.BypassListenerEffects = false;
defaultOptions.BypassReverbZones = false;
defaultOptions.Priority = 128;
defaultOptions.ReverbZoneMix = 1f;
defaultOptions.DopplerLevel = 1f;
defaultOptions.Spread = 0;
defaultOptions.RolloffMode = AudioRolloffMode.Logarithmic;
defaultOptions.MinDistance = 1f;
defaultOptions.MaxDistance = 500f;
defaultOptions.DoNotAutoRecycleIfNotDonePlaying = true;
return defaultOptions;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 123f46c3aae9cbd4bba38e5dcf9cecae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections;
using System.Collections.Generic;
using MoreMountains.Tools;
using UnityEngine;
namespace MoreMountains.Tools
{
/// <summary>
/// This class stores MMSoundManager settings and lets you tweak them from the MMSoundManagerSettingsSO's inspector
/// </summary>
[Serializable]
public class MMSoundManagerSettings
{
public const float _minimalVolume = 0.0001f;
public const float _maxVolume = 10f;
public const float _defaultVolume = 1f;
[Header("Audio Mixer Control")]
/// whether or not the settings described below should override the ones defined in the AudioMixer
[Tooltip("whether or not the settings described below should override the ones defined in the AudioMixer")]
public bool OverrideMixerSettings = true;
[Header("Audio Mixer Exposed Parameters")]
/// the name of the exposed MasterVolume parameter in the AudioMixer
[Tooltip("the name of the exposed MasterVolume parameter in the AudioMixer")]
public string MasterVolumeParameter = "MasterVolume";
/// the name of the exposed MusicVolume parameter in the AudioMixer
[Tooltip("the name of the exposed MusicVolume parameter in the AudioMixer")]
public string MusicVolumeParameter = "MusicVolume";
/// the name of the exposed SfxVolume parameter in the AudioMixer
[Tooltip("the name of the exposed SfxVolume parameter in the AudioMixer")]
public string SfxVolumeParameter = "SfxVolume";
/// the name of the exposed UIVolume parameter in the AudioMixer
[Tooltip("the name of the exposed UIVolume parameter in the AudioMixer")]
public string UIVolumeParameter = "UIVolume";
[Header("Master")]
/// the master volume
[Range(_minimalVolume,_maxVolume)]
[Tooltip("the master volume")]
[MMReadOnly]
public float MasterVolume = _defaultVolume;
/// whether the master track is active at the moment or not
[Tooltip("whether the master track is active at the moment or not")]
[MMReadOnly]
public bool MasterOn = true;
/// the volume of the master track before it was muted
[Tooltip("the volume of the master track before it was muted")]
[MMReadOnly]
public float MutedMasterVolume;
[Header("Music")]
/// the music volume
[Range(_minimalVolume,_maxVolume)]
[Tooltip("the music volume")]
[MMReadOnly]
public float MusicVolume = _defaultVolume;
/// whether the music track is active at the moment or not
[Tooltip("whether the music track is active at the moment or not")]
[MMReadOnly]
public bool MusicOn = true;
/// the volume of the music track before it was muted
[Tooltip("the volume of the music track before it was muted")]
[MMReadOnly]
public float MutedMusicVolume;
[Header("Sound Effects")]
/// the sound fx volume
[Range(_minimalVolume,_maxVolume)]
[Tooltip("the sound fx volume")]
[MMReadOnly]
public float SfxVolume = _defaultVolume;
/// whether the SFX track is active at the moment or not
[Tooltip("whether the SFX track is active at the moment or not")]
[MMReadOnly]
public bool SfxOn = true;
/// the volume of the SFX track before it was muted
[Tooltip("the volume of the SFX track before it was muted")]
[MMReadOnly]
public float MutedSfxVolume;
[Header("UI")]
/// the UI sounds volume
[Range(_minimalVolume,_maxVolume)]
[Tooltip("the UI sounds volume")]
[MMReadOnly]
public float UIVolume = _defaultVolume;
/// whether the UI track is active at the moment or not
[Tooltip("whether the UI track is active at the moment or not")]
[MMReadOnly]
public bool UIOn = true;
/// the volume of the UI track before it was muted
[Tooltip("the volume of the UI track before it was muted")]
[MMReadOnly]
public float MutedUIVolume;
[Header("Save & Load")]
/// whether or not the MMSoundManager should automatically load settings when starting
[Tooltip("whether or not the MMSoundManager should automatically load settings when starting")]
public bool AutoLoad = true;
/// whether or not each change in the settings should be automaticall saved. If not, you'll have to call a save MMSoundManager event for settings to be saved.
[Tooltip("whether or not each change in the settings should be automaticall saved. If not, you'll have to call a save MMSoundManager event for settings to be saved.")]
public bool AutoSave = false;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 504ec383a394760468beec53f890add9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,210 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Audio;
namespace MoreMountains.Tools
{
/// <summary>
/// A class to save sound settings (music on or off, sfx on or off)
/// </summary>
[Serializable]
[CreateAssetMenu(menuName = "MoreMountains/Audio/MMSoundManagerSettings")]
public class MMSoundManagerSettingsSO : ScriptableObject
{
[Header("Audio Mixer")]
/// the audio mixer to use when playing sounds
[Tooltip("the audio mixer to use when playing sounds")]
public AudioMixer TargetAudioMixer;
/// the master group
[Tooltip("the master group")]
public AudioMixerGroup MasterAudioMixerGroup;
/// the group on which to play all music sounds
[Tooltip("the group on which to play all music sounds")]
public AudioMixerGroup MusicAudioMixerGroup;
/// the group on which to play all sound effects
[Tooltip("the group on which to play all sound effects")]
public AudioMixerGroup SfxAudioMixerGroup;
/// the group on which to play all UI sounds
[Tooltip("the group on which to play all UI sounds")]
public AudioMixerGroup UIAudioMixerGroup;
/// the multiplier to apply when converting normalized volume values to audio mixer values
[Tooltip("the multiplier to apply when converting normalized volume values to audio mixer values")]
public float MixerValuesMultiplier = 20;
[Header("Settings Unfold")]
/// the full settings for this MMSoundManager
[Tooltip("the full settings for this MMSoundManager")]
public MMSoundManagerSettings Settings;
protected const string _saveFolderName = "MMSoundManager/";
protected const string _saveFileName = "mmsound.settings";
#region SaveAndLoad
/// <summary>
/// Saves the sound settings to file
/// </summary>
public virtual void SaveSoundSettings()
{
MMSaveLoadManager.Save(this.Settings, _saveFileName, _saveFolderName);
}
/// <summary>
/// Loads the sound settings from file (if found)
/// </summary>
public virtual void LoadSoundSettings()
{
if (Settings.OverrideMixerSettings)
{
MMSoundManagerSettings settings =
(MMSoundManagerSettings) MMSaveLoadManager.Load(typeof(MMSoundManagerSettings), _saveFileName,
_saveFolderName);
if (settings != null)
{
this.Settings = settings;
ApplyTrackVolumes();
}
MMSoundManagerEvent.Trigger(MMSoundManagerEventTypes.SettingsLoaded);
}
}
/// <summary>
/// Resets the sound settings by destroying the save file
/// </summary>
public virtual void ResetSoundSettings()
{
MMSaveLoadManager.DeleteSave(_saveFileName, _saveFolderName);
}
#endregion
#region Volume
/// <summary>
/// sets the volume of the selected track to the value passed in parameters
/// </summary>
/// <param name="track"></param>
/// <param name="volume"></param>
public virtual void SetTrackVolume(MMSoundManager.MMSoundManagerTracks track, float volume)
{
if (volume <= 0f)
{
volume = MMSoundManagerSettings._minimalVolume;
}
switch (track)
{
case MMSoundManager.MMSoundManagerTracks.Master:
TargetAudioMixer.SetFloat(Settings.MasterVolumeParameter, NormalizedToMixerVolume(volume));
Settings.MasterVolume = volume;
break;
case MMSoundManager.MMSoundManagerTracks.Music:
TargetAudioMixer.SetFloat(Settings.MusicVolumeParameter, NormalizedToMixerVolume(volume));
Settings.MusicVolume = volume;
break;
case MMSoundManager.MMSoundManagerTracks.Sfx:
TargetAudioMixer.SetFloat(Settings.SfxVolumeParameter, NormalizedToMixerVolume(volume));
Settings.SfxVolume = volume;
break;
case MMSoundManager.MMSoundManagerTracks.UI:
TargetAudioMixer.SetFloat(Settings.UIVolumeParameter, NormalizedToMixerVolume(volume));
Settings.UIVolume = volume;
break;
}
if (Settings.AutoSave)
{
SaveSoundSettings();
}
}
/// <summary>
/// Returns the volume of the specified track
/// </summary>
/// <param name="track"></param>
/// <returns></returns>
public virtual float GetTrackVolume(MMSoundManager.MMSoundManagerTracks track)
{
float volume = 1f;
switch (track)
{
case MMSoundManager.MMSoundManagerTracks.Master:
TargetAudioMixer.GetFloat(Settings.MasterVolumeParameter, out volume);
break;
case MMSoundManager.MMSoundManagerTracks.Music:
TargetAudioMixer.GetFloat(Settings.MusicVolumeParameter, out volume);
break;
case MMSoundManager.MMSoundManagerTracks.Sfx:
TargetAudioMixer.GetFloat(Settings.SfxVolumeParameter, out volume);
break;
case MMSoundManager.MMSoundManagerTracks.UI:
TargetAudioMixer.GetFloat(Settings.UIVolumeParameter, out volume);
break;
}
return MixerVolumeToNormalized(volume);
}
/// <summary>
/// assigns the volume of each track to the settings values
/// </summary>
public virtual void GetTrackVolumes()
{
Settings.MasterVolume = GetTrackVolume(MMSoundManager.MMSoundManagerTracks.Master);
Settings.MusicVolume = GetTrackVolume(MMSoundManager.MMSoundManagerTracks.Music);
Settings.SfxVolume = GetTrackVolume(MMSoundManager.MMSoundManagerTracks.Sfx);
Settings.UIVolume = GetTrackVolume(MMSoundManager.MMSoundManagerTracks.UI);
}
/// <summary>
/// applies volume to all tracks and saves if needed
/// </summary>
protected virtual void ApplyTrackVolumes()
{
if (Settings.OverrideMixerSettings)
{
TargetAudioMixer.SetFloat(Settings.MasterVolumeParameter, NormalizedToMixerVolume(Settings.MasterVolume));
TargetAudioMixer.SetFloat(Settings.MusicVolumeParameter, NormalizedToMixerVolume(Settings.MusicVolume));
TargetAudioMixer.SetFloat(Settings.SfxVolumeParameter, NormalizedToMixerVolume(Settings.SfxVolume));
TargetAudioMixer.SetFloat(Settings.UIVolumeParameter, NormalizedToMixerVolume(Settings.UIVolume));
if (!Settings.MasterOn) { TargetAudioMixer.SetFloat(Settings.MasterVolumeParameter, -80f); }
if (!Settings.MusicOn) { TargetAudioMixer.SetFloat(Settings.MusicVolumeParameter, -80f); }
if (!Settings.SfxOn) { TargetAudioMixer.SetFloat(Settings.SfxVolumeParameter, -80f); }
if (!Settings.UIOn) { TargetAudioMixer.SetFloat(Settings.UIVolumeParameter, -80f); }
if (Settings.AutoSave)
{
SaveSoundSettings();
}
}
}
/// <summary>
/// Converts a normalized volume to the mixer group db scale
/// </summary>
/// <param name="normalizedVolume"></param>
/// <returns></returns>
public virtual float NormalizedToMixerVolume(float normalizedVolume)
{
return Mathf.Log10(normalizedVolume) * MixerValuesMultiplier;
}
/// <summary>
/// Converts mixer volume to a normalized value
/// </summary>
/// <param name="mixerVolume"></param>
/// <returns></returns>
public virtual float MixerVolumeToNormalized(float mixerVolume)
{
return (float)Math.Pow(10, (mixerVolume / MixerValuesMultiplier));
}
#endregion Volume
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aa48f53704ecf834c9f5e203a06874d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MoreMountains.Tools
{
/// <summary>
/// A simple struct used to store information about the sounds played by the MMSoundManager
/// </summary>
[Serializable]
public struct MMSoundManagerSound
{
/// the ID of the sound
public int ID;
/// the track the sound is being played on
public MMSoundManager.MMSoundManagerTracks Track;
/// the associated audiosource
public AudioSource Source;
/// whether or not this sound will play over multiple scenes
public bool Persistent;
public float PlaybackTime;
public float PlaybackDuration;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f351cd555ef38d4d8b69d8be107ce7f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,195 @@
using System;
using UnityEngine;
using UnityEngine.UI;
namespace MoreMountains.Tools
{
/// <summary>
/// You can add this class to a slider in your UI and it'll let you control a target Track volume
/// via the MMSoundManager
/// </summary>
public class MMSoundManagerTrackVolumeSlider : MonoBehaviour,
MMEventListener<MMSoundManagerEvent>,
MMEventListener<MMSoundManagerTrackEvent>,
MMEventListener<MMSoundManagerTrackFadeEvent>
{
/// <summary>
/// The possible modes this slider can be in
/// - Read : the slider will move to reflect the volume of the track
/// - Write : the value of the slider will be applied to the volume of the track
/// This slider can also listen for events (mute, unmute, fade, volume change) and automatically switch to read mode
/// if one is caught. This means that most of the time, the slider is in write mode, and switches to read mode only
/// when needed, to be always accurate
/// </summary>
public enum Modes { Read, Write }
[Header("Track Volume Settings")]
/// The track to change volume on
[Tooltip("The track to change volume on")]
public MMSoundManager.MMSoundManagerTracks Track;
/// The volume to apply to the track when the slider is at its minimum
[Tooltip("The volume to apply to the track when the slider is at its minimum")]
public float MinVolume = 0f;
/// The volume to apply to the track when the slider is at its maximum
[Tooltip("The volume to apply to the track when the slider is at its maximum")]
public float MaxVolume = 1f;
[Header("Read/Write Mode")]
/// in read mode, the value of the slider will be applied to the volume of the track. in read mode, the slider will move to reflect the volume of the track
[Tooltip("in read mode, the value of the slider will be applied to the volume of the track. in read mode, the slider will move to reflect the volume of the track")]
public Modes Mode = Modes.Write;
/// if this is true, the slider will automatically switch to read mode for the required duration when a track fade event is caught
[Tooltip("if this is true, the slider will automatically switch to read mode for the required duration when a track fade event is caught")]
public bool ChangeModeOnTrackFade = true;
/// if this is true, the slider will automatically switch to read mode for the required duration when a track mute event is caught
[Tooltip("if this is true, the slider will automatically switch to read mode for the required duration when a track mute event is caught")]
public bool ChangeModeOnMute = true;
/// if this is true, the slider will automatically switch to read mode for the required duration when a track unmute event is caught
[Tooltip("if this is true, the slider will automatically switch to read mode for the required duration when a track unmute event is caught")]
public bool ChangeModeOnUnmute = true;
/// if this is true, the slider will automatically switch to read mode for the required duration when a track volume change event is caught
[Tooltip("if this is true, the slider will automatically switch to read mode for the required duration when a track volume change event is caught")]
public bool ChangeModeOnTrackVolumeChange = false;
/// when switching automatically (and temporarily) to Read Mode, the minimum duration the slider will remain in that mode
[Tooltip("when switching automatically (and temporarily) to Read Mode, the minimum duration the slider will remain in that mode")]
public float ModeSwitchBufferTime = 0.1f;
protected Slider _slider;
protected Modes _resetToMode;
protected bool _resetNeeded = false;
protected float _resetTimestamp;
/// <summary>
/// On awake we cache our slider
/// </summary>
protected virtual void Awake()
{
_slider = this.gameObject.GetComponent<Slider>();
}
/// <summary>
/// On late update, we update our slider's value if in read mode, and reset our mode if needed
/// </summary>
protected virtual void LateUpdate()
{
if (Mode == Modes.Read)
{
float trackVolume = MMSoundManager.Instance.GetTrackVolume(Track, false);
_slider.value = trackVolume;
}
if (_resetNeeded && (Time.unscaledTime >= _resetTimestamp))
{
Mode = _resetToMode;
_resetNeeded = false;
}
}
/// <summary>
/// A public method you can use to switch to read mode for a limited time, resetting to write after that
/// </summary>
/// <param name="duration"></param>
public virtual void ChangeModeToRead(float duration)
{
_resetToMode = Modes.Write;
Mode = Modes.Read;
_resetTimestamp = Time.unscaledTime + duration;
_resetNeeded = true;
}
/// <summary>
/// Bind your slider to this method
/// </summary>
public virtual void UpdateVolume(float newValue)
{
if (Mode == Modes.Read)
{
return;
}
float newVolume = MMMaths.Remap(newValue, 0f, 1f, MinVolume, MaxVolume);
MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.SetVolumeTrack, Track, newVolume);
}
/// <summary>
/// When we get an event letting us know the settings have been loaded, we update our slider to reflect the current track volume
/// </summary>
/// <param name="soundManagerEvent"></param>
public void OnMMEvent(MMSoundManagerEvent soundManagerEvent)
{
if (soundManagerEvent.EventType == MMSoundManagerEventTypes.SettingsLoaded)
{
UpdateSliderValueWithTrackVolume();
}
}
/// <summary>
/// Updates the slider value to reflect the current track volume
/// </summary>
public virtual void UpdateSliderValueWithTrackVolume()
{
_slider.value = MMMaths.Remap(MMSoundManager.Instance.GetTrackVolume(Track, false), 0f, 1f, MinVolume, MaxVolume);
}
/// <summary>
/// if we grab a track event, we switch to read mode if needed
/// </summary>
/// <param name="trackEvent"></param>
public void OnMMEvent(MMSoundManagerTrackEvent trackEvent)
{
switch (trackEvent.TrackEventType)
{
case MMSoundManagerTrackEventTypes.MuteTrack:
if (ChangeModeOnMute)
{
ChangeModeToRead(ModeSwitchBufferTime);
}
break;
case MMSoundManagerTrackEventTypes.UnmuteTrack:
if (ChangeModeOnUnmute)
{
ChangeModeToRead(ModeSwitchBufferTime);
}
break;
case MMSoundManagerTrackEventTypes.SetVolumeTrack:
if (ChangeModeOnTrackVolumeChange)
{
ChangeModeToRead(ModeSwitchBufferTime);
}
break;
}
}
/// <summary>
/// if we grab a track fade event, we switch to read mode if needed
/// </summary>
/// <param name="fadeEvent"></param>
public void OnMMEvent(MMSoundManagerTrackFadeEvent fadeEvent)
{
if (ChangeModeOnTrackFade)
{
ChangeModeToRead(fadeEvent.FadeDuration + ModeSwitchBufferTime);
}
}
/// <summary>
/// On enable we start listening for events
/// </summary>
protected virtual void OnEnable()
{
this.MMEventStartListening<MMSoundManagerEvent>();
this.MMEventStartListening<MMSoundManagerTrackEvent>();
this.MMEventStartListening<MMSoundManagerTrackFadeEvent>();
}
/// <summary>
/// On disable we stop listening for events
/// </summary>
protected virtual void OnDisable()
{
this.MMEventStopListening<MMSoundManagerEvent>();
this.MMEventStopListening<MMSoundManagerTrackEvent>();
this.MMEventStopListening<MMSoundManagerTrackFadeEvent>();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 487bb7f14f728c74ca3bfc0020509b6b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2fafbd11d20ac5e4c8a268445549fe64
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,185 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!244 &-4576635800020724519
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_EffectID: 0035689472bad0c428c533b0be360a9c
m_EffectName: Attenuation
m_MixLevel: 0523e55fe65fca847af0ff87fc3e2f3e
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!243 &-3783893103257190392
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: UI
m_AudioMixer: {fileID: 24100000}
m_GroupID: 39aa7f1a81fc93045baa62f351379f1b
m_Children: []
m_Volume: 6b54e6bee2cbfc5408b1e9a970814b35
m_Pitch: 78268b4570bb375429c64ce14ed21608
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: 5731139238567193833}
m_UserColorIndex: 6
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!243 &-3165942800488051908
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Sfx
m_AudioMixer: {fileID: 24100000}
m_GroupID: 8d8d3def649fd3b49ba6ebeb6b0f1029
m_Children: []
m_Volume: 534e52a4a366075458077c0854e78569
m_Pitch: 502a12b576dde844491815d64d1c4531
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: -4576635800020724519}
m_UserColorIndex: 3
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!243 &-2273146209696275378
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Music
m_AudioMixer: {fileID: 24100000}
m_GroupID: 5c8f408e844ac0647b599b8ec8c96f5c
m_Children: []
m_Volume: c2e19a843805c3a4ca00e7acf8b2ba84
m_Pitch: a80843d39fe3901479d44fa0b40f4d60
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: 8563443583662627042}
m_UserColorIndex: 1
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!241 &24100000
AudioMixerController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: MMSoundManagerAudioMixer
m_OutputGroup: {fileID: 0}
m_MasterGroup: {fileID: 24300002}
m_Snapshots:
- {fileID: 24500006}
m_StartSnapshot: {fileID: 24500006}
m_SuspendThreshold: -80
m_EnableSuspend: 1
m_UpdateMode: 0
m_ExposedParameters:
- guid: 0f44e1c042b36e646938a885f8d57ea3
name: MasterVolume
- guid: c2e19a843805c3a4ca00e7acf8b2ba84
name: MusicVolume
- guid: 534e52a4a366075458077c0854e78569
name: SfxVolume
- guid: 6b54e6bee2cbfc5408b1e9a970814b35
name: UiVolume
m_AudioMixerGroupViews:
- guids:
- 2c09fe8489093d84eb269d15e44106a0
- 5c8f408e844ac0647b599b8ec8c96f5c
- 8d8d3def649fd3b49ba6ebeb6b0f1029
- 39aa7f1a81fc93045baa62f351379f1b
name: View
m_CurrentViewIndex: 0
m_TargetSnapshot: {fileID: 24500006}
--- !u!243 &24300002
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Master
m_AudioMixer: {fileID: 24100000}
m_GroupID: 2c09fe8489093d84eb269d15e44106a0
m_Children:
- {fileID: -2273146209696275378}
- {fileID: -3165942800488051908}
- {fileID: -3783893103257190392}
m_Volume: 0f44e1c042b36e646938a885f8d57ea3
m_Pitch: f75e977dcda787e4283bdfd2e0fbd835
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: 24400004}
m_UserColorIndex: 8
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!244 &24400004
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_EffectID: 7dc96f4113679f544a75c528d0f85b47
m_EffectName: Attenuation
m_MixLevel: f11780d0b2aaa304183d250aaf941743
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!245 &24500006
AudioMixerSnapshotController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Snapshot
m_AudioMixer: {fileID: 24100000}
m_SnapshotID: e34bbd4b8f6f0a34491e4d36d702e6f6
m_FloatValues:
0f44e1c042b36e646938a885f8d57ea3: 0
c2e19a843805c3a4ca00e7acf8b2ba84: 0
534e52a4a366075458077c0854e78569: 0
6b54e6bee2cbfc5408b1e9a970814b35: 0
m_TransitionOverrides: {}
--- !u!244 &5731139238567193833
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_EffectID: f022f94adadb655468a67368350bbdbb
m_EffectName: Attenuation
m_MixLevel: 40e95a1bf00f08941905242957d20f00
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!244 &8563443583662627042
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_EffectID: fe11159c9acdb3d43b19af8fbdc26ff9
m_EffectName: Attenuation
m_MixLevel: cb4222fa13b8c2c439912f02c6159aaa
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7e984f512e89b60468e829489e8883e3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 24100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: aa48f53704ecf834c9f5e203a06874d3, type: 3}
m_Name: MMSoundManagerSettings
m_EditorClassIdentifier:
TargetAudioMixer: {fileID: 24100000, guid: 7e984f512e89b60468e829489e8883e3, type: 2}
MasterAudioMixerGroup: {fileID: 24300002, guid: 7e984f512e89b60468e829489e8883e3,
type: 2}
MusicAudioMixerGroup: {fileID: -2273146209696275378, guid: 7e984f512e89b60468e829489e8883e3,
type: 2}
SfxAudioMixerGroup: {fileID: -3165942800488051908, guid: 7e984f512e89b60468e829489e8883e3,
type: 2}
UIAudioMixerGroup: {fileID: -3783893103257190392, guid: 7e984f512e89b60468e829489e8883e3,
type: 2}
Settings:
OverrideMixerSettings: 1
MasterVolumeParameter: MasterVolume
MusicVolumeParameter: MusicVolume
SfxVolumeParameter: SfxVolume
UIVolumeParameter: UiVolume
MasterVolume: 1
MasterOn: 1
MutedMasterVolume: 0
MusicVolume: 0.2
MusicOn: 1
MutedMusicVolume: 0
SfxVolume: 1
SfxOn: 1
MutedSfxVolume: 0
UIVolume: 1
UIOn: 1
MutedUIVolume: 0
AutoLoad: 1
AutoSave: 0

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 07ea3ff88e1ecb84d91f58aa804badc6
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant: