using UnityEngine; // 오토플레이용 스틱 포즈 드라이버. // 매니저가 매 프레임 Drive(dt)를 호출하면 dt(타격까지 남은 시간)에 따라 회전을 세팅한다. // 모션: 대기 → (윈드업) 위로 들기 → (스트라이크) 내려치며 대기 자세보다 _strikeOvershoot 만큼 더 깊이 → // (팔로스루) 대기 자세로 부드럽게 복귀. // 노트는 타격 순간 파괴돼 그 뒤 dt가 끊기므로, 팔로스루만 Time.deltaTime으로 스틱이 자체 처리한다. public class RhythmStick : MonoBehaviour { [Tooltip("들어올릴 때 회전할 로컬 축 (예: 손목 꺾이는 축)")] [SerializeField] private Vector3 _raiseAxis = Vector3.right; [Tooltip("대기 자세에서 위로 들어올리는 각도(도)")] [SerializeField] private float _raiseAngle = 50f; [Tooltip("타격 시 대기 자세보다 더 깊이 내려가는 각도(도)")] [SerializeField] private float _strikeOvershoot = 10f; [Tooltip("타격 몇 초 전부터 들어올리기 시작하는지")] [SerializeField] private float _windupTime = 0.1f; [Tooltip("내려치는 데 걸리는 시간(초). _windupTime 보다 작아야 한다")] [SerializeField] private float _strikeTime = 0.04f; [Tooltip("타격 후 대기 자세로 되돌아오는 시간(초)")] [SerializeField] private float _recoverTime = 0.08f; private Quaternion _restRot; // 대기 자세 private Quaternion _raisedRot; // 들어올린 자세 private Quaternion _overshootRot; // 타격 시 더 깊이 내려간 자세 private bool _armed; // 내려치는 중 → 다음 프레임 타격 예정 private bool _recovering; // 타격 후 복귀 중 private float _recoverElapsed; private Quaternion _recoverFrom; // 복귀 시작 회전(스냅 방지) private void Awake() { _restRot = transform.localRotation; Vector3 axis = _raiseAxis.normalized; _raisedRot = _restRot * Quaternion.AngleAxis(_raiseAngle, axis); _overshootRot = _restRot * Quaternion.AngleAxis(-_strikeOvershoot, axis); // 반대 방향 = 더 내려감 } // dt = 다음 타격까지 남은 시간(초). 다가오는 노트가 없으면 +∞. public void Drive(float dt) { // 1) 임박한 스윙(윈드업~타격)이 최우선 if (dt > 0f && dt <= _windupTime) { _recovering = false; _armed = true; // 곧 타격함 if (dt > _strikeTime) { // 들어올리는 구간: dt가 _windupTime→_strikeTime 으로 줄며 rest→raised float u = Mathf.InverseLerp(_windupTime, _strikeTime, dt); transform.localRotation = Quaternion.Slerp(_restRot, _raisedRot, Mathf.SmoothStep(0f, 1f, u)); } else { // 내려치는 구간: dt가 _strikeTime→0 으로 줄며 raised→overshoot (dt=0에 가장 깊이) float s = Mathf.InverseLerp(_strikeTime, 0f, dt); transform.localRotation = Quaternion.Slerp(_raisedRot, _overshootRot, Mathf.SmoothStep(0f, 1f, s)); } return; } // 2) 방금 타격이 끝났다면(노트 파괴로 dt가 끊김) 팔로스루 시작 if (_armed) { _armed = false; _recovering = true; _recoverElapsed = 0f; _recoverFrom = transform.localRotation; // 현재 위치에서 복귀(스냅 방지) } // 3) 팔로스루: 현재 → 대기 자세로 부드럽게 if (_recovering) { _recoverElapsed += Time.deltaTime; float r = _recoverTime > 0f ? Mathf.Clamp01(_recoverElapsed / _recoverTime) : 1f; transform.localRotation = Quaternion.Slerp(_recoverFrom, _restRot, Mathf.SmoothStep(0f, 1f, r)); if (r >= 1f) _recovering = false; return; } // 4) 평상시 대기 자세 transform.localRotation = _restRot; } // 즉시 대기 자세로 되돌림 (오토플레이 종료 시 호출) public void ResetPose() { _armed = false; _recovering = false; transform.localRotation = _restRot; } }