unity 2D游戏开发教程(四):音频管理工具

1.音频管理工具

1.1简单使用

双击

1.2 实践案例

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.SceneManagement;
using static MoreMountains.Tools.MMSoundManager;

namespace MoreMountains.Tools
{
    public class TestSoundManager : MonoBehaviour
    {
        [Header("MMSoundManagerSoundPlayEvent")]
        public AudioClip SoundClipLoop;
        public MMSoundManager.MMSoundManagerTracks UseTracks;
        public AudioMixerGroup OtherAudioGroup;

        [MMInspectorButton("TestSoundManagerSoundPlayEvent")]
        public bool TestSoundManagerSoundPlayEventButton;
        protected virtual void TestSoundManagerSoundPlayEvent()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = true;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = UseTracks;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClipLoop, options);
        }

        [MMInspectorButton("TestSoundManagerSoundPlayEventSound1")]
        public bool TestSoundManagerSoundPlayEventSound1Button;
        protected virtual void TestSoundManagerSoundPlayEventSound1()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = true;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = UseTracks;
            options.ID = 1;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClipLoop, options);
        }

        [MMInspectorButton("TestSoundManagerSoundPlayPersistent")]
        public bool TestSoundManagerSoundPlayPersistentButton;
        protected virtual void TestSoundManagerSoundPlayPersistent()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = true;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = UseTracks;
            options.Persistent = true;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClipLoop, options);
        }


        [MMInspectorButton("TestUseOtherAudioGrup")]
        public bool TestUseOtherAudioGrupButton;
        protected virtual void TestUseOtherAudioGrup()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = true;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = MMSoundManager.MMSoundManagerTracks.Music;
            options.AudioGroup = OtherAudioGroup;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClipLoop, options);
        }

        [MMInspectorButton("TestSoundManagerSoundPlayNoloop")]
        public bool TestSoundManagerSoundPlayNoloopButton;
        protected virtual void TestSoundManagerSoundPlayNoloop()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = true;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = UseTracks;
            options.Loop = false;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClipLoop, options);
        }


        [MMInspectorButton("TestFaderSound")]
        public bool TestFaderSoundButton;
        protected virtual void TestFaderSound()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = true;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = UseTracks;
            options.Fade = true;
            options.FadeInitialVolume = 1;
            options.Volume = 0;
            options.FadeDuration = 10;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClipLoop, options);

        }

        public AudioClip SoundClipNoLoop;
        [MMInspectorButton("TestSoundManagerNoLoop")]
        public bool TestSoundManagerNoLoopButton;

        protected virtual void TestSoundManagerNoLoop()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = false;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = MMSoundManager.MMSoundManagerTracks.Sfx;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClipNoLoop, options);
        }

        [MMInspectorButton("TestSoloSingleTrack")]
        public bool TestSoloSingleTrackButton;
        protected virtual void TestSoloSingleTrack()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = false;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = MMSoundManager.MMSoundManagerTracks.Music;
            options.SoloSingleTrack = true;
            options.AutoUnSoloOnEnd = true;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClipNoLoop, options);
        }

        [MMInspectorButton("TestSoloAllTracks")]
        public bool TestSoloAllTracksButton;
        protected virtual void TestSoloAllTracks()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = false;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = MMSoundManager.MMSoundManagerTracks.Sfx;
            options.SoloAllTracks = true;
            options.AutoUnSoloOnEnd = true;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClipNoLoop, options);
        }

        [Header("MMSoundManagerTrackEvent")]
        public MMSoundManager.MMSoundManagerTracks MMSoundManagerTrackEventTestTracks = MMSoundManagerTracks.Music;
        public float TestSetVolume = 1f;

        [MMInspectorButton("MuteTrack")]
        public bool MuteTrackButton;
        protected virtual void MuteTrack()
        {
            MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.MuteTrack, MMSoundManagerTracks.Music);
        }

        [MMInspectorButton("UnmuteTrack")]
        public bool UnmuteTrackButton;
        protected virtual void UnmuteTrack()
        {
            MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.UnmuteTrack, MMSoundManagerTracks.Music, 0);
        }

        [MMInspectorButton("SetVolumeTrack")]
        public bool SetVolumeTrackButton;
        protected virtual void SetVolumeTrack()
        {
            MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.SetVolumeTrack, MMSoundManagerTrackEventTestTracks, TestSetVolume);
        }


        [MMInspectorButton("PauseTrack")]
        public bool PauseTrackButton;
        protected virtual void PauseTrack()
        {
            MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.PauseTrack, MMSoundManagerTracks.Music, 0.5f);
        }

        [MMInspectorButton("StopTrack")]
        public bool StopTrackButton;
        protected virtual void StopTrack()
        {
            MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.StopTrack, MMSoundManagerTracks.Music, 0.5f);
        }

        [MMInspectorButton("PlayTrack")]
        public bool PlayTrackButton;
        protected virtual void PlayTrack()
        {
            MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.PlayTrack, MMSoundManagerTracks.Music, 0.5f);
        }

        [MMInspectorButton("FreeTrack")]
        public bool FreeTrackButton;
        protected virtual void FreeTrack()
        {
            MMSoundManagerTrackEvent.Trigger(MMSoundManagerTrackEventTypes.FreeTrack, MMSoundManagerTracks.Music, 0.5f);
        }

        [Header("MMSoundManagerEvent")]
        [MMInspectorButton("SaveSettings")]
        public bool SaveSettingsButton;
        protected virtual void SaveSettings()
        {
            MMSoundManagerEvent.Trigger(MMSoundManagerEventTypes.SaveSettings);
        }

        [MMInspectorButton("LoadSettings")]
        public bool LoadSettingsButton;
        protected virtual void LoadSettings()
        {
            MMSoundManagerEvent.Trigger(MMSoundManagerEventTypes.LoadSettings);
        }

        [MMInspectorButton("ResetSettings")]
        public bool ResetSettingsButton;
        protected virtual void ResetSettings()
        {
            MMSoundManagerEvent.Trigger(MMSoundManagerEventTypes.ResetSettings);
        }

        [Header("MMSoundManagerSoundControlEvent")]
        [MMInspectorButton("Pause")]
        public bool PauseButton;
        protected virtual void Pause()
        {
            MMSoundManagerSoundControlEvent.Trigger(MMSoundManagerSoundControlEventTypes.Pause, 1);
        }

        [MMInspectorButton("Resume")]
        public bool ResumeButton;
        protected virtual void Resume()
        {
            MMSoundManagerSoundControlEvent.Trigger(MMSoundManagerSoundControlEventTypes.Resume, 1);
        }

        [MMInspectorButton("Stop")]
        public bool StopButton;
        protected virtual void Stop()
        {
            MMSoundManagerSoundControlEvent.Trigger(MMSoundManagerSoundControlEventTypes.Stop, 1);
        }

        [MMInspectorButton("Free")]
        public bool FreeButton;
        protected virtual void Free()
        {
            MMSoundManagerSoundControlEvent.Trigger(MMSoundManagerSoundControlEventTypes.Free,1);
        }

        [Header("MMSoundManagerSoundFadeEvent")]
        public MMTweenType TestSoundFadeEventTweenType;
        [MMInspectorButton("TestSoundFadeEvent")]
        public bool TestSoundFadeEventButton;
        protected virtual void TestSoundFadeEvent()
        {
            MMSoundManagerSoundFadeEvent.Trigger(0, 10, 0, TestSoundFadeEventTweenType);
        }

        [Header("MMSoundManagerAllSoundsControlEvent")]
        [MMInspectorButton("AllSoundsPause")]
        public bool AllSoundsPauseButton;
        protected virtual void AllSoundsPause()
        {
            MMSoundManagerAllSoundsControlEvent.Trigger(MMSoundManagerAllSoundsControlEventTypes.Pause);
        }

        [MMInspectorButton("AllSoundsPlay")]
        public bool AllSoundsPlayButton;
        protected virtual void AllSoundsPlay()
        {
            MMSoundManagerAllSoundsControlEvent.Trigger(MMSoundManagerAllSoundsControlEventTypes.Play);
        }

        [MMInspectorButton("AllSoundsStop")]
        public bool AllSoundsStopButton;
        protected virtual void AllSoundsStop()
        {
            MMSoundManagerAllSoundsControlEvent.Trigger(MMSoundManagerAllSoundsControlEventTypes.Stop);
        }

        [MMInspectorButton("AllSoundsFree")]
        public bool AllSoundsFreeButton;
        protected virtual void AllSoundsFree()
        {
            MMSoundManagerAllSoundsControlEvent.Trigger(MMSoundManagerAllSoundsControlEventTypes.Free);
        }

        [MMInspectorButton("AllSoundsFreeAllButPersistent")]
        public bool AllSoundsFreeAllButPersistentButton;
        protected virtual void AllSoundsFreeAllButPersistent()
        {
            MMSoundManagerAllSoundsControlEvent.Trigger(MMSoundManagerAllSoundsControlEventTypes.FreeAllButPersistent);
        }

        [MMInspectorButton("AllSoundsFreeAllLooping")]
        public bool AllSoundsFreeAllLoopingButton;
        protected virtual void AllSoundsFreeAllLooping()
        {
            MMSoundManagerAllSoundsControlEvent.Trigger(MMSoundManagerAllSoundsControlEventTypes.FreeAllLooping);
        }


        [Header("MMSoundManagerTrackFadeEvent")]

        public MMSoundManager.MMSoundManagerTracks TrackFadeEventTracks = MMSoundManagerTracks.Music;
        public MMTweenType TrackFadeEventTweenType;

        [MMInspectorButton("TestTrackFadeEvent")]
        public bool TestTrackFadeEventButton;
        protected virtual void TestTrackFadeEvent()
        {
            MMSoundManagerTrackFadeEvent.Trigger(TrackFadeEventTracks, 10, 0, TrackFadeEventTweenType);
        }

        [Header("MMSfxEvent")]
        public AudioClip OnMMSfxEventClip;
        [MMInspectorButton("TestMMSfxEvent")]
        public bool TestMMSfxEventButton;
        protected virtual void TestMMSfxEvent()
        {
            MMSfxEvent.Trigger(OnMMSfxEventClip);
        }

        [Header("SceneChange")]
        public string NextSceneName;
        [MMInspectorButton("TestSceneChange")]
        public bool TestSceneChangeButton;
        protected virtual void TestSceneChange()
        {
            SceneManager.LoadScene(NextSceneName);
        }
        public void GoToNextLevel()
        {
            SceneManager.LoadScene(NextSceneName);
        }


    }

}

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.SceneManagement;

