325 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using FMOD.Studio;
 | |
| using FMODUnity;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Runtime.InteropServices;
 | |
| using UnityEditor;
 | |
| using UnityEngine;
 | |
| using UnityEngine.SceneManagement;
 | |
| 
 | |
| using STOP_MODE = FMOD.Studio.STOP_MODE;
 | |
| 
 | |
| public class AudioManager : MonoBehaviour
 | |
| {
 | |
|     [Header("Volume")]
 | |
|     [Range(0, 1)]
 | |
| 
 | |
|     public float MasterVolume = 1;
 | |
|     [Range(0, 1)]
 | |
| 
 | |
|     public float MusicVolume = 1;
 | |
|     [Range(0, 1)]
 | |
| 
 | |
|     public float SFXVolume = 1;
 | |
|     [Range(0, 1)]
 | |
| 
 | |
|     public float UIVolume = 1;
 | |
|     [Range(0, 1)]
 | |
| 
 | |
|     private Bus masterBus;
 | |
|     private Bus musicBus;
 | |
|     private Bus sfxBus;
 | |
|     private Bus reverbBus;
 | |
|     private Bus uiBus;
 | |
|     const string sid = "00000000-0000-0000-0000-000000000000";
 | |
|     static readonly Guid nullGuid = new Guid(sid);
 | |
| 
 | |
|     private List<EventInstance> eventInstances = new();
 | |
|     class TimelineInfo
 | |
|     {
 | |
|         public FMOD.StringWrapper LastMarker = new();
 | |
|     }
 | |
|     TimelineInfo timelineInfo;
 | |
|     GCHandle timelineHandle;
 | |
| 
 | |
|     EVENT_CALLBACK beatCallback;
 | |
| 
 | |
|     private static AudioManager _instance { get; set; }
 | |
|     public static event Action<string> OnNewBGMMarker;
 | |
|     // public static AudioManager instance;
 | |
|     private static EventInstance musicEventInstance;
 | |
| 
 | |
| 
 | |
|     // public access for the Singleton
 | |
|     // and lazy instantiation if not exists
 | |
|     public static AudioManager Instance
 | |
|     {
 | |
|         get
 | |
|         {
 | |
|             // if exists directly return
 | |
|             if (_instance) return _instance;
 | |
| 
 | |
|             // otherwise search it in the scene
 | |
|             _instance = FindObjectOfType<AudioManager>();
 | |
| 
 | |
|             // found it?
 | |
|             if (_instance) return _instance;
 | |
| 
 | |
|             // otherwise create and initialize it
 | |
|             CreateInstance();
 | |
| 
 | |
|             return _instance;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
 | |
|     private static void CreateInstance()
 | |
|     {
 | |
|         // skip if already exists
 | |
|         if (_instance) return;
 | |
|         if (SceneManager.GetActiveScene().name == "BankLoader") return;
 | |
|         InitializeInstance(new GameObject(nameof(AudioManager)).AddComponent<AudioManager>());
 | |
|     }
 | |
| 
 | |
|     private static void InitializeInstance(AudioManager instance)
 | |
|     {
 | |
|         _instance = instance;
 | |
|         DontDestroyOnLoad(_instance.gameObject);
 | |
|         _instance.eventInstances = new List<EventInstance>();
 | |
|         _instance.masterBus = RuntimeManager.GetBus("bus:/");
 | |
|         _instance.musicBus = RuntimeManager.GetBus("bus:/Music");
 | |
|         _instance.sfxBus = RuntimeManager.GetBus("bus:/SFX");
 | |
|         _instance.uiBus = RuntimeManager.GetBus("bus:/UI");
 | |
|         // _instance.MasterVolume = SaveManager.Instance.systemData.MasterVolume / 100f;
 | |
|         // _instance.SFXVolume = SaveManager.Instance.systemData.SFXVolume / 100f;
 | |
|         // _instance.MusicVolume = SaveManager.Instance.systemData.MusicVolume / 100f;
 | |
|         // _instance.UIVolume = SaveManager.Instance.systemData.UIVolume / 100f;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     private void Awake()
 | |
|     {
 | |
|         if (_instance && _instance != this)
 | |
|         {
 | |
|             Destroy(gameObject);
 | |
|             return;
 | |
|         }
 | |
|         InitializeInstance(this);
 | |
|     }
 | |
| 
 | |
|     public EventInstance CreateInstance(EventReference eventReference)
 | |
|     {
 | |
|         EventInstance eventInstance = RuntimeManager.CreateInstance(eventReference);
 | |
|         eventInstances.Add(eventInstance);
 | |
|         return eventInstance;
 | |
|     }
 | |
| 
 | |
|     private void Update()
 | |
|     {
 | |
|         musicBus.setVolume(MusicVolume);
 | |
|         sfxBus.setVolume(SFXVolume);
 | |
|         uiBus.setVolume(UIVolume);
 | |
|         masterBus.setVolume(MasterVolume);
 | |
|     }
 | |
| 
 | |
|     public static bool IsEventReferenceValid(EventReference eventReference)
 | |
|     {
 | |
|         return eventReference.Guid != nullGuid;
 | |
|     }
 | |
|     public static void PlayOneShot(EventReference eventReference)
 | |
|     {
 | |
|         if (eventReference.Guid != nullGuid)
 | |
|             RuntimeManager.PlayOneShot(eventReference);
 | |
|         else
 | |
|         {
 | |
|             Debug.LogWarning("EventReference is null, ignoring...");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void PlayOneShot3D(EventReference sound, Vector3 position)
 | |
|     {
 | |
|         if (!IsEventReferenceValid(sound))
 | |
|         {
 | |
|             Debug.LogWarning("Tried to play invalid FMOD event (3D)");
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         RuntimeManager.PlayOneShot(sound, position);
 | |
|     }
 | |
| 
 | |
|     public EventInstance PlayAttachedInstance(EventReference sound, GameObject go)
 | |
|     {
 | |
|         if (!IsEventReferenceValid(sound))
 | |
|         {
 | |
|             Debug.LogWarning("Tried to play invalid FMOD event (attached)");
 | |
|             return default;
 | |
|         }
 | |
| 
 | |
|         EventInstance instance = RuntimeManager.CreateInstance(sound);
 | |
|         RuntimeManager.AttachInstanceToGameObject(instance, go);
 | |
|         instance.start();
 | |
|         // instance.release();
 | |
| 
 | |
|         eventInstances.Add(instance);
 | |
|         return instance;
 | |
|     }
 | |
| 
 | |
|     public void StopInstance(EventInstance instance, STOP_MODE mode = STOP_MODE.ALLOWFADEOUT)
 | |
|     {
 | |
|         if (!instance.isValid()) return;
 | |
|         instance.stop(mode);
 | |
|         instance.release();
 | |
|     }
 | |
| 
 | |
|     public void SetParameter(EventInstance instance, string parameterName, float value)
 | |
|     {
 | |
|         if (!instance.isValid()) return;
 | |
|         instance.setParameterByName(parameterName, value);
 | |
|     }
 | |
| 
 | |
|     public void SetGlobalParameter(string parameterName, float value)
 | |
|     {
 | |
|         RuntimeManager.StudioSystem.setParameterByName(parameterName, value);
 | |
|     }
 | |
| 
 | |
|     public void InitializeMusic(EventReference musicEventReference)
 | |
|     {
 | |
|         if (musicEventReference.Guid == nullGuid)
 | |
|         {
 | |
|             Debug.LogWarning("EventReference is null, ignoring.");
 | |
|             return;
 | |
|         }
 | |
|         musicEventInstance = CreateInstance(musicEventReference);
 | |
|         timelineInfo = new TimelineInfo();
 | |
| 
 | |
|         // Explicitly create the delegate object and assign it to a member so it doesn't get freed
 | |
|         // by the garbage collected while it's being used
 | |
|         beatCallback = new EVENT_CALLBACK(BeatEventCallback);
 | |
| 
 | |
|         // Pin the class that will store the data modified during the callback
 | |
|         timelineHandle = GCHandle.Alloc(timelineInfo);
 | |
|         // Pass the object through the userdata of the instance
 | |
|         musicEventInstance.setUserData(GCHandle.ToIntPtr(timelineHandle));
 | |
|         musicEventInstance.setCallback(beatCallback, EVENT_CALLBACK_TYPE.TIMELINE_BEAT | EVENT_CALLBACK_TYPE.TIMELINE_MARKER);
 | |
|     }
 | |
| 
 | |
|     public void SetMusicParameter(string parameter, float value)
 | |
|     {
 | |
|         musicEventInstance.setParameterByName(parameter, value);
 | |
|     }
 | |
|     public void StartMusic()
 | |
|     {
 | |
|         if (!musicEventInstance.isValid())
 | |
|         {
 | |
|             Debug.LogWarning("Music is not initialized yet, ignoring.");
 | |
|             return;
 | |
|         }
 | |
|         musicEventInstance.start();
 | |
|     }
 | |
| 
 | |
|     public int GetMusicPosition()
 | |
|     {
 | |
|         musicEventInstance.getTimelinePosition(out int position);
 | |
|         return position;
 | |
|     }
 | |
| 
 | |
|     public void StopSFX()
 | |
|     {
 | |
|         sfxBus.stopAllEvents(FMOD.Studio.STOP_MODE.IMMEDIATE);
 | |
|     }
 | |
| 
 | |
|     public void FadeOutMusic()
 | |
|     {
 | |
|         musicEventInstance.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT);
 | |
|         musicEventInstance.release();
 | |
| 
 | |
|     }
 | |
| 
 | |
|     public static bool IsPlaying()
 | |
|     {
 | |
|         musicEventInstance.getPlaybackState(out PLAYBACK_STATE state);
 | |
|         return state != PLAYBACK_STATE.STOPPED;
 | |
|     }
 | |
| 
 | |
|     public static bool IsPlaying(EventInstance instance)
 | |
|     {
 | |
|         instance.getPlaybackState(out PLAYBACK_STATE state);
 | |
|         return state != PLAYBACK_STATE.STOPPED;
 | |
|     }
 | |
|     public static void Unpause()
 | |
|     {
 | |
|         musicEventInstance.setPaused(false);
 | |
|     }
 | |
| 
 | |
|     public static void Unpause(EventInstance instance)
 | |
|     {
 | |
|         instance.setPaused(false);
 | |
|     }
 | |
| 
 | |
|     public static void Pause()
 | |
|     {
 | |
|         musicEventInstance.setPaused(true);
 | |
|     }
 | |
| 
 | |
|     public static void Pause(EventInstance instance)
 | |
|     {
 | |
|         instance.setPaused(true);
 | |
|     }
 | |
| 
 | |
|     private void CleanUp()
 | |
|     {
 | |
|         if (eventInstances != null)
 | |
|         {
 | |
|             foreach (EventInstance eventInstance in eventInstances)
 | |
|             {
 | |
|                 eventInstance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
 | |
|                 eventInstance.release();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     // taken from https://www.fmod.com/docs/2.02/unity/examples-timeline-callbacks.html
 | |
|     [AOT.MonoPInvokeCallback(typeof(EVENT_CALLBACK))]
 | |
|     static FMOD.RESULT BeatEventCallback(EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
 | |
|     {
 | |
|         EventInstance instance = new(instancePtr);
 | |
| 
 | |
|         // Retrieve the user data
 | |
|         FMOD.RESULT result = instance.getUserData(out IntPtr timelineInfoPtr);
 | |
|         if (result != FMOD.RESULT.OK)
 | |
|         {
 | |
|             Debug.LogError("Timeline Callback error: " + result);
 | |
|         }
 | |
|         else if (timelineInfoPtr != IntPtr.Zero)
 | |
|         {
 | |
|             // Get the object to store beat and marker details
 | |
|             GCHandle timelineHandle = GCHandle.FromIntPtr(timelineInfoPtr);
 | |
|             TimelineInfo timelineInfo = (TimelineInfo)timelineHandle.Target;
 | |
| 
 | |
|             switch (type)
 | |
|             {
 | |
|                 case EVENT_CALLBACK_TYPE.TIMELINE_MARKER:
 | |
|                     {
 | |
|                         var parameter = (TIMELINE_MARKER_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(TIMELINE_MARKER_PROPERTIES));
 | |
|                         timelineInfo.LastMarker = parameter.name;
 | |
|                         OnNewBGMMarker?.Invoke(parameter.name);
 | |
|                         break;
 | |
|                     }
 | |
|                 case EVENT_CALLBACK_TYPE.DESTROYED:
 | |
|                     {
 | |
|                         // Now the event has been destroyed, unpin the timeline memory so it can be garbage collected
 | |
|                         timelineHandle.Free();
 | |
|                         break;
 | |
|                     }
 | |
|             }
 | |
|         }
 | |
|         return FMOD.RESULT.OK;
 | |
|     }
 | |
| 
 | |
|     private void OnDestroy()
 | |
|     {
 | |
|         CleanUp();
 | |
|     }
 | |
| 
 | |
| } |