286 lines
9.1 KiB
C#
286 lines
9.1 KiB
C#
using UnityEngine;
|
|
using TMPro;
|
|
using System;
|
|
using UnityEngine.UI;
|
|
|
|
// Runs before default-order scripts so the per-track held-state table (trackHeld) is
|
|
// updated from keyboard/touch this frame *before* hold notes read it. This preserves the
|
|
// frame-accurate timing the old direct Input.GetKey polling had in HoldNote.
|
|
[DefaultExecutionOrder(-100)]
|
|
public class InputManager : MonoBehaviour
|
|
{
|
|
public static InputManager Instance { get; private set; }
|
|
public static event Action<KeyCode> OnKeyPressed;
|
|
public static event Action<KeyCode> OnKeyReleased;
|
|
|
|
private static readonly string[] TrackColors = { "red", "green", "yellow", "purple", "blue" };
|
|
|
|
[Header("Inspector")]
|
|
public TextMeshProUGUI[] trackJudgeTexts = new TextMeshProUGUI[5];
|
|
|
|
[Header("Inspector")]
|
|
public Text[] trackKeyTexts = new Text[5];
|
|
|
|
[Header("Lane Background Settings")]
|
|
[Tooltip("The sprites for the 5 lanes (Red, Green, Yellow, Purple, Blue)")]
|
|
public SpriteRenderer[] laneSprites = new SpriteRenderer[5];
|
|
[Tooltip("Alpha value when pressed (0-255)")]
|
|
public float pressedAlpha = 30f;
|
|
|
|
[Header("Inspector")]
|
|
public bool showJudgeText = true;
|
|
|
|
// Documentation text normalized.
|
|
[Header("Inspector")]
|
|
[Tooltip("Documentation text normalized.")]
|
|
public Color keyActiveColor = Color.yellow;
|
|
[Tooltip("Documentation text normalized.")]
|
|
public Color keyInactiveColor = Color.black;
|
|
|
|
// Documentation text normalized.
|
|
public Color perfectColor = Color.yellow;
|
|
public Color greatColor = Color.green;
|
|
public Color goodColor = Color.cyan;
|
|
public Color missColor = Color.red;
|
|
|
|
private KeyCode[] cachedKeys = new KeyCode[5];
|
|
private bool pauseBlockedLastFrame = false;
|
|
|
|
// Per-track held state. Written by both keyboard (Update) and touch (PressTrack/
|
|
// ReleaseTrack) so hold-note logic can query one platform-agnostic source instead
|
|
// of polling Input.GetKey directly (which cannot be driven by touch on Android).
|
|
private bool[] trackHeld = new bool[5];
|
|
|
|
/// <summary>
|
|
/// True while the given track (0-4) is currently held, regardless of whether the
|
|
/// source is keyboard or touch. Replaces direct Input.GetKey polling in hold notes.
|
|
/// </summary>
|
|
public bool IsTrackHeld(int trackIndex)
|
|
{
|
|
if (trackIndex < 0 || trackIndex >= trackHeld.Length) return false;
|
|
return trackHeld[trackIndex];
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance == null)
|
|
Instance = this;
|
|
else
|
|
Destroy(gameObject);
|
|
|
|
// Documentation text normalized.
|
|
RefreshKeyCache();
|
|
|
|
// Documentation text normalized.
|
|
if (trackKeyTexts != null)
|
|
{
|
|
foreach (var t in trackKeyTexts)
|
|
{
|
|
if (t != null) t.color = keyInactiveColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RefreshKeyCache()
|
|
{
|
|
for (int i = 0; i < TrackColors.Length; i++)
|
|
{
|
|
cachedKeys[i] = KeyBindingManager.GetKeyForColor(TrackColors[i]);
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
// Ensure runtime input and UI labels both use current bindings.
|
|
RefreshKeyCache();
|
|
RefreshKeyLabels();
|
|
|
|
// Initialize lane sprites to 0 alpha
|
|
if (laneSprites != null)
|
|
{
|
|
foreach (var sprite in laneSprites)
|
|
{
|
|
if (sprite != null) SetSpriteAlpha(sprite, 0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetSpriteAlpha(SpriteRenderer sprite, float alpha255)
|
|
{
|
|
if (sprite == null) return;
|
|
Color c = sprite.color;
|
|
c.a = Mathf.Clamp01(alpha255 / 255f);
|
|
sprite.color = c;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
bool pauseBlocked = IsBlockedByPause();
|
|
if (pauseBlocked)
|
|
{
|
|
if (!pauseBlockedLastFrame)
|
|
ForceReleaseAllKeys();
|
|
pauseBlockedLastFrame = true;
|
|
return;
|
|
}
|
|
pauseBlockedLastFrame = false;
|
|
|
|
for (int i = 0; i < TrackColors.Length; i++)
|
|
{
|
|
KeyCode key = cachedKeys[i];
|
|
if (key == KeyCode.None) continue;
|
|
|
|
if (Input.GetKeyDown(key))
|
|
PressTrack(i);
|
|
if (Input.GetKeyUp(key))
|
|
ReleaseTrack(i);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin holding a track (0-4). Called by keyboard Update on key-down and by touch
|
|
/// regions on pointer-down. Sets the held-state table, raises OnKeyPressed with the
|
|
/// track's bound key so event-driven tap notes keep working, and updates lane visuals.
|
|
/// </summary>
|
|
public void PressTrack(int index)
|
|
{
|
|
if (index < 0 || index >= TrackColors.Length) return;
|
|
if (trackHeld[index]) return; // already held; avoid duplicate press events
|
|
|
|
trackHeld[index] = true;
|
|
|
|
KeyCode key = cachedKeys[index];
|
|
if (key != KeyCode.None)
|
|
OnKeyPressed?.Invoke(key);
|
|
|
|
if (laneSprites != null && index < laneSprites.Length)
|
|
SetSpriteAlpha(laneSprites[index], pressedAlpha);
|
|
|
|
if (trackKeyTexts != null && index < trackKeyTexts.Length)
|
|
{
|
|
var txt = trackKeyTexts[index];
|
|
if (txt != null) txt.color = keyActiveColor;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Release a track (0-4). Called by keyboard Update on key-up and by touch regions
|
|
/// on pointer-up. Clears the held-state table, raises OnKeyReleased, and resets visuals.
|
|
/// </summary>
|
|
public void ReleaseTrack(int index)
|
|
{
|
|
if (index < 0 || index >= TrackColors.Length) return;
|
|
if (!trackHeld[index]) return; // not held; nothing to release
|
|
|
|
trackHeld[index] = false;
|
|
|
|
KeyCode key = cachedKeys[index];
|
|
if (key != KeyCode.None)
|
|
OnKeyReleased?.Invoke(key);
|
|
|
|
if (laneSprites != null && index < laneSprites.Length)
|
|
SetSpriteAlpha(laneSprites[index], 0f);
|
|
|
|
if (trackKeyTexts != null && index < trackKeyTexts.Length)
|
|
{
|
|
var txt = trackKeyTexts[index];
|
|
if (txt != null) txt.color = keyInactiveColor;
|
|
}
|
|
}
|
|
|
|
private bool IsBlockedByPause()
|
|
{
|
|
var pm = PauseManager.Instance;
|
|
return pm != null && pm.IsPaused;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force a release event for all bound keys and reset UI indicators.
|
|
/// Useful for external "clear input" operations where the system should treat
|
|
/// all keys as released regardless of physical state.
|
|
/// </summary>
|
|
public void ForceReleaseAllKeys()
|
|
{
|
|
for (int i = 0; i < TrackColors.Length; i++)
|
|
{
|
|
string color = TrackColors[i];
|
|
trackHeld[i] = false; // clear held state so hold notes don't see a stale press after pause/clear
|
|
KeyCode key = KeyBindingManager.GetKeyForColor(color);
|
|
if (key == KeyCode.None) continue;
|
|
try { OnKeyReleased?.Invoke(key); } catch { }
|
|
|
|
int index = GetIndexForColor(color);
|
|
if (laneSprites != null && index >= 0 && index < laneSprites.Length)
|
|
{
|
|
SetSpriteAlpha(laneSprites[index], 0f);
|
|
}
|
|
if (trackKeyTexts != null && index >= 0 && index < trackKeyTexts.Length)
|
|
{
|
|
var txt = trackKeyTexts[index];
|
|
if (txt != null) txt.color = keyInactiveColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Refresh displayed labels for key bindings (called after rebind)
|
|
public void RefreshKeyLabels()
|
|
{
|
|
if (trackKeyTexts != null)
|
|
{
|
|
for (int i = 0; i < TrackColors.Length && i < trackKeyTexts.Length; i++)
|
|
{
|
|
var txt = trackKeyTexts[i];
|
|
if (txt == null) continue;
|
|
KeyCode k = KeyBindingManager.GetKeyForColor(TrackColors[i]);
|
|
txt.text = KeyBindingManager.GetDisplayName(k);
|
|
}
|
|
}
|
|
|
|
// Keep runtime input cache in sync with latest bindings even when label UI is missing.
|
|
RefreshKeyCache();
|
|
}
|
|
|
|
private int GetIndexForColor(string color)
|
|
{
|
|
// Documentation text normalized.
|
|
switch (color)
|
|
{
|
|
case "red": return 0;
|
|
case "green": return 1;
|
|
case "yellow": return 2;
|
|
case "purple": return 3;
|
|
case "blue": return 4;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Documentation text normalized.
|
|
public void ShowJudgeResult(int trackIndex, string result)
|
|
{
|
|
if (!showJudgeText) return;
|
|
if (trackJudgeTexts == null || trackIndex < 0 || trackIndex >= trackJudgeTexts.Length) return;
|
|
var textObj = trackJudgeTexts[trackIndex];
|
|
if (textObj == null) return;
|
|
textObj.text = result;
|
|
switch (result)
|
|
{
|
|
case "Perfect":
|
|
textObj.color = perfectColor;
|
|
break;
|
|
case "Great":
|
|
textObj.color = greatColor;
|
|
break;
|
|
case "Good":
|
|
textObj.color = goodColor;
|
|
break;
|
|
case "Miss":
|
|
textObj.color = missColor;
|
|
break;
|
|
default:
|
|
textObj.color = Color.white;
|
|
break;
|
|
}
|
|
}
|
|
}
|