namespace MoreMountains.Tools
{
    /// <summary>
    /// 一个简单而强大的声音管理器,让您在播放声音时考虑到基于事件的方法和性能。
    /// 
    ///特点:
    /// 
    ///-播放/停止/暂停/恢复/自由声音
    ///-完全控制:环路、音量、音高、摇摄、空间混合、旁路、优先级、混响、多普勒电平、扩展、滚降模式、距离
    ///-2D和3D空间支持
    ///-内置池,自动回收一组音频源以获得最佳性能
    ///-内置混音器和组,带有现成的曲目(Master、Music、SFX、UI),并可根据需要在更多组上播放
    ///-停止/暂停/恢复/释放整个曲目
    ///-一次停止/暂停/恢复/释放所有声音
    ///-将整首曲目静音/设置音量
    ///-保存和加载设置,内置自动保存/自动加载机制
    ///-淡入/淡出声音
    ///-淡入/淡出轨道
    ///-独奏模式:播放一首或所有曲目静音的声音,然后自动取消静音
    ///-PlayOptions结构体
    ///-允许声音在场景加载和场景之间持续存在的选项
    ///-曲目检查器控件(音量、静音、取消静音、播放、暂停、停止、恢复、自由、声音数量)
    ///-MMSfx活动
    ///-MMSoundManagerEvents:静音音轨、控制音轨、保存、加载、重置、停止持续声音
    /// </summary>
    [AddComponentMenu("More Mountains/Tools/Audio/MMSoundManager")]
    public class MMSoundManager : MMPersistentSingleton<MMSoundManager>,
                                     MMEventListener<MMSoundManagerTrackEvent>,//音轨
                                    MMEventListener<MMSoundManagerEvent>,
                                    MMEventListener<MMSoundManagerSoundControlEvent>,//控制
                                    MMEventListener<MMSoundManagerSoundFadeEvent>,//淡入淡出
                                    MMEventListener<MMSoundManagerAllSoundsControlEvent>,//所有音乐管理
                                    MMEventListener<MMSoundManagerTrackFadeEvent>//音频轨道
    {
        /// 管理音轨的可能方法
        public enum MMSoundManagerTracks { Sfx, Music, UI, Master, Other }

        [Header("设置")]
        [Tooltip("当前声音设置")]
        public MMSoundManagerSettingsSO settingsSo;

        [Header("池")]
        [Tooltip("AudioSource池的大小,这是一个将被回收的现成资源的储备。应该大约等于您希望一次播放的最大声音量")]
        public int AudioSourcePoolSize = 10;
        [Tooltip("池是否可以扩展(按需创建新的音频源)。在一个完美的世界里,你应该避免这种情况,并拥有一个足够大的池,以避免昂贵的运行时创建")]
        public bool PoolCanExpand = true;

        protected MMSoundManagerAudioPool _pool;
        protected GameObject _tempAudioSourceGameObject;
        protected MMSoundManagerSound _sound;
        protected List<MMSoundManagerSound> _sounds;
        protected AudioSource _tempAudioSource;

        #region Initialization
        /// <summary>
        /// On Awake we initialize our manager
        /// </summary>
        protected override void Awake()
        {
            base.Awake();
            InitializeSoundManager();
        }

        /// <summary>
        /// On Start we load and apply our saved settings if needed.
        /// This is done on Start and not Awake because of a bug in Unity's AudioMixer API
        /// </summary>
        protected virtual void Start()
        {
            if ((settingsSo != null) && (settingsSo.Settings.AutoLoad))
            {
                settingsSo.LoadSoundSettings();
            }
        }

        /// <summary>
        /// Initializes the pool, fills it, registers to the scene loaded event
        /// </summary>
        protected virtual void InitializeSoundManager()
        {
            if (_pool == null)
            {
                _pool = new MMSoundManagerAudioPool();
            }
            _sounds = new List<MMSoundManagerSound>();
            _pool.FillAudioSourcePool(AudioSourcePoolSize, this.transform);
        }

        #endregion

        #region PlaySound
        /// <summary>
        /// Plays a sound, separate options object signature
        /// </summary>
        /// <param name="audioClip"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public virtual AudioSource PlaySound(AudioClip audioClip, MMSoundManagerPlayOptions options)
        {
            return PlaySound(audioClip, options.MmSoundManagerTrack, options.Location,
                            options.Loop, options.Volume, options.ID,
                            options.Fade, options.FadeInitialVolume, options.FadeDuration, options.FadeTween,
                            options.Persistent,
                            options.RecycleAudioSource, options.AudioGroup,
                            options.Pitch, options.PanStereo, options.SpatialBlend,
                            options.SoloSingleTrack, options.SoloAllTracks, options.AutoUnSoloOnEnd,
                            options.BypassEffects, options.BypassListenerEffects, options.BypassReverbZones, options.Priority,
                            options.ReverbZoneMix,
                            options.DopplerLevel, options.Spread, options.RolloffMode, options.MinDistance, options.MaxDistance
                        );
        }

        /// <summary>
        /// Plays a sound, signature with all options
        /// </summary>
        /// <param name="audioClip"></param>
        /// <param name="mmSoundManagerTrack"></param>
        /// <param name="location"></param>
        /// <param name="loop"></param>
        /// <param name="volume"></param>
        /// <param name="ID"></param>
        /// <param name="fade"></param>
        /// <param name="fadeInitialVolume"></param>
        /// <param name="fadeDuration"></param>
        /// <param name="fadeTween"></param>
        /// <param name="persistent"></param>
        /// <param name="recycleAudioSource"></param>
        /// <param name="audioGroup"></param>
        /// <param name="pitch"></param>
        /// <param name="panStereo"></param>
        /// <param name="spatialBlend"></param>
        /// <param name="soloSingleTrack"></param>
        /// <param name="soloAllTracks"></param>
        /// <param name="autoUnSoloOnEnd"></param>
        /// <param name="bypassEffects"></param>
        /// <param name="bypassListenerEffects"></param>
        /// <param name="bypassReverbZones"></param>
        /// <param name="priority"></param>
        /// <param name="reverbZoneMix"></param>
        /// <param name="dopplerLevel"></param>
        /// <param name="spread"></param>
        /// <param name="rolloffMode"></param>
        /// <param name="minDistance"></param>
        /// <param name="maxDistance"></param>
        /// <returns></returns>
        public virtual AudioSource PlaySound(AudioClip audioClip, 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
                                        )
        {
            if (!audioClip) { return null; }
            // audio source setup ---------------------------------------------------------------------------------
            // we reuse an audiosource if one is passed in parameters
            AudioSource audioSource = recycleAudioSource;
            if (audioSource == null)
            {
                // we pick an idle audio source from the pool if possible
                audioSource = _pool.GetAvailableAudioSource(PoolCanExpand, this.transform);
                if ((audioSource != null) && (!loop))
                {
                    recycleAudioSource = audioSource;
                    // we destroy the host after the clip has played (if it not tag for reusability.
                    StartCoroutine(_pool.AutoDisableAudioSource(audioClip.length / Mathf.Abs(pitch), audioSource, audioClip));
                }
            }
            // we create an audio source if needed
            if (audioSource == null)
            {
                _tempAudioSourceGameObject = new GameObject("MMAudio_" + audioClip.name);
                SceneManager.MoveGameObjectToScene(_tempAudioSourceGameObject, this.gameObject.scene);
                audioSource = _tempAudioSourceGameObject.AddComponent<AudioSource>();
            }
            // audio source settings ---------------------------------------------------------------------------------
            audioSource.transform.position = location;
            audioSource.time = 0.0f;
            audioSource.clip = audioClip;
            audioSource.pitch = pitch;
            audioSource.spatialBlend = spatialBlend;
            audioSource.panStereo = panStereo;
            audioSource.loop = loop;
            audioSource.bypassEffects = bypassEffects;
            audioSource.bypassListenerEffects = bypassListenerEffects;
            audioSource.bypassReverbZones = bypassReverbZones;
            audioSource.priority = priority;
            audioSource.reverbZoneMix = reverbZoneMix;
            audioSource.dopplerLevel = dopplerLevel;
            audioSource.spread = spread;
            audioSource.rolloffMode = rolloffMode;
            audioSource.minDistance = minDistance;
            audioSource.maxDistance = maxDistance;
            // track and volume ---------------------------------------------------------------------------------
            if (settingsSo != null)
            {
                audioSource.outputAudioMixerGroup = settingsSo.MasterAudioMixerGroup;
                switch (mmSoundManagerTrack)
                {
                    case MMSoundManagerTracks.Master:
                        audioSource.outputAudioMixerGroup = settingsSo.MasterAudioMixerGroup;
                        break;
                    case MMSoundManagerTracks.Music:
                        audioSource.outputAudioMixerGroup = settingsSo.MusicAudioMixerGroup;
                        break;
                    case MMSoundManagerTracks.Sfx:
                        audioSource.outputAudioMixerGroup = settingsSo.SfxAudioMixerGroup;
                        break;
                    case MMSoundManagerTracks.UI:
                        audioSource.outputAudioMixerGroup = settingsSo.UIAudioMixerGroup;
                        break;
                }

            }
            if (audioGroup)
            {
                audioSource.outputAudioMixerGroup = audioGroup;
            }
            audioSource.volume = volume;
            // we start playing the sound
            audioSource.Play();

            // we destroy the host after the clip has played if it was a one time AS.
            if (!loop && !recycleAudioSource)
            {
                Destroy(_tempAudioSourceGameObject, audioClip.length);
            }
            // we fade the sound in if needed
            if (fade)
            {
                FadeSound(audioSource, fadeDuration, fadeInitialVolume, volume, fadeTween);
            }
            // we handle soloing
            if (soloSingleTrack)
            {
                MuteSoundsOnTrack(mmSoundManagerTrack, true, 0f);
                audioSource.mute = false;
                if (autoUnSoloOnEnd)
                {
                    MuteSoundsOnTrack(mmSoundManagerTrack, false, audioClip.length);
                }
            }
            else if (soloAllTracks)
            {
                MuteAllSounds();
                audioSource.mute = false;
                if (autoUnSoloOnEnd)
                {
                    StartCoroutine(MuteAllSoundsCoroutine(audioClip.length, false));
                }
            }
            // we prepare for storage
            _sound.ID = ID;
            _sound.Track = mmSoundManagerTrack;
            _sound.Source = audioSource;
            _sound.Persistent = persistent;

            // we check if that audiosource is already being tracked in _sounds
            bool alreadyIn = false;
            for (int i = 0; i < _sounds.Count; i++)
            {
                if (_sounds[i].Source == audioSource)
                {
                    _sounds[i] = _sound;
                    alreadyIn = true;
                }
            }
            if (!alreadyIn)
            {
                _sounds.Add(_sound);
            }

            // we return the audiosource reference
            return audioSource;
        }

        #endregion

        #region SoundControls
        /// <summary>
        /// Pauses the specified audiosource
        /// </summary>
        /// <param name="source"></param>
        public virtual void PauseSound(AudioSource source)
        {
            source.Pause();
        }

        /// <summary>
        /// resumes play on the specified audio source
        /// </summary>
        /// <param name="source"></param>
        public virtual void ResumeSound(AudioSource source)
        {
            source.Play();
        }

        /// <summary>
        /// Stops the specified audio source
        /// </summary>
        /// <param name="source"></param>
        public virtual void StopSound(AudioSource source)
        {
            source.Stop();
        }

        /// <summary>
        /// Frees a specific sound, stopping it and returning it to the pool
        /// </summary>
        /// <param name="source"></param>
        public virtual void FreeSound(AudioSource source)
        {
            source.Stop();
            if (!_pool.FreeSound(source))
            {
                Destroy(source.gameObject);
            }
        }

        #endregion

        #region TrackControls
        /// <summary>
        /// Mutes an entire track
        /// </summary>
        /// <param name="track"></param>
        public virtual void MuteTrack(MMSoundManagerTracks track)
        {
            ControlTrack(track, ControlTrackModes.Mute, 0f);
        }

        /// <summary>
        /// Unmutes an entire track
        /// </summary>
        /// <param name="track"></param>
        public virtual void UnmuteTrack(MMSoundManagerTracks track)
        {
            ControlTrack(track, ControlTrackModes.Unmute, 0f);
        }

        /// <summary>
        /// Sets the volume of an entire track
        /// </summary>
        /// <param name="track"></param>
        /// <param name="volume"></param>
        public virtual void SetTrackVolume(MMSoundManagerTracks track, float volume)
        {
            ControlTrack(track, ControlTrackModes.SetVolume, volume);
        }

        /// <summary>
        /// Returns the current volume of a track
        /// </summary>
        /// <param name="track"></param>
        /// <param name="volume"></param>
        public virtual float GetTrackVolume(MMSoundManagerTracks track, bool mutedVolume)
        {
            switch (track)
            {
                case MMSoundManagerTracks.Master:
                    if (mutedVolume)
                    {
                        return settingsSo.Settings.MutedMasterVolume;
                    }
                    else
                    {
                        return settingsSo.Settings.MasterVolume;
                    }
                case MMSoundManagerTracks.Music:
                    if (mutedVolume)
                    {
                        return settingsSo.Settings.MutedMusicVolume;
                    }
                    else
                    {
                        return settingsSo.Settings.MusicVolume;
                    }
                case MMSoundManagerTracks.Sfx:
                    if (mutedVolume)
                    {
                        return settingsSo.Settings.MutedSfxVolume;
                    }
                    else
                    {
                        return settingsSo.Settings.SfxVolume;
                    }
                case MMSoundManagerTracks.UI:
                    if (mutedVolume)
                    {
                        return settingsSo.Settings.MutedUIVolume;
                    }
                    else
                    {
                        return settingsSo.Settings.UIVolume;
                    }
            }

            return 1f;
        }

        /// <summary>
        /// Pauses all sounds on a track
        /// </summary>
        /// <param name="track"></param>
        public virtual void PauseTrack(MMSoundManagerTracks track)
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if (sound.Track == track)
                {
                    sound.Source.Pause();
                }
            }
        }

        /// <summary>
        /// Plays or resumes all sounds on a track
        /// </summary>
        /// <param name="track"></param>
        public virtual void PlayTrack(MMSoundManagerTracks track)
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if (sound.Track == track)
                {
                    sound.Source.Play();
                }
            }
        }

        /// <summary>
        /// Stops all sounds on a track
        /// </summary>
        /// <param name="track"></param>
        public virtual void StopTrack(MMSoundManagerTracks track)
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if (sound.Track == track)
                {
                    sound.Source.Stop();
                }
            }
        }

        /// <summary>
        /// Stops all sounds on a track, and returns them to the pool
        /// </summary>
        /// <param name="track"></param>
        public virtual void FreeTrack(MMSoundManagerTracks track)
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if (sound.Track == track)
                {
                    sound.Source.Stop();
                    sound.Source.gameObject.SetActive(false);
                }
            }
        }

        /// <summary>
        /// Mutes the music track, QoL method ready to bind to a UnityEvent
        /// </summary>
        public virtual void MuteMusic() { MuteTrack(MMSoundManagerTracks.Music); }

        /// <summary>
        /// Unmutes the music track, QoL method ready to bind to a UnityEvent
        /// </summary>
        public virtual void UnmuteMusic() { UnmuteTrack(MMSoundManagerTracks.Music); }

        /// <summary>
        /// Mutes the sfx track, QoL method ready to bind to a UnityEvent
        /// </summary>
        public virtual void MuteSfx() { MuteTrack(MMSoundManagerTracks.Sfx); }


        /// <summary>
        /// Unmutes the sfx track, QoL method ready to bind to a UnityEvent
        /// </summary>
        public virtual void UnmuteSfx() { UnmuteTrack(MMSoundManagerTracks.Sfx); }

        /// <summary>
        /// Mutes the UI track, QoL method ready to bind to a UnityEvent
        /// </summary>
        public virtual void MuteUI() { MuteTrack(MMSoundManagerTracks.UI); }

        /// <summary>
        /// Unmutes the UI track, QoL method ready to bind to a UnityEvent
        /// </summary>
        public virtual void UnmuteUI() { UnmuteTrack(MMSoundManagerTracks.UI); }

        /// <summary>
        /// Mutes the master track, QoL method ready to bind to a UnityEvent
        /// </summary>
        public virtual void MuteMaster() { MuteTrack(MMSoundManagerTracks.Master); }

        /// <summary>
        /// Unmutes the master track, QoL method ready to bind to a UnityEvent
        /// </summary>
        public virtual void UnmuteMaster() { UnmuteTrack(MMSoundManagerTracks.Master); }

        /// <summary>
        /// Sets the volume of the Music track to the specified value, QoL method, ready to bind to a UnityEvent
        /// </summary>
        public virtual void SetVolumeMusic(float newVolume) { SetTrackVolume(MMSoundManagerTracks.Music, newVolume); }
        /// <summary>
        /// Sets the volume of the SFX track to the specified value, QoL method, ready to bind to a UnityEvent
        /// </summary>
        public virtual void SetVolumeSfx(float newVolume) { SetTrackVolume(MMSoundManagerTracks.Sfx, newVolume); }
        /// <summary>
        /// Sets the volume of the UI track to the specified value, QoL method, ready to bind to a UnityEvent
        /// </summary>
        public virtual void SetVolumeUI(float newVolume) { SetTrackVolume(MMSoundManagerTracks.UI, newVolume); }
        /// <summary>
        /// Sets the volume of the Master track to the specified value, QoL method, ready to bind to a UnityEvent
        /// </summary>
        public virtual void SetVolumeMaster(float newVolume) { SetTrackVolume(MMSoundManagerTracks.Master, newVolume); }


        /// <summary>
        /// A method that will let you mute/unmute a track, or set it to a specified volume
        /// </summary>
        public enum ControlTrackModes { Mute, Unmute, SetVolume }
        protected virtual void ControlTrack(MMSoundManagerTracks track, ControlTrackModes trackMode, float volume = 0.5f)
        {
            string target = "";
            float savedVolume = 0f;
            switch (track)
            {
                case MMSoundManagerTracks.Master:
                    target = settingsSo.Settings.MasterVolumeParameter;
                    if (trackMode == ControlTrackModes.Mute) { settingsSo.TargetAudioMixer.GetFloat(target, out settingsSo.Settings.MutedMasterVolume); settingsSo.Settings.MasterOn = false; }
                    else if (trackMode == ControlTrackModes.Unmute) { savedVolume = settingsSo.Settings.MutedMasterVolume; settingsSo.Settings.MasterOn = true; }
                    break;
                case MMSoundManagerTracks.Music:
                    target = settingsSo.Settings.MusicVolumeParameter;
                    if (trackMode == ControlTrackModes.Mute)
                    {
                        settingsSo.TargetAudioMixer.GetFloat(target, out settingsSo.Settings.MutedMusicVolume);
                        settingsSo.Settings.MusicOn = false;
                    }
                    else if (trackMode == ControlTrackModes.Unmute)
                    {
                        savedVolume = settingsSo.Settings.MutedMusicVolume;
                        settingsSo.Settings.MusicOn = true;
                    }
                    break;
                case MMSoundManagerTracks.Sfx:
                    target = settingsSo.Settings.SfxVolumeParameter;
                    if (trackMode == ControlTrackModes.Mute) { settingsSo.TargetAudioMixer.GetFloat(target, out settingsSo.Settings.MutedSfxVolume); settingsSo.Settings.SfxOn = false; }
                    else if (trackMode == ControlTrackModes.Unmute) { savedVolume = settingsSo.Settings.MutedSfxVolume; settingsSo.Settings.SfxOn = true; }
                    break;
                case MMSoundManagerTracks.UI:
                    target = settingsSo.Settings.UIVolumeParameter;
                    if (trackMode == ControlTrackModes.Mute) { settingsSo.TargetAudioMixer.GetFloat(target, out settingsSo.Settings.MutedUIVolume); settingsSo.Settings.UIOn = false; }
                    else if (trackMode == ControlTrackModes.Unmute) { savedVolume = settingsSo.Settings.MutedUIVolume; settingsSo.Settings.UIOn = true; }
                    break;
            }
            switch (trackMode)
            {
                case ControlTrackModes.Mute:
                    settingsSo.SetTrackVolume(track, 0f);
                    break;
                case ControlTrackModes.Unmute:
                    settingsSo.SetTrackVolume(track, settingsSo.MixerVolumeToNormalized(savedVolume));
                    break;
                case ControlTrackModes.SetVolume:
                    settingsSo.SetTrackVolume(track, volume);
                    break;
            }
            settingsSo.GetTrackVolumes();
            if (settingsSo.Settings.AutoSave)
            {
                settingsSo.SaveSoundSettings();
            }
        }

        #endregion


        #region Fades


        /// <summary>
        /// Fades an entire track over the specified duration towards the desired finalVolume
        /// </summary>
        /// <param name="track"></param>
        /// <param name="duration"></param>
        /// <param name="initialVolume"></param>
        /// <param name="finalVolume"></param>
        /// <param name="tweenType"></param>
        public virtual void FadeTrack(MMSoundManagerTracks track, float duration, float initialVolume = 0f, float finalVolume = 1f, MMTweenType tweenType = null)
        {
            StartCoroutine(FadeTrackCoroutine(track, duration, initialVolume, finalVolume, tweenType));
        }

        /// <summary>
        /// Fades a target sound towards a final volume over time
        /// </summary>
        /// <param name="source"></param>
        /// <param name="duration"></param>
        /// <param name="initialVolume"></param>
        /// <param name="finalVolume"></param>
        /// <param name="tweenType"></param>
        public virtual void FadeSound(AudioSource source, float duration, float initialVolume, float finalVolume, MMTweenType tweenType)
        {
            StartCoroutine(FadeCoroutine(source, duration, initialVolume, finalVolume, tweenType));
        }

        /// <summary>
        /// Fades an entire track over time
        /// </summary>
        /// <param name="track"></param>
        /// <param name="duration"></param>
        /// <param name="initialVolume"></param>
        /// <param name="finalVolume"></param>
        /// <param name="tweenType"></param>
        /// <returns></returns>
        protected virtual IEnumerator FadeTrackCoroutine(MMSoundManagerTracks track, float duration, float initialVolume, float finalVolume, MMTweenType tweenType)
        {
            float startedAt = Time.unscaledTime;
            if (tweenType == null)
            {
                tweenType = new MMTweenType(MMTween.MMTweenCurve.EaseInOutQuartic);
            }
            while (Time.unscaledTime - startedAt <= duration)
            {
                float elapsedTime = Time.unscaledTime - startedAt;
                float newVolume = MMTween.Tween(elapsedTime, 0f, duration, initialVolume, finalVolume, tweenType);
                settingsSo.SetTrackVolume(track, newVolume);
                yield return null;
            }
            settingsSo.SetTrackVolume(track, finalVolume);
        }

        /// <summary>
        /// Fades an audiosource's volume over time
        /// </summary>
        /// <param name="source"></param>
        /// <param name="duration"></param>
        /// <param name="initialVolume"></param>
        /// <param name="finalVolume"></param>
        /// <param name="tweenType"></param>
        /// <returns></returns>
        protected virtual IEnumerator FadeCoroutine(AudioSource source, float duration, float initialVolume, float finalVolume, MMTweenType tweenType)
        {
            float startedAt = Time.unscaledTime;
            if (tweenType == null)
            {
                tweenType = new MMTweenType(MMTween.MMTweenCurve.EaseInOutQuartic);
            }
            while (Time.unscaledTime - startedAt <= duration)
            {
                float elapsedTime = Time.unscaledTime - startedAt;
                float newVolume = MMTween.Tween(elapsedTime, 0f, duration, initialVolume, finalVolume, tweenType);
                source.volume = newVolume;
                yield return null;
            }
            source.volume = finalVolume;

        }

        #endregion

        #region Solo
        /// <summary>
        /// Mutes all sounds playing on a specific track
        /// </summary>
        /// <param name="track"></param>
        /// <param name="mute"></param>
        /// <param name="delay"></param>
        public virtual void MuteSoundsOnTrack(MMSoundManagerTracks track, bool mute, float delay = 0f)
        {
            StartCoroutine(MuteSoundsOnTrackCoroutine(track, mute, delay));
        }

        /// <summary>
        /// Mutes all sounds playing on the MMSoundManager
        /// </summary>
        /// <param name="mute"></param>
        public virtual void MuteAllSounds(bool mute = true)
        {
            StartCoroutine(MuteAllSoundsCoroutine(0f, mute));
        }


        /// <summary>
        /// Mutes all sounds on the specified track after an optional delay
        /// </summary>
        /// <param name="track"></param>
        /// <param name="mute"></param>
        /// <param name="delay"></param>
        /// <returns></returns>
        protected virtual IEnumerator MuteSoundsOnTrackCoroutine(MMSoundManagerTracks track, bool mute, float delay)
        {
            if (delay > 0)
            {
                yield return MMCoroutine.WaitForUnscaled(delay);
            }
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if (sound.Track == track)
                {
                    sound.Source.mute = mute;
                }
            }

        }

        /// <summary>
        /// Mutes all sounds after an optional delay
        /// </summary>
        /// <param name="delay"></param>
        /// <param name="mute"></param>
        /// <returns></returns>
        protected virtual IEnumerator MuteAllSoundsCoroutine(float delay, bool mute = true)
        {
            if (delay > 0)
            {
                yield return MMCoroutine.WaitForUnscaled(delay);
            }
            foreach (MMSoundManagerSound sound in _sounds)
            {
                sound.Source.mute = mute;
            }
        }

        #endregion

        #region Find
        /// <summary>
        /// Returns an audio source played with the specified ID, if one is found
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        public virtual AudioSource FindByID(int ID)
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if (sound.ID == ID)
                {
                    return sound.Source;
                }
            }

            return null;
        }

        /// <summary>
        /// Returns an audio source played with the specified ID, if one is found
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        public virtual AudioSource FindByClip(AudioClip clip)
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if (sound.Source.clip == clip)
                {
                    return sound.Source;
                }
            }

            return null;
        }


        #endregion


        #region AllSoundsControls
        /// <summary>
        /// Pauses all sounds playing on the MMSoundManager
        /// </summary>
        public virtual void PauseAllSounds()
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                sound.Source.Pause();
            }
        }

        /// <summary>
        /// Pauses all sounds playing on the MMSoundManager
        /// </summary>
        public virtual void PlayAllSounds()
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                sound.Source.Play();
            }
        }

        /// <summary>
        /// Stops all sounds playing on the MMSoundManager
        /// </summary>
        public virtual void StopAllSounds()
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                sound.Source.Stop();
            }
        }

        /// <summary>
        /// Stops all sounds and returns them to the pool
        /// </summary>
        public virtual void FreeAllSounds()
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if (sound.Source != null)
                {
                    FreeSound(sound.Source);
                }
            }
        }

        /// <summary>
        /// Stops all sounds except the persistent ones, and returns them to the pool
        /// </summary>
        public virtual void FreeAllSoundsButPersistent()
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if ((!sound.Persistent) && (sound.Source != null))
                {
                    FreeSound(sound.Source);
                }
            }
        }

        /// <summary>
        /// Stops all looping sounds and returns them to the pool
        /// </summary>
        public virtual void FreeAllLoopingSounds()
        {
            foreach (MMSoundManagerSound sound in _sounds)
            {
                if ((sound.Source.loop) && (sound.Source != null))
                {
                    FreeSound(sound.Source);
                }
            }
        }


        #endregion

        

        #region Events

        /// <summary>
        /// Registered on enable, triggers every time a new scene is loaded
        /// At which point we free all sounds except the persistent ones
        /// </summary>
        protected virtual void OnSceneLoaded(Scene arg0, LoadSceneMode loadSceneMode)
        {
            FreeAllSoundsButPersistent();
        }

        public void OnMMEvent(MMSoundManagerTrackEvent soundManagerTrackEvent)
        {
            switch (soundManagerTrackEvent.TrackEventType)
            {
                case MMSoundManagerTrackEventTypes.MuteTrack:
                    MuteTrack(soundManagerTrackEvent.Track);
                    break;
                case MMSoundManagerTrackEventTypes.UnmuteTrack:
                    UnmuteTrack(soundManagerTrackEvent.Track);
                    break;
                case MMSoundManagerTrackEventTypes.SetVolumeTrack:
                    SetTrackVolume(soundManagerTrackEvent.Track, soundManagerTrackEvent.Volume);
                    break;
                case MMSoundManagerTrackEventTypes.PlayTrack:
                    PlayTrack(soundManagerTrackEvent.Track);
                    break;
                case MMSoundManagerTrackEventTypes.PauseTrack:
                    PauseTrack(soundManagerTrackEvent.Track);
                    break;
                case MMSoundManagerTrackEventTypes.StopTrack:
                    StopTrack(soundManagerTrackEvent.Track);
                    break;
                case MMSoundManagerTrackEventTypes.FreeTrack:
                    FreeTrack(soundManagerTrackEvent.Track);
                    break;
            }
            
        }

        public void OnMMEvent(MMSoundManagerEvent soundManagerEvent)
        {
            switch (soundManagerEvent.EventType)
            {
                case MMSoundManagerEventTypes.SaveSettings:
                    SaveSettings();
                    break;
                case MMSoundManagerEventTypes.LoadSettings:
                    settingsSo.LoadSoundSettings();
                    break;
                case MMSoundManagerEventTypes.ResetSettings:
                    settingsSo.ResetSoundSettings();
                    break;
            }
        }

        /// <summary>
        /// Save sound settings to file
        /// </summary>
        public virtual void SaveSettings()
        {
            settingsSo.SaveSoundSettings();
        }

        /// <summary>
        /// Loads sound settings from file
        /// </summary>
        public virtual void LoadSettings()
        {
            settingsSo.LoadSoundSettings();
        }

        /// <summary>
        /// Deletes any saved sound settings
        /// </summary>
        public virtual void ResetSettings()
        {
            settingsSo.ResetSoundSettings();
        }

        public void OnMMEvent(MMSoundManagerSoundControlEvent soundControlEvent)
        {
            if (soundControlEvent.TargetSource == null)
            {
                _tempAudioSource = FindByID(soundControlEvent.SoundID);
            }
            else
            {
                _tempAudioSource = soundControlEvent.TargetSource;
            }

            if (_tempAudioSource != null)
            {
                switch (soundControlEvent.MMSoundManagerSoundControlEventType)
                {
                    case MMSoundManagerSoundControlEventTypes.Pause:
                        PauseSound(_tempAudioSource);
                        break;
                    case MMSoundManagerSoundControlEventTypes.Resume:
                        ResumeSound(_tempAudioSource);
                        break;
                    case MMSoundManagerSoundControlEventTypes.Stop:
                        StopSound(_tempAudioSource);
                        break;
                    case MMSoundManagerSoundControlEventTypes.Free:
                        FreeSound(_tempAudioSource);
                        break;
                }
            }
        }

        public void OnMMEvent(MMSoundManagerTrackFadeEvent trackFadeEvent)
        {
            FadeTrack(trackFadeEvent.Track, trackFadeEvent.FadeDuration, settingsSo.GetTrackVolume(trackFadeEvent.Track), trackFadeEvent.FinalVolume, trackFadeEvent.FadeTween);
        }

        public void OnMMEvent(MMSoundManagerSoundFadeEvent soundFadeEvent)
        {
            _tempAudioSource = FindByID(soundFadeEvent.SoundID);
            if (_tempAudioSource != null)
            {
                FadeSound(_tempAudioSource, soundFadeEvent.FadeDuration, _tempAudioSource.volume, soundFadeEvent.FinalVolume,
                   soundFadeEvent.FadeTween);
            }
        }

        public void OnMMEvent(MMSoundManagerAllSoundsControlEvent allSoundsControlEvent)
        {
            switch (allSoundsControlEvent.EventType)
            {
                case MMSoundManagerAllSoundsControlEventTypes.Pause:
                    PauseAllSounds();
                    break;
                case MMSoundManagerAllSoundsControlEventTypes.Play:
                    PlayAllSounds();
                    break;
                case MMSoundManagerAllSoundsControlEventTypes.Stop:
                    StopAllSounds();
                    break;
                case MMSoundManagerAllSoundsControlEventTypes.Free:
                    FreeAllSounds();
                    break;
                case MMSoundManagerAllSoundsControlEventTypes.FreeAllButPersistent:
                    FreeAllSoundsButPersistent();
                    break;
                case MMSoundManagerAllSoundsControlEventTypes.FreeAllLooping:
                    FreeAllLoopingSounds();
                    break;
            }
        }


        private void OnMMSfxEvent(AudioClip clipToPlay, AudioMixerGroup audioGroup, float volume, float pitch)
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Location = this.transform.position;
            options.AudioGroup = audioGroup;
            options.Volume = volume;
            options.Pitch = pitch;
            options.MmSoundManagerTrack = MMSoundManagerTracks.Sfx;
            options.Loop = false;
            PlaySound(clipToPlay, options);
        }

        public virtual AudioSource OnMMSoundManagerSoundPlayEvent(AudioClip clip, MMSoundManagerPlayOptions options)
        {
            return PlaySound(clip, options);
        }

        /// <summary>
        /// On enable we start listening for events
        /// </summary>
        protected virtual void OnEnable()
        {
            MMSfxEvent.Register(OnMMSfxEvent);
            MMSoundManagerSoundPlayEvent.Register(OnMMSoundManagerSoundPlayEvent);
            this.MMEventStartListening<MMSoundManagerEvent>();
            this.MMEventStartListening<MMSoundManagerTrackEvent>();
            this.MMEventStartListening<MMSoundManagerSoundControlEvent>();
            this.MMEventStartListening<MMSoundManagerTrackFadeEvent>();
            this.MMEventStartListening<MMSoundManagerSoundFadeEvent>();
            this.MMEventStartListening<MMSoundManagerAllSoundsControlEvent>();
            SceneManager.sceneLoaded += OnSceneLoaded;
        }

       

        /// <summary>
        /// On disable we stop listening for events
        /// </summary>
        protected virtual void OnDisable()
        {
            if (_enabled)
            {
                MMSfxEvent.Unregister(OnMMSfxEvent);
                MMSoundManagerSoundPlayEvent.Unregister(OnMMSoundManagerSoundPlayEvent);
                this.MMEventStopListening<MMSoundManagerEvent>();
                this.MMEventStopListening<MMSoundManagerTrackEvent>();
                this.MMEventStopListening<MMSoundManagerSoundControlEvent>();
                this.MMEventStopListening<MMSoundManagerTrackFadeEvent>();
                this.MMEventStopListening<MMSoundManagerSoundFadeEvent>();
                this.MMEventStopListening<MMSoundManagerAllSoundsControlEvent>();
                SceneManager.sceneLoaded -= OnSceneLoaded;
            }
               
        }

        
        #endregion
    }

}

