Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/NoteController.cs
T
2026-07-22 05:35:53 +08:00

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;
}
}