using UnityEngine; /// /// Centralized judge sound player. Assign four AudioSources (one per judgement) in the Inspector. /// Designed to be low-latency: each judgement has a dedicated AudioSource and PlayOneShot is used. /// Place this component on a persistent gameobject in the scene (or your audio container). /// public class JudgeSoundManager : MonoBehaviour { public static JudgeSoundManager Instance { get; private set; } [Header("AudioSources (assign dedicated AudioSource for each judgement)")] public AudioSource perfectSource; public AudioSource greatSource; public AudioSource goodSource; public AudioSource missSource; [Header("Enable/Disable per-judgement playback")] [Tooltip("Toggle whether Perfect sounds are played")] public bool enablePerfect = true; [Tooltip("Toggle whether Great sounds are played")] public bool enableGreat = true; [Tooltip("Toggle whether Good sounds are played")] public bool enableGood = true; [Tooltip("Toggle whether Miss sounds are played")] public bool enableMiss = true; [Header("Fallback clips (optional, used if the assigned AudioSource has no clip)")] public AudioClip perfectClip; public AudioClip greatClip; public AudioClip goodClip; public AudioClip missClip; private void Awake() { if (Instance == null) { Instance = this; // Do not automatically destroy; leave lifecycle to scene management } else { Destroy(gameObject); return; } // Ensure AudioSources are configured for low latency ConfigureSource(perfectSource); ConfigureSource(greatSource); ConfigureSource(goodSource); ConfigureSource(missSource); } private void ConfigureSource(AudioSource src) { if (src == null) return; src.playOnAwake = false; // keep 3D off for UI/flat sounds src.spatialBlend = 0f; // ensure not looping src.loop = false; } public enum JudgeResult { Perfect, Great, Good, Miss } public void PlayJudgeSound(string result) { if (string.IsNullOrEmpty(result)) return; switch (result) { case "Perfect": PlayJudgeSound(JudgeResult.Perfect); break; case "Great": PlayJudgeSound(JudgeResult.Great); break; case "Good": PlayJudgeSound(JudgeResult.Good); break; case "Miss": PlayJudgeSound(JudgeResult.Miss); break; default: break; } } public void PlayJudgeSound(JudgeResult r) { // honor inspector toggles to allow disabling specific judgement sounds switch (r) { case JudgeResult.Perfect: if (!enablePerfect) return; PlayOnSource(perfectSource, perfectClip); break; case JudgeResult.Great: if (!enableGreat) return; PlayOnSource(greatSource, greatClip); break; case JudgeResult.Good: if (!enableGood) return; PlayOnSource(goodSource, goodClip); break; case JudgeResult.Miss: if (!enableMiss) return; PlayOnSource(missSource, missClip); break; } } private void PlayOnSource(AudioSource src, AudioClip fallbackClip) { if (src != null) { // Prefer PlayOneShot so sources can overlap and we don't need to manage clip state if (src.clip != null) { src.PlayOneShot(src.clip); } else if (fallbackClip != null) { src.PlayOneShot(fallbackClip); } } else if (fallbackClip != null) { // no dedicated source provided: play via a temporary one-shot using AudioSource.PlayClipAtPoint at camera position AudioSource.PlayClipAtPoint(fallbackClip, Camera.main != null ? Camera.main.transform.position : Vector3.zero); } } }