数据

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;

namespace MoreMountains.Tools
{
    /// <summary>
    /// 一个用于保存声音设置的类(音乐打开或关闭,sfx打开或关闭)
    /// </summary>
    [Serializable]
    [CreateAssetMenu(menuName = "MoreMountains/Audio/MMSoundManagerSettings")]
    public class MMSoundManagerSettingsSO : ScriptableObject
    {
        [Header("Audio Mixer")]
        [Tooltip("播放声音时使用的混音器")]
        public AudioMixer TargetAudioMixer;
        [Tooltip("主组")]
        public AudioMixerGroup MasterAudioMixerGroup;
        [Tooltip("播放所有音乐声音的组")]
        public AudioMixerGroup MusicAudioMixerGroup;
        [Tooltip("播放所有音效的组")]
        public AudioMixerGroup SfxAudioMixerGroup;
        [Tooltip("播放所有UI声音的组")]
        public AudioMixerGroup UIAudioMixerGroup;

        [Header("设置展开")]
        [Tooltip("此MMSoundManager的完整设置")]
        public MMSoundManagerSettings Settings;

        protected const string _saveFolderName = "MMSoundManager/";
        protected const string _saveFileName = "mmsound.settings";

        #region SaveAndLoad
        /// <summary>
        /// 将声音设置保存到文件
        /// </summary>
        public virtual void SaveSoundSettings()
        {

            MMSaveLoadManager.Save(this.Settings, _saveFileName, _saveFolderName);
        }


