2026-06-15 리듬게임 프로토타입

This commit is contained in:
skrwns304@gmail.com
2026-06-15 15:29:54 +09:00
parent 19c26533f8
commit 6fe34d8eec
23 changed files with 265 additions and 26 deletions

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Hovl;
using UnityEngine;
using UnityEngine.InputSystem;
@@ -10,20 +9,26 @@ public class RhythmManager : MonoBehaviour
[SerializeField] private AudioSource _audioSource;
[SerializeField] private RhythmChart _currentChart;
[SerializeField] private RhythmNoteSpawner _spawner;
[SerializeField] private float _leadTime = 2f; // 노트가 생성돼 판정선까지 흐르는 시간(초)
[SerializeField] private float _leadTime = 1f; // 노트가 생성돼 판정선까지 흐르는 시간(초)
[Header("판정 윈도우 (초, 절대 시간차 기준)")]
[SerializeField] private float _perfectWindow = 0.05f;
[SerializeField] private float _goodWindow = 0.10f;
[SerializeField] private float _badWindow = 0.15f; // 이 밖이면 입력 무시(헛침)
[Header("효과음")]
[SerializeField] private AudioClip _hitSfx; // 내려칠 때마다 재생 (판정과 무관, 헛침 포함)
[SerializeField] private GameObject StartButtonObj;
public float SongTime => _audioSource.time; // 모든 타이밍의 기준
// 모든 타이밍의 기준. 오디오 클럭(dspTime) 기반이라 리드인 동안 음수(-leadTime→0)로 흐른다
public float SongTime => (float)(AudioSettings.dspTime - _dspSongStart);
private List<Note> SongNoteList;
private int _nextNoteIndex; // 다음에 소환할 노트 인덱스
private bool _isPlaying; //곡이 재생 중인지 (종료 감지용)
private double _dspSongStart; // 오디오가 실제 시작되는 dsp 시각 (= SongTime 0 지점)
private float _clipLength; // 곡 길이 캐시 (종료 감지용)
private Func<float> _songTimeProvider; // 노트에 넘길 시간 제공자 (매 스폰마다 람다 재생성 방지용 캐시)
private readonly List<RhythmNoteInstance> _activeNotes = new(); // 화면에 떠 있는(아직 처리 안 된) 노트들
@@ -43,24 +48,23 @@ private void Awake()
private void Start()
{
ChangeSong();
InputManager.Instance.OnKey_Left_Event += OnPlayerInput;
InputManager.Instance.OnKey_Right_Event += OnPlayerInput;
}
private void Update()
{
//재생 중이던 곡이 끝나면 주변음 복구
if (_isPlaying && !_audioSource.isPlaying)
if (!_isPlaying) return;
//곡이 끝나면(리드인 지나 곡 길이 도달) 정지·주변음 복구
if (SongTime >= _clipLength)
{
StopSong();
return;
}
if (!_isPlaying) return;
SpawnDueNotes();
// 테스트용: 스페이스로 판정 (실제 VR에서는 컨트롤러/콜라이더 입력으로 교체)
if (Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame)
OnPlayerInput();
}
// SongTime 기준으로 소환할 때가 된 노트들을 순서대로 생성
@@ -95,7 +99,13 @@ public void StartSong()
OnScoreChanged?.Invoke(Score); // HUD 초기화(0점)
StartButtonObj.SetActive(false);
_audioSource.Play();
_clipLength = _audioSource.clip != null ? _audioSource.clip.length : 0f;
// 리드인: 지금부터 _leadTime 뒤에 오디오 시작.
// 그 사이 SongTime이 -_leadTime→0으로 흘러 첫 노트(0초 부근)도 충분히 날아온다
_dspSongStart = AudioSettings.dspTime + _leadTime;
_audioSource.PlayScheduled(_dspSongStart);
_isPlaying = true;
//BGM·환경음을 낮춰 리듬게임 소리만 들리게
@@ -125,7 +135,13 @@ private void OnNoteMissed(RhythmNoteInstance note)
public void OnPlayerInput()
{
if (!_isPlaying || _activeNotes.Count == 0) return;
if (!_isPlaying) return;
// 판정과 무관하게 내려칠 때마다 효과음 (헛침 포함)
if (_hitSfx != null && SoundManager.Instance != null)
SoundManager.Instance.PlaySFX(_hitSfx);
if (_activeNotes.Count == 0) return;
// 판정선에 가장 가까운(시간차 최소) 노트 탐색
RhythmNoteInstance target = null;

View File

@@ -11,6 +11,10 @@ public class InputManager : MonoBehaviour, GameInput.IPlayerActions
// ─── 입력 이벤트들 (PlayerController 등이 구독) ──────────────────────
public event Action OnJump_Event; // 한 번씩 (눌렀을 때)
//키보드로 테스트용
public event Action OnKey_Left_Event;
public event Action OnKey_Right_Event;
private void Awake()
{
@@ -39,4 +43,16 @@ public void OnJump(InputAction.CallbackContext ctx)
if (ctx.phase == InputActionPhase.Started)
OnJump_Event?.Invoke();
}
public void OnKey_Left(InputAction.CallbackContext ctx)
{
if (ctx.phase == InputActionPhase.Started)
OnKey_Left_Event?.Invoke();
}
public void OnKey_Right(InputAction.CallbackContext ctx)
{
if (ctx.phase == InputActionPhase.Started)
OnKey_Right_Event?.Invoke();
}
}