diff --git a/Assets/01_Scenes/WhaleAdventure_VR/Rooms/CatsRoom.unity b/Assets/01_Scenes/WhaleAdventure_VR/Rooms/CatsRoom.unity index e2d3f78e..771dbefd 100644 --- a/Assets/01_Scenes/WhaleAdventure_VR/Rooms/CatsRoom.unity +++ b/Assets/01_Scenes/WhaleAdventure_VR/Rooms/CatsRoom.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76e69faebfad2fa3c8c6740c4598192b39e2cc7262cc2f85488433958ee7c763 -size 1997430 +oid sha256:bf233d545596bc310da144b6a8935ddbd486528e791c1431adee908a17901b24 +size 2042631 diff --git a/Assets/02_Scripts/Managers/CatsRoom/RhythmManager.cs b/Assets/02_Scripts/Managers/CatsRoom/RhythmManager.cs index 76ab64aa..881c40d4 100644 --- a/Assets/02_Scripts/Managers/CatsRoom/RhythmManager.cs +++ b/Assets/02_Scripts/Managers/CatsRoom/RhythmManager.cs @@ -17,6 +17,8 @@ public class RhythmManager : MonoBehaviour [SerializeField] private float _goodWindow = 0.10f; [SerializeField] private float _badWindow = 0.15f; // 이 밖이면 입력 무시(헛침) + [SerializeField] private GameObject StartButtonObj; + public float SongTime => _audioSource.time; // 모든 타이밍의 기준 private List SongNoteList; @@ -25,6 +27,14 @@ public class RhythmManager : MonoBehaviour private Func _songTimeProvider; // 노트에 넘길 시간 제공자 (매 스폰마다 람다 재생성 방지용 캐시) private readonly List _activeNotes = new(); // 화면에 떠 있는(아직 처리 안 된) 노트들 + // 점수 상태 (HUD가 읽음). 누적 로직은 RhythmScore가 전담 + public RhythmScore Score { get; } = new(); + + public event Action OnSongStarted; // 곡 시작 (ScoreHud 표시 / ResultHud 숨김) + public event Action OnJudged; // 노트 하나가 판정될 때마다 (HUD 연출용) + public event Action OnScoreChanged; // 점수/콤보가 바뀔 때 (실시간 HUD) + public event Action OnSongFinished; // 곡이 끝났을 때 (결과창) + private void Awake() { _songTimeProvider = () => SongTime; // 한 번만 만들어 모든 노트가 공유 @@ -80,6 +90,11 @@ public void StartSong() _nextNoteIndex = 0; _activeNotes.Clear(); + Score.Reset(); + OnSongStarted?.Invoke(); // ScoreHud 표시 / ResultHud 숨김 + OnScoreChanged?.Invoke(Score); // HUD 초기화(0점) + StartButtonObj.SetActive(false); + _audioSource.Play(); _isPlaying = true; @@ -90,17 +105,21 @@ public void StartSong() // 곡 정지 + 주변음 복구 public void StopSong() { + if (!_isPlaying) return; // 중복 호출 방지(결과창 두 번 뜨는 것 차단) + _audioSource.Stop(); _isPlaying = false; if (SoundManager.Instance != null) SoundManager.Instance.ExitMinigameMode(); + + OnSongFinished?.Invoke(Score); // 결과창 표시 } // 노트가 판정선을 지나쳐 스스로 Miss 처리될 때 호출 (노트는 이미 자기 파괴됨) private void OnNoteMissed(RhythmNoteInstance note) { _activeNotes.Remove(note); - // TODO: 점수/콤보 시스템 연결 시 Miss 반영 + ApplyJudge(Result.Miss); Debug.Log("[Rhythm] Miss (지나침)"); } @@ -134,10 +153,18 @@ public void OnPlayerInput() _activeNotes.Remove(target); Destroy(target.gameObject); - // TODO: 점수/콤보 시스템 연결 시 result 반영 + ApplyJudge(result); Debug.Log($"[Rhythm] {result} (diff {bestDiff:F3}s)"); } + // 판정 결과를 점수에 반영하고 이벤트로 알림 + private void ApplyJudge(Result result) + { + Score.Apply(result); + OnJudged?.Invoke(result); + OnScoreChanged?.Invoke(Score); + } + // diff = 절대 시간차(초). 윈도우 안이면 Perfect/Good/Bad, 밖이면 Miss(입력 무시) public Result Judge(float diff) { diff --git a/Assets/02_Scripts/Rhythm/RhythmScore.cs b/Assets/02_Scripts/Rhythm/RhythmScore.cs new file mode 100644 index 00000000..77a79c87 --- /dev/null +++ b/Assets/02_Scripts/Rhythm/RhythmScore.cs @@ -0,0 +1,59 @@ +// 리듬게임 점수/콤보/판정수 누적 (Unity 비의존 순수 로직 - 테스트하기 쉬움) +public class RhythmScore +{ + // 판정별 기본 점수 + private const int PerfectPoint = 100; + private const int GoodPoint = 50; + private const int BadPoint = 10; + + public int Score { get; private set; } + public int Combo { get; private set; } + public int MaxCombo { get; private set; } + + public int PerfectCount { get; private set; } + public int GoodCount { get; private set; } + public int BadCount { get; private set; } + public int MissCount { get; private set; } + + public int TotalJudged => PerfectCount + GoodCount + BadCount + MissCount; + + // 판정 하나를 점수에 반영 + public void Apply(Result result) + { + switch (result) + { + case Result.Perfect: + PerfectCount++; + Combo++; + Score += PerfectPoint + Combo; // 콤보가 쌓일수록 보너스 (연속 보상) + break; + case Result.Good: + GoodCount++; + Combo++; + Score += GoodPoint + Combo; + break; + case Result.Bad: + BadCount++; + Combo = 0; // 콤보 끊김 + Score += BadPoint; + break; + case Result.Miss: + MissCount++; + Combo = 0; // 콤보 끊김 + break; + } + + if (Combo > MaxCombo) MaxCombo = Combo; + } + + public void Reset() + { + Score = 0; + Combo = 0; + MaxCombo = 0; + PerfectCount = 0; + GoodCount = 0; + BadCount = 0; + MissCount = 0; + } +} diff --git a/Assets/02_Scripts/Rhythm/RhythmScore.cs.meta b/Assets/02_Scripts/Rhythm/RhythmScore.cs.meta new file mode 100644 index 00000000..2019c883 --- /dev/null +++ b/Assets/02_Scripts/Rhythm/RhythmScore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: adfd55d5980f58940ad4cb735db42f89 \ No newline at end of file diff --git a/Assets/02_Scripts/UI/RhythmResultHud.cs b/Assets/02_Scripts/UI/RhythmResultHud.cs new file mode 100644 index 00000000..0a851a0f --- /dev/null +++ b/Assets/02_Scripts/UI/RhythmResultHud.cs @@ -0,0 +1,50 @@ +using TMPro; +using UnityEngine; + +// 곡 종료 후 결과창. 총점/판정수/정확도/등급/최대콤보를 표시한다. +public class RhythmResultHud : MonoBehaviour +{ + [SerializeField] private RhythmManager _manager; + [SerializeField] private GameObject _root; // 결과창 루트 + [SerializeField] private TMP_Text _scoreText; + [SerializeField] private TMP_Text _maxComboText; + [SerializeField] private TMP_Text _countText; // Perfect/Good/Bad/Miss 모아서 표시 + + private void Awake() + { + if (_root != null) _root.SetActive(false); + } + + private void OnEnable() + { + if (_manager == null) return; + _manager.OnSongStarted += Hide; + _manager.OnSongFinished += Show; + } + + private void OnDisable() + { + if (_manager == null) return; + _manager.OnSongStarted -= Hide; + _manager.OnSongFinished -= Show; + } + + private void Hide() + { + if (_root != null) _root.SetActive(false); // 곡 시작/재시작 시 이전 결과창 숨김 + } + + private void Show(RhythmScore score) + { + if (_scoreText != null) _scoreText.text = $"{score.Score:N0}"; + if (_maxComboText != null) _maxComboText.text = $"{score.MaxCombo}"; + if (_countText != null) + _countText.text = + $"Perfect {score.PerfectCount}\n" + + $"Good {score.GoodCount}\n" + + $"Bad {score.BadCount}\n" + + $"Miss {score.MissCount}"; + + if (_root != null) _root.SetActive(true); + } +} diff --git a/Assets/02_Scripts/UI/RhythmResultHud.cs.meta b/Assets/02_Scripts/UI/RhythmResultHud.cs.meta new file mode 100644 index 00000000..39c6d543 --- /dev/null +++ b/Assets/02_Scripts/UI/RhythmResultHud.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b187e420986956a4ba9407759ed4050f \ No newline at end of file diff --git a/Assets/02_Scripts/UI/RhythmScoreHud.cs b/Assets/02_Scripts/UI/RhythmScoreHud.cs new file mode 100644 index 00000000..f274e190 --- /dev/null +++ b/Assets/02_Scripts/UI/RhythmScoreHud.cs @@ -0,0 +1,56 @@ +using TMPro; +using UnityEngine; + +// 플레이 중 실시간 점수/콤보 HUD. RhythmManager 이벤트만 구독하고 표시만 한다. +public class RhythmScoreHud : MonoBehaviour +{ + [SerializeField] private RhythmManager _manager; + [SerializeField] private GameObject _root; // HUD 루트 (곡 시작/종료에 켜고 끔) + [SerializeField] private TMP_Text _scoreText; + [SerializeField] private TMP_Text _comboText; + [SerializeField] private TMP_Text _judgeText; // Perfect/Good 등 최근 판정 표시(선택) + + private void Awake() + { + if (_root != null) _root.SetActive(false); // 곡 시작 전엔 숨김 + } + + private void OnEnable() + { + if (_manager == null) return; + _manager.OnSongStarted += HandleSongStarted; + _manager.OnScoreChanged += HandleScoreChanged; + _manager.OnJudged += HandleJudged; + _manager.OnSongFinished += HandleSongFinished; + } + + private void OnDisable() + { + if (_manager == null) return; + _manager.OnSongStarted -= HandleSongStarted; + _manager.OnScoreChanged -= HandleScoreChanged; + _manager.OnJudged -= HandleJudged; + _manager.OnSongFinished -= HandleSongFinished; + } + + private void HandleSongStarted() + { + if (_root != null) _root.SetActive(true); // 곡 시작 시 실시간 HUD 표시 + } + + private void HandleScoreChanged(RhythmScore score) + { + if (_scoreText != null) _scoreText.text = $"SCORE {score.Score:N0}"; + if (_comboText != null) _comboText.text = score.Combo > 0 ? $"{score.Combo}" : ""; + } + + private void HandleJudged(Result result) + { + if (_judgeText != null) _judgeText.text = result.ToString(); + } + + private void HandleSongFinished(RhythmScore score) + { + if (_root != null) _root.SetActive(false); // 곡 끝나면 실시간 HUD는 숨김(결과창이 대신 뜸) + } +} diff --git a/Assets/02_Scripts/UI/RhythmScoreHud.cs.meta b/Assets/02_Scripts/UI/RhythmScoreHud.cs.meta new file mode 100644 index 00000000..124f2dd8 --- /dev/null +++ b/Assets/02_Scripts/UI/RhythmScoreHud.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2d2c41822c633294a986c6803818303d \ No newline at end of file