        /// <summary>
        /// 从文件加载声音设置(如果找到)
        /// </summary>
        public virtual void LoadSoundSettings()
        {
            if (Settings.OverrideMixerSettings)
            {
                MMSoundManagerSettings settings =
                    (MMSoundManagerSettings)MMSaveLoadManager.Load(typeof(MMSoundManagerSettings), _saveFileName,
                        _saveFolderName);
                if (settings != null)
                {
                    this.Settings = settings;
                    ApplyTrackVolumes();
                }

            }
        }

        /// <summary>
        /// 通过销毁保存文件重置声音设置
        /// </summary>
        public virtual void ResetSoundSettings()
        {
            MMSaveLoadManager.DeleteSave(_saveFileName, _saveFolderName);
        }

        #endregion

        #region Volume
        /// <summary>
        /// 将所选曲目的音量设置为参数中传递的值
        /// </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>
        /// 返回指定曲目的音量
        /// </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>
        /// 将每个曲目的音量分配给设置值
        /// </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>
        /// 将音量应用于所有曲目,并在需要时进行保存
        /// </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>
        /// 将标准化音量转换为混音器组db比例
        /// </summary>
        /// <param name="normalizedVolume"></param>
        /// <returns></returns>
        public virtual float NormalizedToMixerVolume(float normalizedVolume)
        {
            return Mathf.Log10(normalizedVolume) * 20;
        }

