ProjectDDD/Packages/SLUnity/SLSound.cs
2025-07-08 19:46:31 +09:00

295 lines
8.3 KiB
C#

using Superlazy;
using System.Collections.Generic;
using UnityEngine;
public class SLSound : SLGameComponent
{
private static GameObject soundRoot;
private static bool mute = false;
// 재생 관리를 위한 큐
private static readonly Dictionary<string, List<AudioSource>> enableObjects = new Dictionary<string, List<AudioSource>>();
// 페이드
private static readonly List<AudioSource> fades = new List<AudioSource>();
private static readonly List<AudioSource> removes = new List<AudioSource>();
// 리소스 풀
private static int unusedCount = 0;
private static readonly Dictionary<string, Queue<AudioSource>> unused = new Dictionary<string, Queue<AudioSource>>();
// mute 정보
private static readonly HashSet<string> muteTagList = new HashSet<string>();
private static readonly List<string> currentFramePlays = new List<string>();
private static readonly Dictionary<string, SLEntity> delaySounds = new Dictionary<string, SLEntity>();
private static AudioSource GetAudioSource(string clipName)
{
if (currentFramePlays.Contains(clipName)) return null;
currentFramePlays.Add(clipName);
AudioSource audioSource = null;
if (unused.ContainsKey(clipName) && unused[clipName].Count != 0)
{
audioSource = unused[clipName].Dequeue();
--unusedCount;
}
else
{
//TODO : 추후 사운드도 ResourceManager 로 이관해야함
var sound = SLResources.Load<AudioClip>(clipName);
if (sound != null)
{
sound.name = clipName;
audioSource = soundRoot.AddComponent<AudioSource>();
audioSource.clip = sound;
}
}
return audioSource;
}
public static void MuteSounds(bool mute)
{
SLSound.mute = mute;
}
public static void ClearPool()
{
foreach (var queue in unused)
{
foreach (var source in queue.Value)
{
Object.Destroy(source);
}
}
unused.Clear();
unusedCount = 0;
}
public static void PlaySound(string clipName, string tag = "Effect", bool loop = false, bool unique = false, bool reset = true, float volume = 1, float delay = 0)
{
if (string.IsNullOrEmpty(clipName)) return;
if (clipName == "Off") return; // TODO: Off라는 이름의 사운드가 없어 임시 처리. 추후 수정 필요
if (soundRoot == null) return;
if (tag == "BGM")
{
volume *= SLGame.Session["Option"]["Volume"]["BGM"] * 0.1f;
}
else
{
volume *= SLGame.Session["Option"]["Volume"]["SE"] * 0.1f;
}
if (delay > 0)
{
SLEntity context = SLEntity.Empty;
context["Tage"] = tag;
context["Loop"] = loop;
context["Unique"] = unique;
context["Reset"] = reset;
context["Volume"] = volume;
context["Delay"] = delay;
delaySounds.Add(clipName, context);
return;
}
var exist = false;
if (unique)
{
if (enableObjects.ContainsKey(tag))
{
var stopList = new List<AudioSource>();
foreach (var source in enableObjects[tag])
{
if (reset == false && source.clip.name == clipName)
{
exist = true;
source.volume = volume;
continue;
}
if (source.loop)
{
fades.Add(source);
}
else
{
source.Stop();
removes.Add(source);
}
stopList.Add(source);
}
foreach (var source in stopList)
{
enableObjects[tag].Remove(source);
}
}
}
if (exist) return;
if (tag != "BGM" && tag != "Weather" && IsMuteTag(tag)) return;
if (enableObjects.ContainsKey(tag) && tag != "TextSound")
{
if (enableObjects[tag].Count > 4) return;
}
var audioSource = GetAudioSource(clipName);
if (audioSource != null)
{
// 여기서 각종 옵션을 제어한다.
{
audioSource.loop = loop;
audioSource.volume = volume;
audioSource.mute = IsMuteTag(tag);
}
audioSource.enabled = true;
audioSource.Play();
if (enableObjects.ContainsKey(tag) == false)
enableObjects[tag] = new List<AudioSource>();
enableObjects[tag].Add(audioSource);
}
}
public static void StopSound(string tag)
{
if (enableObjects.ContainsKey(tag))
{
foreach (var source in enableObjects[tag])
{
if (source.loop)
{
fades.Add(source);
}
else
{
source.Stop();
removes.Add(source);
}
}
enableObjects[tag].Clear();
}
}
public static void SetMute(string tag, bool mute)
{
if (mute) muteTagList.Add(tag);
else muteTagList.Remove(tag);
}
private static bool IsMuteTag(string tag)
{
if (mute) return true;
if (tag == "BGM" || tag == "Weather")
{
return muteTagList.Contains("BGM");
}
else
{
return muteTagList.Contains("Effect");
}
}
public static void VolumeChange(float before, float next)
{
foreach (var clip in enableObjects["BGM"])
{
clip.volume = before == 0f ? next * 0.1f : clip.volume * next / before;
}
}
public override void Begin()
{
if (soundRoot == null)
{
soundRoot = new GameObject("SoundRoot");
Object.DontDestroyOnLoad(soundRoot);
}
}
public override void Update()
{
foreach (var sourceList in enableObjects)
{
foreach (var audioSource in sourceList.Value)
{
if (audioSource.clip.loadInBackground == false && audioSource.isPlaying == false)
{
removes.Add(audioSource);
}
else
{
audioSource.mute = IsMuteTag(sourceList.Key);
}
}
sourceList.Value.RemoveAll(u => u.clip.loadInBackground == false && u.isPlaying == false);
}
foreach (var source in fades)
{
source.volume -= Time.unscaledDeltaTime;
if (source.volume <= 0)
{
source.Stop();
removes.Add(source);
}
}
fades.RemoveAll(u => u.volume <= 0);
foreach (var audioSource in removes)
{
var name = audioSource.clip.name;
if (unused.ContainsKey(name) == false)
{
unused[name] = new Queue<AudioSource>();
}
unused[name].Enqueue(audioSource);
++unusedCount;
audioSource.enabled = false;
}
removes.Clear();
if (unusedCount >= 30)
{
ClearPool();
}
if (delaySounds.Count > 0)
{
var removeDelaySounds = new List<string>();
foreach (var delaySound in delaySounds)
{
delaySound.Value["Delay"] -= Time.unscaledDeltaTime;
if (delaySound.Value["Delay"] <= 0)
{
var context = delaySound.Value;
PlaySound(delaySound.Key, context["Tag"], context["Loop"], context["Unique"], context["Reset"], context["Volume"]);
removeDelaySounds.Add(delaySound.Key);
}
}
foreach (var key in removeDelaySounds)
{
delaySounds.Remove(key);
}
}
currentFramePlays.Clear();
}
public override void End()
{
}
}