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 OnKeyPressed; public static event Action 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]; /// /// 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. /// 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); } } /// /// 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. /// 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; } } /// /// 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. /// 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; } /// /// 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. /// 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; } } /// /// 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; } } }