using System.Collections; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using OctoRef = OctoCore.OctoCore; namespace Unheimlich.Navigation.Debuging { /// /// Listens for debug input combos and drives DebugNavigation. /// /// — Gamepad : L2+R2 held 0.5s → unlock/lock. L2/R2 short press → prev/next (menu must be open). /// — Keyboard : backquote (² AZERTY / ~ QWERTY) → unlock/lock. ←/→ → prev/next. /// /// Device display: switches based on last active input, not physical connection. /// public class DebugMenuUnlocker : MonoBehaviour { [Header("Data")] [SerializeField] private DebugNavigation _debugNavigation; [Header("Timing")] [Tooltip("Duration in seconds L2+R2 must be held to toggle the debug menu.")] [SerializeField] private float _holdDuration = 0.5f; [Header("Unlock Events")] public UnityEvent OnDebugMenuUnlockedGamepad; public UnityEvent OnDebugMenuUnlockedKeyboard; public UnityEvent OnDebugMenuLocked; [Header("Navigation Events")] public UnityEvent OnPreviousScene; public UnityEvent OnNextScene; [Header("Device Switch Events")] [Tooltip("Fired when gamepad input is detected and last input was keyboard/mouse.")] public UnityEvent OnSwitchedToGamepad; [Tooltip("Fired when keyboard/mouse input is detected and last input was gamepad.")] public UnityEvent OnSwitchedToKeyboard; // ── State ──────────────────────────────────────────────────────────── private bool _isUnlocked = false; private bool _isL2Held = false; private bool _isR2Held = false; private float _holdTimer = 0f; private bool _holdFired = false; private bool _lastWasGamepad = false; // Input Actions private InputAction _keyboardUnlockAction; private InputAction _gamepadL2Action; private InputAction _gamepadR2Action; private InputAction _keyboardPreviousAction; private InputAction _keyboardNextAction; private InputAction _anyGamepadAction; private InputAction _anyKeyboardAction; // ── Unity lifecycle ────────────────────────────────────────────────── private void Awake() => BuildInputActions(); private void OnEnable() => EnableActions(); private void Start() { StartCoroutine(InitWhenReady()); } private IEnumerator InitWhenReady() { yield return new WaitUntil(() => OctoRef.Instance != null); yield return new WaitUntil(() => OctoRef.Instance.Navigation.CurrentScene != null); SyncDebugNavigation(); SubscribeToSceneEvents(); } private void OnDisable() { DisableActions(); UnsubscribeFromSceneEvents(); } private void OnDestroy() => DisposeActions(); private void Update() => TickHoldTimer(); // ── Build ──────────────────────────────────────────────────────────── private void BuildInputActions() { // Keyboard unlock toggle _keyboardUnlockAction = new InputAction("DebugUnlockKeyboard", InputActionType.Button); _keyboardUnlockAction.AddBinding("/backquote"); _keyboardUnlockAction.performed += _ => ToggleDebugMenu(false); // Gamepad L2 / R2 _gamepadL2Action = new InputAction("DebugL2", InputActionType.Button); _gamepadL2Action.AddBinding("/leftTrigger") .WithProcessor("AxisDeadzone(min=0.5,max=1)"); _gamepadL2Action.performed += _ => OnL2Down(); _gamepadL2Action.canceled += _ => OnL2Up(); _gamepadR2Action = new InputAction("DebugR2", InputActionType.Button); _gamepadR2Action.AddBinding("/rightTrigger") .WithProcessor("AxisDeadzone(min=0.5,max=1)"); _gamepadR2Action.performed += _ => OnR2Down(); _gamepadR2Action.canceled += _ => OnR2Up(); // Keyboard navigation _keyboardPreviousAction = new InputAction("DebugPrevKeyboard", InputActionType.Button); _keyboardPreviousAction.AddBinding("/leftArrow"); _keyboardPreviousAction.performed += _ => TryPrevious(); _keyboardNextAction = new InputAction("DebugNextKeyboard", InputActionType.Button); _keyboardNextAction.AddBinding("/rightArrow"); _keyboardNextAction.performed += _ => TryNext(); // Device detection — wildcard bindings _anyGamepadAction = new InputAction("AnyGamepad", InputActionType.PassThrough); _anyGamepadAction.AddBinding("/leftStick"); _anyGamepadAction.AddBinding("/rightStick"); _anyGamepadAction.AddBinding("/buttonSouth"); _anyGamepadAction.AddBinding("/buttonNorth"); _anyGamepadAction.AddBinding("/buttonEast"); _anyGamepadAction.AddBinding("/buttonWest"); _anyGamepadAction.AddBinding("/leftShoulder"); _anyGamepadAction.AddBinding("/rightShoulder"); _anyGamepadAction.AddBinding("/start"); _anyGamepadAction.AddBinding("/select"); _anyGamepadAction.AddBinding("/dpad"); _anyGamepadAction.performed += _ => OnAnyGamepadInput(); _anyKeyboardAction = new InputAction("AnyKeyboard", InputActionType.PassThrough); _anyKeyboardAction.AddBinding("/anyKey"); _anyKeyboardAction.AddBinding("/delta"); _anyKeyboardAction.AddBinding("/leftButton"); _anyKeyboardAction.AddBinding("/rightButton"); _anyKeyboardAction.performed += _ => OnAnyKeyboardInput(); } private void EnableActions() { _keyboardUnlockAction?.Enable(); _gamepadL2Action?.Enable(); _gamepadR2Action?.Enable(); _keyboardPreviousAction?.Enable(); _keyboardNextAction?.Enable(); _anyGamepadAction?.Enable(); _anyKeyboardAction?.Enable(); } private void DisableActions() { _keyboardUnlockAction?.Disable(); _gamepadL2Action?.Disable(); _gamepadR2Action?.Disable(); _keyboardPreviousAction?.Disable(); _keyboardNextAction?.Disable(); _anyGamepadAction?.Disable(); _anyKeyboardAction?.Disable(); } private void DisposeActions() { _keyboardUnlockAction?.Dispose(); _gamepadL2Action?.Dispose(); _gamepadR2Action?.Dispose(); _keyboardPreviousAction?.Dispose(); _keyboardNextAction?.Dispose(); _anyGamepadAction?.Dispose(); _anyKeyboardAction?.Dispose(); } // ── L2 / R2 hold logic ─────────────────────────────────────────────── private void OnL2Down() { _isL2Held = true; if (_isR2Held) StartHold(); } private void OnL2Up() { // Short press navigation — only if hold didn't fire if (_isUnlocked && !_holdFired && !_isR2Held) TryPrevious(); _isL2Held = false; // Reset hold only when both triggers are released if (!_isR2Held) ResetHold(); } private void OnR2Down() { _isR2Held = true; if (_isL2Held) StartHold(); } private void OnR2Up() { // Short press navigation — only if hold didn't fire if (_isUnlocked && !_holdFired && !_isL2Held) TryNext(); _isR2Held = false; // Reset hold only when both triggers are released if (!_isL2Held) ResetHold(); } private void StartHold() { _holdTimer = 0f; _holdFired = false; } private void ResetHold() { _holdTimer = 0f; _holdFired = false; } private void TickHoldTimer() { if (!_isL2Held || !_isR2Held || _holdFired) return; _holdTimer += Time.unscaledDeltaTime; if (_holdTimer >= _holdDuration) { _holdFired = true; ToggleDebugMenu(true); } } // ── Device switch ──────────────────────────────────────────────────── private void OnAnyGamepadInput() { if (_lastWasGamepad) return; _lastWasGamepad = true; OnSwitchedToGamepad?.Invoke(); } private void OnAnyKeyboardInput() { if (!_lastWasGamepad) return; _lastWasGamepad = false; OnSwitchedToKeyboard?.Invoke(); } // ── Logic ──────────────────────────────────────────────────────────── private void SubscribeToSceneEvents() { if (OctoRef.Instance == null) return; OctoRef.Instance.Navigation.OnSceneLoadCompleted += OnSceneLoadCompleted; } private void UnsubscribeFromSceneEvents() { if (OctoRef.Instance == null) return; OctoRef.Instance.Navigation.OnSceneLoadCompleted -= OnSceneLoadCompleted; } private void OnSceneLoadCompleted(OctoCore.Navigation.Data.OctoSceneData sceneData) { _debugNavigation?.SyncWithCurrentScene(sceneData); } private void SyncDebugNavigation() { if (_debugNavigation == null || OctoRef.Instance == null) return; _debugNavigation.SyncWithCurrentScene(OctoRef.Instance.Navigation.CurrentScene); } private void ToggleDebugMenu(bool isGamepad) { _isUnlocked = !_isUnlocked; if (_isUnlocked) { SyncDebugNavigation(); if (isGamepad) OnDebugMenuUnlockedGamepad?.Invoke(); else OnDebugMenuUnlockedKeyboard?.Invoke(); Debug.Log($"[DebugMenuUnlocker] Unlocked ({(isGamepad ? "Gamepad" : "Keyboard")})."); } else { OnDebugMenuLocked?.Invoke(); Debug.Log("[DebugMenuUnlocker] Locked."); } } private void TryPrevious() { if (!_isUnlocked || _debugNavigation == null) return; OnPreviousScene?.Invoke(); _debugNavigation.Previous(); } private void TryNext() { if (!_isUnlocked || _debugNavigation == null) return; OnNextScene?.Invoke(); _debugNavigation.Next(); } /// Called directly by Debug Menu UI buttons (0-based index). public void LoadSceneByIndex(int index) { if (_debugNavigation == null) return; _debugNavigation.LoadByIndex(index); } public bool IsUnlocked => _isUnlocked; } }