        /// <summary>
        /// 将混音器音量转换为归一化值
        /// </summary>
        /// <param name="mixerVolume"></param>
        /// <returns></returns>
        public virtual float MixerVolumeToNormalized(float mixerVolume)
        {
            return (float)Math.Pow(10, (mixerVolume / 20));
        }


        #endregion
    }

}

设置

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MoreMountains.Tools
{
    /// <summary>
    /// 此类存储MMSoundManager设置,并允许您从MMSoundManagerSettingsSO的检查器中调整它们
    /// </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")]
        [Tooltip("下面描述的设置是否应覆盖AudioMixer中定义的设置")]
        public bool OverrideMixerSettings = true;

        [Header("混音器暴露参数")]
        [Tooltip("AudioMixer中公开的MasterVolume参数的名称")]
        public string MasterVolumeParameter = "MasterVolume";
        [Tooltip("AudioMixer中公开的MusicVolume参数的名称")]
        public string MusicVolumeParameter = "MusicVolume";
        [Tooltip("AudioMixer中暴露的SfxVolume参数的名称")]
        public string SfxVolumeParameter = "SfxVolume";
        [Tooltip("AudioMixer中公开的UIVolume参数的名称")]
        public string UIVolumeParameter = "UIVolume";

        [Header("Master")]
        [MMReadOnly]
        [Range(_minimalVolume, _maxVolume)]
        [Tooltip("主音量")]
        public float MasterVolume = _defaultVolume;
        [Tooltip("当前主轨道是否处于活动状态")]
        [MMReadOnly]
        public bool MasterOn = true;
        [Tooltip("主曲目静音前的音量")]
        [MMReadOnly]
        public float MutedMasterVolume;

        [Header("音乐")]
        [MMReadOnly]
        [Range(_minimalVolume, _maxVolume)]
        [Tooltip("音乐音量")]
        public float MusicVolume = _defaultVolume;
        [Tooltip("当前音乐曲目是否处于活动状态")]
        [MMReadOnly]
        public bool MusicOn = true;
        [Tooltip("音乐曲目静音前的音量")]
        [MMReadOnly]
        public float MutedMusicVolume;

        [Header("音效")]
        [MMReadOnly]
        [Range(_minimalVolume, _maxVolume)]
        [Tooltip("声音fx音量")]
        public float SfxVolume = _defaultVolume;
        [Tooltip("SFX轨迹当前是否处于活动状态")]
        [MMReadOnly]
        public bool SfxOn = true;
        [Tooltip("SFX曲目静音前的音量")]
        [MMReadOnly]
        public float MutedSfxVolume;


        [Header("UI")]
        [MMReadOnly]
        [Range(_minimalVolume, _maxVolume)]
        [Tooltip("UI的音量")]
        public float UIVolume = _defaultVolume;
        [Tooltip("UI轨迹当前是否处于活动状态")]
        [MMReadOnly]
        public bool UIOn = true;
        [Tooltip("UI曲目静音前的音量")]
        [MMReadOnly]
        public float MutedUIVolume;

        [Header("Save & Load")]
        [Tooltip("MMSoundManager在启动时是否应自动加载设置")]
        public bool AutoLoad = true;
        [Tooltip("是否应自动保存设置中的每次更改。如果没有,则必须调用save MMSoundManager事件以保存设置")]
        public bool AutoSave = false;

    }
}

