130 lines
3.9 KiB
C#
130 lines
3.9 KiB
C#
using UnityEngine;
|
|
using System;
|
|
|
|
public class HoldNoteController : MonoBehaviour
|
|
{
|
|
private bool isInsideJudgeZone = false;
|
|
public event Action<bool> OnJudgeZoneChanged;
|
|
private bool isMoving = false;
|
|
private float speed = 0f;
|
|
private float activationTime;
|
|
|
|
// Absolute positioning for hold segments (keeps segments connected under frame spikes)
|
|
private bool useAbsolutePositioning = false;
|
|
private Vector3 spawnPosition;
|
|
private float baseSpawnYOffset = 0f; // can be negative to offset upward for delayed segments
|
|
private float visualOffset = 0f; // additional Y offset for visual compensation
|
|
|
|
private void Update()
|
|
{
|
|
if (useAbsolutePositioning)
|
|
{
|
|
if (!isMoving) return;
|
|
ApplyAbsolutePosition(GameplayClock.NowSongTime);
|
|
return;
|
|
}
|
|
|
|
// Start moving after activation time in legacy mode
|
|
if (!isMoving && GameplayClock.NowSongTime >= activationTime)
|
|
{
|
|
isMoving = true;
|
|
}
|
|
|
|
if (isMoving)
|
|
{
|
|
transform.Translate(Vector3.down * speed * Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configure legacy timing (per-segment activation).
|
|
/// </summary>
|
|
public void ConfigureTiming(float baseHitTime, float segmentDelay, float travelTime)
|
|
{
|
|
float hitTime = baseHitTime + segmentDelay;
|
|
activationTime = hitTime - travelTime;
|
|
isMoving = false;
|
|
useAbsolutePositioning = false;
|
|
visualOffset = 0f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Legacy delay setter (not recommended).
|
|
/// </summary>
|
|
public void SetSegmentDelay(float segmentDelay)
|
|
{
|
|
activationTime = GameplayClock.NowSongTime + segmentDelay;
|
|
isMoving = false;
|
|
useAbsolutePositioning = false;
|
|
visualOffset = 0f;
|
|
}
|
|
|
|
public void SetSpeed(float newSpeed)
|
|
{
|
|
speed = newSpeed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configure absolute positioning for hold segments so they stay connected.
|
|
/// spawnPoint should be the original spawn point for the lane.
|
|
/// baseSpawnYOffset is applied to keep delayed segments offset upward (can be negative).
|
|
/// </summary>
|
|
public void ConfigureAbsolutePositioning(Vector3 spawnPoint, float newActivationTime, float spawnYOffset, float newVisualOffset = 0f)
|
|
{
|
|
spawnPosition = spawnPoint;
|
|
activationTime = newActivationTime;
|
|
baseSpawnYOffset = spawnYOffset;
|
|
visualOffset = newVisualOffset;
|
|
useAbsolutePositioning = true;
|
|
isMoving = true;
|
|
|
|
// Snap immediately to the expected position to avoid a 1-frame pop.
|
|
ApplyAbsolutePosition(GameplayClock.NowSongTime);
|
|
}
|
|
|
|
public void StopMovement()
|
|
{
|
|
isMoving = false;
|
|
visualOffset = 0f;
|
|
}
|
|
|
|
private void OnTriggerEnter2D(Collider2D collision)
|
|
{
|
|
if (!collision.CompareTag("JudgmentLine")) return;
|
|
|
|
isInsideJudgeZone = true;
|
|
OnJudgeZoneChanged?.Invoke(true);
|
|
}
|
|
|
|
private void OnTriggerExit2D(Collider2D collision)
|
|
{
|
|
if (!collision.CompareTag("JudgmentLine")) return;
|
|
|
|
isInsideJudgeZone = false;
|
|
OnJudgeZoneChanged?.Invoke(false);
|
|
}
|
|
|
|
public bool IsInsideJudgArea()
|
|
{
|
|
return isInsideJudgeZone;
|
|
}
|
|
|
|
public float ActivationTime => activationTime;
|
|
public float CurrentSpeed => speed;
|
|
public bool UsesAbsolutePositioning => useAbsolutePositioning;
|
|
public float BaseSpawnYOffset => baseSpawnYOffset;
|
|
public Vector3 SpawnPosition => spawnPosition;
|
|
public float VisualOffset => visualOffset;
|
|
|
|
private void ApplyAbsolutePosition(float time)
|
|
{
|
|
float elapsed = time - activationTime;
|
|
float travelDistance = speed * elapsed;
|
|
|
|
Vector3 newPos = spawnPosition;
|
|
// Apply visual offset (positive moves the note down its path earlier)
|
|
newPos.y -= (baseSpawnYOffset + travelDistance + visualOffset);
|
|
transform.position = newPos;
|
|
}
|
|
}
|