using UnityEngine; using UnityEngine.Events; public class RhythmGameManager : MonoBehaviour { public enum ResultType { Perfect, Good, Miss } [Header("References")] [SerializeField] private RhythmGameUI ui; [SerializeField] private CatNoteSpawner noteSpawner; [SerializeField] private RhythmHapticManager haptic; [Header("Game Settings")] [SerializeField] private string songTitle = "고양이 합창단의 노래"; [SerializeField] private bool startOnAwake = false; [SerializeField] private bool useFishBonusOnStart = false; [Header("Rules")] [SerializeField] private int requiredScore = 1000; [SerializeField] private int allowedMisses = 5; [SerializeField] private int perfectScore = 150; [SerializeField] private int goodScore = 80; [Header("Judgment Distance")] [Tooltip("HitZone 중심과 노트 중심의 거리입니다. 값이 작을수록 판정이 어려워집니다.")] [SerializeField] private float perfectDistance = 35f; [SerializeField] private float goodDistance = 75f; [SerializeField] private float missRemoveDistance = 120f; [Header("Events")] public UnityEvent onGameStarted; public UnityEvent onGameSuccess; public UnityEvent onGameFailed; public UnityEvent onPerfect; public UnityEvent onGood; public UnityEvent onMiss; [Header("Debug")] [SerializeField] private bool showDebugLog = true; private int score; private int combo; private int missCount; private bool activeGame; private bool fishBonusActive; public int Score => score; public int Combo => combo; public int MissCount => missCount; public bool IsPlaying => activeGame; public bool FishBonusActive => fishBonusActive; private void OnEnable() { if (noteSpawner != null) noteSpawner.NoteAutoMissed += HandleAutoMiss; } private void OnDisable() { if (noteSpawner != null) noteSpawner.NoteAutoMissed -= HandleAutoMiss; } private void Start() { if (startOnAwake) StartGame(useFishBonusOnStart); else InitializeIdleState(); } private void InitializeIdleState() { score = 0; combo = 0; missCount = 0; activeGame = false; if (ui != null) { ui.InitializeUI(songTitle); ui.ShowFishBonus(false); } if (noteSpawner != null) noteSpawner.StopSpawn(true); } public void StartGame() { StartGame(false); } public void StartGameWithFishBonus() { StartGame(true); } public void StartGame(bool useFishBonus) { score = 0; combo = 0; missCount = 0; fishBonusActive = useFishBonus; activeGame = true; if (ui != null) { ui.InitializeUI(songTitle); ui.ShowFishBonus(fishBonusActive); ui.UpdateScore(score); ui.UpdateCombo(combo); } if (noteSpawner != null) noteSpawner.StartSpawn(fishBonusActive); onGameStarted?.Invoke(); if (showDebugLog) Debug.Log("[RhythmGameManager] 리듬게임 시작"); } public void SubmitHit() { if (!activeGame) return; if (noteSpawner == null) { RegisterMiss(null, false); return; } RhythmNote closestNote = noteSpawner.GetClosestNote(); if (closestNote == null) { RegisterMiss(null, false); return; } float distance = closestNote.GetDistanceToHitZone(); ResultType result = EvaluateResult(distance); switch (result) { case ResultType.Perfect: RegisterPerfect(closestNote); break; case ResultType.Good: RegisterGood(closestNote); break; case ResultType.Miss: bool removeNote = distance <= missRemoveDistance; RegisterMiss(closestNote, removeNote); break; } CheckGameEnd(); } private ResultType EvaluateResult(float distance) { if (distance <= perfectDistance) return ResultType.Perfect; if (distance <= goodDistance) return ResultType.Good; return ResultType.Miss; } private void RegisterPerfect(RhythmNote note) { score += perfectScore; combo++; if (noteSpawner != null) noteSpawner.RemoveNote(note, true); if (ui != null) { ui.UpdateScore(score); ui.UpdateCombo(combo); ui.ShowJudgment("완벽!"); } if (haptic != null) haptic.Perfect(); onPerfect?.Invoke(); if (showDebugLog) Debug.Log("[RhythmGameManager] Perfect"); } private void RegisterGood(RhythmNote note) { score += goodScore; combo++; if (noteSpawner != null) noteSpawner.RemoveNote(note, true); if (ui != null) { ui.UpdateScore(score); ui.UpdateCombo(combo); ui.ShowJudgment("좋아!"); } if (haptic != null) haptic.Good(); onGood?.Invoke(); if (showDebugLog) Debug.Log("[RhythmGameManager] Good"); } private void RegisterMiss(RhythmNote note, bool removeNote) { missCount++; combo = 0; if (removeNote && noteSpawner != null && note != null) noteSpawner.RemoveNote(note, true); if (ui != null) { ui.UpdateCombo(combo); ui.ShowJudgment("실패!"); } if (haptic != null) haptic.Miss(); onMiss?.Invoke(); if (showDebugLog) Debug.Log($"[RhythmGameManager] Miss {missCount}/{allowedMisses}"); } private void HandleAutoMiss(RhythmNote note) { if (!activeGame) return; RegisterMiss(note, false); CheckGameEnd(); } private void CheckGameEnd() { if (!activeGame) return; if (score >= requiredScore) { GameSuccess(); return; } if (missCount >= allowedMisses) { GameFail(); } } private void GameSuccess() { activeGame = false; if (noteSpawner != null) noteSpawner.StopSpawn(true); if (ui != null) ui.ShowFinalResult("리듬 성공!"); onGameSuccess?.Invoke(); if (showDebugLog) Debug.Log("[RhythmGameManager] 리듬게임 성공"); } private void GameFail() { activeGame = false; if (noteSpawner != null) noteSpawner.StopSpawn(true); if (ui != null) ui.ShowFinalResult("리듬 실패!"); onGameFailed?.Invoke(); if (showDebugLog) Debug.Log("[RhythmGameManager] 리듬게임 실패"); } public void StopGame() { activeGame = false; if (noteSpawner != null) noteSpawner.StopSpawn(true); } public void ResetGame() { StartGame(fishBonusActive); } }