播放选项

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;

namespace MoreMountains.Tools
{
    /// <summary>
    /// 用于存储MMSoundManager播放选项的类
    /// </summary>
    [Serializable]
    public class MMSoundManagerPlayOptions 
    {
        /// 播放声音的曲目
        public MMSoundManager.MMSoundManagerTracks MmSoundManagerTrack;
        /// 放置声音的位置
        public Vector3 Location;
        /// 声音是否应该循环
        public bool Loop;
        /// 播放声音的音量
        public float Volume;
        /// 声音的ID,有助于以后再次找到该声音
        public int ID;
        /// 播放时是否要减弱声音
        public bool Fade;
        /// 声音衰减前的初始音量
        public float FadeInitialVolume;
        /// 淡入淡出的持续时间(秒)
        public float FadeDuration;
        /// 减弱声音时使用的tween
        public MMTweenType FadeTween;
        ///声音是否应在场景过渡期间持续存在
        public bool Persistent;
        /// 如果您不想从池中选择一个,则可以使用一个音频源
        public AudioSource RecycleAudioSource;
        /// 如果你不想在任何预设曲目上播放,可以使用一个音频组
        public AudioMixerGroup AudioGroup;
        /// 音频源的音高
        public float Pitch;
        /// 以立体声方式(向左或向右)平移播放声音。这仅适用于单声道或立体声的声音
        public float PanStereo;
        /// 设置此音频源受3D空间化计算(衰减、多普勒等)的影响程度。0.0使声音完全为2D,1.0使其完全为3D
        public float SpatialBlend;
        /// 该声音是否应在其目标曲目上以独奏模式播放。如果是,当此声音开始播放时,该曲目上的所有其他声音都将被静音
        public bool SoloSingleTrack;
        /// 该声音是否应在所有其他曲目上以独奏模式播放。如果是,当此声音开始播放时,所有其他曲目都将静音
        public bool SoloAllTracks;
        /// 如果在任何独奏模式下,一旦声音停止播放,AutoUnSoloOnEnd将自动取消曲目静音
        public bool AutoUnSoloOnEnd;
        /// 旁路效果(从过滤器组件或全局侦听器过滤器应用)
        public bool BypassEffects;
        /// 当在AudioListener上设置全局效果时,它将不会应用于由AudioSource生成的音频信号。如果音频源正在混音器组中播放,则不适用
        public bool BypassListenerEffects;
        /// 当设置不将信号从音频源路由到与混响区相关的全局混响时
        public bool BypassReverbZones;
        /// 设置音频源的优先级
        public int Priority;
        /// 来自音频源的信号将混合到与混响区相关的全局混响中的量
        public float ReverbZoneMix;
        /// 设置此音频源的多普勒刻度
        public float DopplerLevel;
        /// 设置扬声器空间中3d立体声或多声道声音的扩散角度(以度为单位)
        public int Spread;
        /// 设置/获取音频源随距离衰减的方式
        public AudioRolloffMode RolloffMode;
        /// 在最小距离内,音频源的音量将不再增大
        public float MinDistance;
        /// (对数滚降)MaxDistance是声音停止衰减的距离
        public float MaxDistance;


