using System; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using AppleHills.Core.Settings; // Added for IInteractionSettings namespace Input { public enum InputMode { Game, UI, GameAndUI, InputDisabled } /// /// 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 const string UiActions = "UI"; private const string GameActions = "PlayerTouch"; 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; } } // Settings reference private IInteractionSettings _interactionSettings; private PlayerInput playerInput; private InputAction tapMoveAction; private InputAction holdMoveAction; private InputAction positionAction; private ITouchInputConsumer defaultConsumer; private bool isHoldActive; void Awake() { _instance = this; // DontDestroyOnLoad(gameObject); // Initialize settings reference _interactionSettings = GameManager.GetSettingsObject(); 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); } private void Start() { // SceneManagerService.Instance.SceneLoadCompleted += SwitchInputOnSceneLoaded; SwitchInputOnSceneLoaded(SceneManager.GetActiveScene().name); } private void SwitchInputOnSceneLoaded(string sceneName) { if (sceneName.ToLower().Contains("mainmenu")) { Debug.Log("[InputManager] SwitchInputOnSceneLoaded - Setting InputMode to UI for MainMenu"); SetInputMode(InputMode.UI); } else { Debug.Log("[InputManager] SwitchInputOnSceneLoaded - Setting InputMode to PlayerTouch"); SetInputMode(InputMode.GameAndUI); } } public void SetInputMode(InputMode inputMode) { switch (inputMode) { case InputMode.UI: playerInput.actions.FindActionMap(UiActions).Enable(); playerInput.actions.FindActionMap(GameActions).Disable(); break; case InputMode.Game: playerInput.actions.FindActionMap(UiActions).Disable(); playerInput.actions.FindActionMap(GameActions).Enable(); break; case InputMode.GameAndUI: playerInput.actions.FindActionMap(UiActions).Enable(); playerInput.actions.FindActionMap(GameActions).Enable(); break; case InputMode.InputDisabled: playerInput.actions.FindActionMap(UiActions).Disable(); playerInput.actions.FindActionMap(GameActions).Disable(); break; } } 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) { if (EventSystem.current.IsPointerOverGameObject()) { return; } 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 = _interactionSettings != null ? _interactionSettings.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; } } }