124 lines
4.1 KiB
C#
124 lines
4.1 KiB
C#
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|