        /// <summary>
        /// 一组默认选项,旨在适应最常见的情况。
        ///使用选项时,最好从这个开始,只覆盖你需要覆盖的内容。
        ///
        ///示例:
        /// 
        ///MMSoundManagerPlayOptions选项=MMSoundManagerPlay选项。违约;
        ///选项。Loop=循环;
        ///选项。位置=Vector3.zero;
        ///选项。MmSoundManagerTrack=MMSoundManager。MMSoundManager曲目。音乐;
        ///     
        ///MMSoundManager声音播放事件。触发器(声音剪辑,选项);
        ///
        ///在这里,我们初始化一个新的本地选项集,覆盖其循环、位置和轨迹设置,并使用它调用一个播放事件
        /// 
        /// </summary>
        public static MMSoundManagerPlayOptions Default
        {
            get
            {
                MMSoundManagerPlayOptions defaultOptions = new MMSoundManagerPlayOptions();
                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 = null;
                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;
                return defaultOptions;
            }
        }
    }

}

播放监听

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;

namespace MoreMountains.Tools
{
    /// <summary>
    /// 此事件将允许您在MMSoundManager上播放声音
    ///
    ///示例:MMSoundManager声音播放事件。触发器(ExplosionSfx,MMSoundManager.MMSoundManagerTracks.Sfx,this.transform.position);
    ///将在SFX轨道上调用它的对象的位置播放一个剪辑(这里我们的剪辑称为ExplosionSfx)
    /// </summary>
    public class MMSoundManagerSoundPlayEvent
    {
        public delegate AudioSource Delegate(AudioClip clip, MMSoundManagerPlayOptions options);
        //event是delegate 的一种特例,就是受限制的delegate
        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);
        }

    }

}

