feat: debug scene navigation system

This commit is contained in:
Ferlat Thibaud
2026-06-02 12:53:44 +02:00
parent 5f3a527cc5
commit dec1bed017
25 changed files with 7099 additions and 2055 deletions
@@ -0,0 +1,21 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9089d8b7e41057e4cac3aad004af4b93, type: 3}
m_Name: debug_navigation
m_EditorClassIdentifier: Assembly-CSharp::Unheimlich.Navigation.Debug.DebugNavigation
_scenes:
- {fileID: 11400000, guid: a996cfbfd3cdcf94fbcc944840699825, type: 2}
- {fileID: 11400000, guid: b092f756642b19043bec9bab913906f9, type: 2}
- {fileID: 11400000, guid: 95381629b7e563a45b923c8eca96a142, type: 2}
- {fileID: 11400000, guid: eef5b93bc2c31ae48ac01a27f20884d5, type: 2}
- {fileID: 11400000, guid: 23d4f22660e26344b8e2e2488c560c4c, type: 2}
- {fileID: 11400000, guid: ecd5a6faaacb2244fa810987501a9b0b, type: 2}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f20090b5a50ef084fa449b094cd9fa09
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,17 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a1f04313388f6a846a9e2a6bfe6e985a, type: 3}
m_Name: scene_Omnissiah
m_EditorClassIdentifier: OctoCore.Runtime::OctoCore.Navigation.Data.OctoSceneData
Id: scene_Omnissiah
SceneName: Omnissiah
UseFadeTransition: 1
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b092f756642b19043bec9bab913906f9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,17 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a1f04313388f6a846a9e2a6bfe6e985a, type: 3}
m_Name: scene_titleScreen
m_EditorClassIdentifier: OctoCore.Runtime::OctoCore.Navigation.Data.OctoSceneData
Id: scene_titleScreen
SceneName: TitleScreen
UseFadeTransition: 1
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b09fa30cf6bafad46a041153b0bdb17e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+3 -3
View File
@@ -2342,7 +2342,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: => To the next scene
m_text: To the next scene =>
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
@@ -20848,7 +20848,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
m_IsActive: 1
--- !u!224 &1962392721
RectTransform:
m_ObjectHideFlags: 0
@@ -21173,7 +21173,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: 'R2 to the next scene '
m_text: To the next scene R2
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
+4 -111
View File
@@ -307,7 +307,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.x
value: 240.00002
value: 240
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -1293,7 +1293,6 @@ RectTransform:
- {fileID: 1869488458}
- {fileID: 1841450551}
- {fileID: 1903583299}
- {fileID: 1880389255}
m_Father: {fileID: 2109208143}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@@ -1982,7 +1981,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.x
value: 720.00006
value: 960
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -2229,7 +2228,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.x
value: 240.00002
value: 320
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -2261,112 +2260,6 @@ RectTransform:
m_CorrespondingSourceObject: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
m_PrefabInstance: {fileID: 1869488457}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &1880389254
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 1354814231}
m_Modifications:
- target: {fileID: 148381449121762492, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_Name
value: Button Variant (3)
objectReference: {fileID: 0}
- target: {fileID: 710399409615174608, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_text
value: 8
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_Pivot.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_Pivot.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchorMax.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchorMin.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_SizeDelta.x
value: 200
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_SizeDelta.y
value: 200
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalRotation.x
value: -0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalRotation.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalRotation.z
value: -0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.x
value: 1680.0001
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.y
value: -690
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
--- !u!224 &1880389255 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
m_PrefabInstance: {fileID: 1880389254}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &1903583298
PrefabInstance:
m_ObjectHideFlags: 0
@@ -2449,7 +2342,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.x
value: 1200.0001
value: 1600
objectReference: {fileID: 0}
- target: {fileID: 5856627074613423090, guid: 10b589291a51b4a4f858c6c79ab6772a, type: 3}
propertyPath: m_AnchoredPosition.y
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0412faee34f1f1e4a9163b338262c618
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: de5e8b6dd52514441b1115e22ce12f85
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using OctoCore.Navigation.Data;
using OctoRef = OctoCore.OctoCore;
namespace Unheimlich.Navigation.Debuging
{
/// <summary>
/// ScriptableObject holding the ordered list of debug scenes.
/// Drag OctoSceneData assets in the desired order in the inspector.
/// </summary>
[CreateAssetMenu(fileName = "DebugNavigation", menuName = "Unheimlich/Debug/Debug Navigation")]
public class DebugNavigation : ScriptableObject
{
[Tooltip("Ordered list of scenes available in debug mode.")]
[SerializeField] private List<OctoSceneData> _scenes = new List<OctoSceneData>();
public IReadOnlyList<OctoSceneData> Scenes => _scenes;
/// <summary>Invoked when a debug scene change is requested.</summary>
public Action<OctoSceneData> OnDebugSceneRequested;
private int _currentIndex = -1;
public int CurrentIndex => _currentIndex;
/// <summary>
/// Sync the current index with the active scene.
/// If the active scene is not in the list, _currentIndex stays at -1
/// so Next() will correctly start at index 0.
/// </summary>
public void SyncWithCurrentScene(OctoSceneData currentScene)
{
_currentIndex = currentScene != null ? _scenes.IndexOf(currentScene) : -1;
if (_currentIndex == -1)
UnityEngine.Debug.Log("[DebugNavigation] Scene not found in debug list — Next() will start at index 0.");
}
/// <summary>Load the previous scene in the list (wraps).</summary>
public void Previous()
{
if (_scenes == null || _scenes.Count == 0) return;
// If not in list, go to last
_currentIndex = (_currentIndex <= 0) ? _scenes.Count - 1 : _currentIndex - 1;
RequestScene(_currentIndex);
}
/// <summary>Load the next scene in the list (wraps).</summary>
public void Next()
{
if (_scenes == null || _scenes.Count == 0) return;
// If not in list (-1), go to 0. Otherwise increment.
_currentIndex = (_currentIndex >= _scenes.Count - 1) ? 0 : _currentIndex + 1;
RequestScene(_currentIndex);
}
/// <summary>Load a scene by index directly (called by Debug Menu UI buttons).</summary>
public void LoadByIndex(int index)
{
if (index < 0 || index >= _scenes.Count)
{
UnityEngine.Debug.LogWarning($"[DebugNavigation] Index {index} out of range.");
return;
}
_currentIndex = index;
RequestScene(_currentIndex);
}
private void RequestScene(int index)
{
OctoSceneData scene = _scenes[index];
if (scene == null)
{
UnityEngine.Debug.LogWarning($"[DebugNavigation] Scene at index {index} is null.");
return;
}
// Don't reload the scene that's already active
if (OctoRef.Instance != null &&
OctoRef.Instance.Navigation.CurrentScene == scene)
{
UnityEngine.Debug.Log($"[DebugNavigation] Scene '{scene.SceneName}' is already loaded, skipping.");
return;
}
OnDebugSceneRequested?.Invoke(scene);
if (OctoRef.Instance)
OctoRef.Instance.Navigation.LoadScene(scene);
else
UnityEngine.Debug.LogError("[DebugNavigation] OctoCore instance not found.");
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9089d8b7e41057e4cac3aad004af4b93
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 767e525b8e268ce40b879a01f27987aa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,320 @@
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using OctoRef = OctoCore.OctoCore;
namespace Unheimlich.Navigation.Debuging
{
/// <summary>
/// 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.
/// </summary>
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("<Keyboard>/backquote");
_keyboardUnlockAction.performed += _ => ToggleDebugMenu(false);
// Gamepad L2 / R2
_gamepadL2Action = new InputAction("DebugL2", InputActionType.Button);
_gamepadL2Action.AddBinding("<Gamepad>/leftTrigger")
.WithProcessor("AxisDeadzone(min=0.5,max=1)");
_gamepadL2Action.performed += _ => OnL2Down();
_gamepadL2Action.canceled += _ => OnL2Up();
_gamepadR2Action = new InputAction("DebugR2", InputActionType.Button);
_gamepadR2Action.AddBinding("<Gamepad>/rightTrigger")
.WithProcessor("AxisDeadzone(min=0.5,max=1)");
_gamepadR2Action.performed += _ => OnR2Down();
_gamepadR2Action.canceled += _ => OnR2Up();
// Keyboard navigation
_keyboardPreviousAction = new InputAction("DebugPrevKeyboard", InputActionType.Button);
_keyboardPreviousAction.AddBinding("<Keyboard>/leftArrow");
_keyboardPreviousAction.performed += _ => TryPrevious();
_keyboardNextAction = new InputAction("DebugNextKeyboard", InputActionType.Button);
_keyboardNextAction.AddBinding("<Keyboard>/rightArrow");
_keyboardNextAction.performed += _ => TryNext();
// Device detection — wildcard bindings
// Note: on exclut les triggers du anyGamepad pour éviter les faux positifs
// lors du relâchement après un hold unlock
_anyGamepadAction = new InputAction("AnyGamepad", InputActionType.PassThrough);
_anyGamepadAction.AddBinding("<Gamepad>/leftStick");
_anyGamepadAction.AddBinding("<Gamepad>/rightStick");
_anyGamepadAction.AddBinding("<Gamepad>/buttonSouth");
_anyGamepadAction.AddBinding("<Gamepad>/buttonNorth");
_anyGamepadAction.AddBinding("<Gamepad>/buttonEast");
_anyGamepadAction.AddBinding("<Gamepad>/buttonWest");
_anyGamepadAction.AddBinding("<Gamepad>/leftShoulder");
_anyGamepadAction.AddBinding("<Gamepad>/rightShoulder");
_anyGamepadAction.AddBinding("<Gamepad>/start");
_anyGamepadAction.AddBinding("<Gamepad>/select");
_anyGamepadAction.AddBinding("<Gamepad>/dpad");
_anyGamepadAction.performed += _ => OnAnyGamepadInput();
_anyKeyboardAction = new InputAction("AnyKeyboard", InputActionType.PassThrough);
_anyKeyboardAction.AddBinding("<Keyboard>/anyKey");
_anyKeyboardAction.AddBinding("<Mouse>/delta");
_anyKeyboardAction.AddBinding("<Mouse>/leftButton");
_anyKeyboardAction.AddBinding("<Mouse>/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; // marque comme fired AVANT le toggle pour bloquer OnR2Up/OnL2Up
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();
UnityEngine.Debug.Log($"[DebugMenuUnlocker] Unlocked ({(isGamepad ? "Gamepad" : "Keyboard")}).");
}
else
{
OnDebugMenuLocked?.Invoke();
UnityEngine.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();
}
/// <summary>Called directly by Debug Menu UI buttons (0-based index).</summary>
public void LoadSceneByIndex(int index)
{
if (_debugNavigation == null) return;
_debugNavigation.LoadByIndex(index);
}
public bool IsUnlocked => _isUnlocked;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e578ffb82b0e1c74bbb3a7e914b5b305