using UnityEngine; using UnityEngine.InputSystem; /// /// Handles input events and dispatches them to the appropriate ITouchInputConsumer. /// Supports tap and hold/drag logic, with interactable delegation and debug logging. /// public class InputManager : MonoBehaviour { private static InputManager _instance; private static bool _isQuitting = false; public static InputManager Instance { get { if (_instance == null && Application.isPlaying && !_isQuitting) { _instance = FindAnyObjectByType(); if (_instance == null) { var go = new GameObject("InputManager"); _instance = go.AddComponent(); // DontDestroyOnLoad(go); } } return _instance; } } private PlayerInput playerInput; private InputAction tapMoveAction; private InputAction holdMoveAction; private InputAction positionAction; private ITouchInputConsumer defaultConsumer; private bool isHoldActive; void Awake() { _instance = this; // DontDestroyOnLoad(gameObject); playerInput = GetComponent(); if (playerInput == null) { Debug.LogError("[InputManager] InputManager requires a PlayerInput component attached to the same GameObject."); return; } tapMoveAction = playerInput.actions.FindAction("TapMove", false); holdMoveAction = playerInput.actions.FindAction("HoldMove", false); positionAction = playerInput.actions.FindAction("TouchPosition", false); } void OnEnable() { if (tapMoveAction != null) tapMoveAction.performed += OnTapMovePerformed; if (holdMoveAction != null) { holdMoveAction.performed += OnHoldMoveStarted; holdMoveAction.canceled += OnHoldMoveCanceled; } } void OnDisable() { if (tapMoveAction != null) tapMoveAction.performed -= OnTapMovePerformed; if (holdMoveAction != null) { holdMoveAction.performed -= OnHoldMoveStarted; holdMoveAction.canceled -= OnHoldMoveCanceled; } } void OnApplicationQuit() { _isQuitting = true; } /// /// Sets the default ITouchInputConsumer to receive input events. /// public void SetDefaultConsumer(ITouchInputConsumer consumer) { defaultConsumer = consumer; } /// /// Handles tap input, delegates to interactable if present, otherwise to default consumer. /// private void OnTapMovePerformed(InputAction.CallbackContext ctx) { Vector2 screenPos = positionAction.ReadValue(); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); Debug.Log($"[InputManager] TapMove performed at {worldPos2D}"); if (!TryDelegateToInteractable(worldPos2D)) { Debug.Log("[InputManager] No interactable found, forwarding tap to default consumer"); defaultConsumer?.OnTap(worldPos2D); } else { Debug.Log("[InputManager] Tap delegated to interactable"); } } /// /// Handles the start of a hold input. /// private void OnHoldMoveStarted(InputAction.CallbackContext ctx) { isHoldActive = true; Vector2 screenPos = positionAction.ReadValue(); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); Debug.Log($"[InputManager] HoldMove started at {worldPos2D}"); defaultConsumer?.OnHoldStart(worldPos2D); } /// /// Handles the end of a hold input. /// private void OnHoldMoveCanceled(InputAction.CallbackContext ctx) { if (!isHoldActive) return; isHoldActive = false; Vector2 screenPos = positionAction.ReadValue(); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); Debug.Log($"[InputManager] HoldMove canceled at {worldPos2D}"); defaultConsumer?.OnHoldEnd(worldPos2D); } /// /// Continuously updates hold move input while active. /// void Update() { if (isHoldActive && holdMoveAction != null && holdMoveAction.phase == InputActionPhase.Performed) { Vector2 screenPos = positionAction.ReadValue(); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); // Debug.Log($"[InputManager] HoldMove update at {worldPos2D}"); defaultConsumer?.OnHoldMove(worldPos2D); } } /// /// Attempts to delegate a tap to an interactable at the given world position. /// Traces on the "Interactable" channel and logs detailed info. /// private bool TryDelegateToInteractable(Vector2 worldPos) { LayerMask mask = GameManager.Instance != null ? GameManager.Instance.InteractableLayerMask : -1; Collider2D hit = Physics2D.OverlapPoint(worldPos, mask); if (hit != null) { var consumer = hit.GetComponent(); if (consumer != null) { Debug.unityLogger.Log("Interactable", $"[InputManager] Delegating tap to consumer at {worldPos} (GameObject: {hit.gameObject.name})"); consumer.OnTap(worldPos); return true; } Debug.unityLogger.Log("Interactable", $"[InputManager] Collider2D hit at {worldPos} (GameObject: {hit.gameObject.name}), but no ITouchInputConsumer found."); } else { Debug.unityLogger.Log("Interactable", $"[InputManager] No Collider2D found at {worldPos} for interactable delegation."); } return false; } }