对象池

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace MoreMountains.Tools
{
    /// <summary>
    /// 此类管理音频源的对象池
    /// </summary>
    [Serializable]
    public class MMSoundManagerAudioPool
    {
        protected List<AudioSource> _pool;

        /// <summary>
        /// 用即用型音频源填满游泳池
        /// </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>();
                temporaryAudioHost.transform.SetParent(parent);
                temporaryAudioHost.SetActive(false);
                _pool.Add(tempSource);
            }


        }

        /// <summary>
        /// 播放完毕后禁用音频源
        /// </summary>
        /// <param name="duration"></param>
        /// <param name="targetObject"></param>
        /// <returns></returns>
        public virtual IEnumerator AutoDisableAudioSource(float duration, AudioSource source, AudioClip clip)
        {
            yield return MMCoroutine.WaitFor(duration);
            if (source.clip != clip)
            {
                yield break;
            }
            source.gameObject.SetActive(false);
        }

        /// <summary>
        /// 从池中提取可用的音频源
        /// </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>
        /// 停止音频源并将其返回到池中
        /// </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;
        }


    }

}

结构体

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MoreMountains.Tools
{
    /// <summary>
    /// 一个简单的结构体,用于存储有关MMSoundManager播放的声音的信息
    /// </summary>
    [Serializable]
    public struct MMSoundManagerSound
    {
        public int ID;
        public MMSoundManager.MMSoundManagerTracks Track;
        public AudioSource Source;
        /// 此声音是否可以在多个场景中播放
        public bool Persistent;
    }

}

背景音乐

using MoreMountains.Tools;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MoreMountains.CorgiEngine
{
    /// <summary>
    /// 将此类添加到GameObject中,使其在发出通知时播放背景音乐。
    ///小心:一次只能播放一首背景音乐。
    /// </summary>
    [AddComponentMenu("Corgi Engine/Audio/Background Music")]
    public class BackgroundMusic : MMPersistentHumbleSingleton<BackgroundMusic>
    {
        [Tooltip("要播放的背景音乐音频片段")]
        public AudioClip SoundClip;
        [Tooltip("音乐是否应该循环播放")]
        public bool Loop = true;

        public AudioSource _source;

        /// <summary>
        /// 获取与该游戏对象关联的音频源,并要求游戏管理器播放它
        /// </summary>
        protected virtual void Start()
        {
            MMSoundManagerPlayOptions options = MMSoundManagerPlayOptions.Default;
            options.Loop = Loop;
            options.Location = Vector3.zero;
            options.MmSoundManagerTrack = MMSoundManager.MMSoundManagerTracks.Music;
            MMSoundManagerSoundPlayEvent.Trigger(SoundClip, options);
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值