168 lines
5.8 KiB
C#
168 lines
5.8 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
|
|
public class NoteController : MonoBehaviour
|
|
{
|
|
private float speed;
|
|
private bool isMoving = true;
|
|
private bool isInJudgeZone = false; // Documentation text normalized.
|
|
private ParticleSystem hitEffect;
|
|
private Note linkedNote;
|
|
private bool useAbsolutePositioning = false;
|
|
|
|
// 去物理判定:用几何阈值复现原 Trigger 的进入/离开时刻。
|
|
private Collider2D _col;
|
|
private float _enterY, _exitY;
|
|
private bool _thresholdsReady;
|
|
|
|
// For absolute position tracking instead of relative translate
|
|
private Vector3 spawnPosition;
|
|
private float activationTime = -1f; // When the note should start moving (hitTime - travelTime)
|
|
private float baseSpawnYOffset = 0f; // Initial Y offset from compensation at spawn time
|
|
|
|
private void Awake()
|
|
{
|
|
hitEffect = GetComponentInChildren<ParticleSystem>();
|
|
linkedNote = GetComponent<Note>();
|
|
spawnPosition = transform.position;
|
|
_col = GetComponent<Collider2D>();
|
|
// 去物理判定:判定改为几何驱动,音符不再需要 Physics2D 模拟。
|
|
// 关闭 Rigidbody2D 模拟,消除每帧移动 kinematic 刚体触发的物理同步/重叠检测开销。
|
|
// 保留 Collider 组件本身(仅用于读取其 bounds 计算阈值,不参与物理)。
|
|
var rb = GetComponent<Rigidbody2D>();
|
|
if (rb != null) rb.simulated = false;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (isMoving && useAbsolutePositioning)
|
|
{
|
|
// Use absolute positioning based on elapsed time since activation
|
|
float elapsedSinceActivation = GameplayClock.NowSongTime - activationTime;
|
|
float travelDistance = speed * Mathf.Max(0f, elapsedSinceActivation);
|
|
|
|
// Position = spawnPoint + initial offset + downward travel
|
|
Vector3 newPos = spawnPosition;
|
|
newPos.y -= (baseSpawnYOffset + travelDistance);
|
|
transform.position = newPos;
|
|
}
|
|
|
|
// 去物理判定:用音符 Y 与阈值的比较复现 OnTriggerEnter2D/Exit2D 的进入/离开时刻。
|
|
UpdateJudgeZoneByGeometry();
|
|
}
|
|
|
|
private void UpdateJudgeZoneByGeometry()
|
|
{
|
|
if (linkedNote == null) return;
|
|
if (!_thresholdsReady)
|
|
{
|
|
// 判定线捕获后,用音符自身 collider 计算进入/离开阈值(只算一次)。
|
|
if (!JudgeZoneGeometry.LineReady) return;
|
|
if (!JudgeZoneGeometry.TryGetThresholds(_col, transform, out _enterY, out _exitY)) return;
|
|
_thresholdsReady = true;
|
|
}
|
|
|
|
float noteY = transform.position.y;
|
|
// 进入:noteY 下降到 ≤ enterY 且尚未进入。
|
|
if (!isInJudgeZone && noteY <= _enterY)
|
|
{
|
|
EnterJudgeZone();
|
|
// 单帧穿透(帧尖峰导致一帧内越过整个判定区):立即补一次离开,行为与物理一致。
|
|
if (noteY <= _exitY) ExitJudgeZone();
|
|
}
|
|
// 离开:noteY 继续下降到 ≤ exitY 且当前在区内。
|
|
else if (isInJudgeZone && noteY <= _exitY)
|
|
{
|
|
ExitJudgeZone();
|
|
}
|
|
}
|
|
|
|
public void SetSpeed(float newSpeed)
|
|
{
|
|
speed = newSpeed;
|
|
}
|
|
|
|
public void StopMovement()
|
|
{
|
|
isMoving = false;
|
|
}
|
|
|
|
public void PlayHitEffect()
|
|
{
|
|
if (hitEffect != null)
|
|
{
|
|
hitEffect.Play();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configure timing for absolute position tracking.
|
|
/// Call this from NoteSpawner after the note is spawned.
|
|
/// </summary>
|
|
public void ConfigureAbsolutePositioning(Vector3 initialSpawnPos, float hitTime, float travelTime, float initialYOffset)
|
|
{
|
|
spawnPosition = initialSpawnPos;
|
|
activationTime = hitTime - travelTime;
|
|
baseSpawnYOffset = initialYOffset;
|
|
useAbsolutePositioning = true;
|
|
transform.position = GetExpectedPosition(GameplayClock.NowSongTime);
|
|
|
|
if (JudgeManager.IsDebugEnabled)
|
|
Debug.Log($"[NoteController] Configured: spawnPos={spawnPosition}, activationTime={activationTime:F3}, initialYOffset={initialYOffset:F4}");
|
|
}
|
|
|
|
public Vector3 GetExpectedPosition(float time)
|
|
{
|
|
float elapsedSinceActivation = time - activationTime;
|
|
float travelDistance = speed * Mathf.Max(0f, elapsedSinceActivation);
|
|
Vector3 newPos = spawnPosition;
|
|
newPos.y -= (baseSpawnYOffset + travelDistance);
|
|
return newPos;
|
|
}
|
|
|
|
public void ResetState()
|
|
{
|
|
speed = 0f;
|
|
isMoving = true;
|
|
isInJudgeZone = false;
|
|
activationTime = -1f;
|
|
baseSpawnYOffset = 0f;
|
|
spawnPosition = transform.position;
|
|
useAbsolutePositioning = false;
|
|
_thresholdsReady = false; // 池化复用:下次重新计算阈值。
|
|
|
|
if (hitEffect != null)
|
|
{
|
|
hitEffect.Stop();
|
|
hitEffect.Clear();
|
|
}
|
|
}
|
|
|
|
// 进入判定区(原 OnTriggerEnter2D 逻辑,保持完全一致)。
|
|
private void EnterJudgeZone()
|
|
{
|
|
if (linkedNote == null) return;
|
|
isInJudgeZone = true;
|
|
// register note by instance id so TrackKeyManager can prioritize
|
|
// tap notes are short notes without hold
|
|
TrackKeyManager.Instance.RegisterKey(linkedNote.GetTrackIndex(), linkedNote.GetCachedInstanceId(), "tap", linkedNote.HitTime);
|
|
}
|
|
|
|
// 离开判定区(原 OnTriggerExit2D 逻辑,保持完全一致)。
|
|
private void ExitJudgeZone()
|
|
{
|
|
if (linkedNote == null) return;
|
|
isInJudgeZone = false;
|
|
TrackKeyManager.Instance.UnregisterKey(linkedNote.GetTrackIndex(), linkedNote.GetCachedInstanceId());
|
|
linkedNote.SetJudgeZone(isInJudgeZone);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Query Note whether it's in judge zone
|
|
/// </summary>
|
|
public bool IsInJudgeZone()
|
|
{
|
|
return isInJudgeZone;
|
|
}
|
|
}
|