2025-09-25 10:06:51 +00:00
using System ;
using UnityEngine ;
using UnityEngine.EventSystems ;
2025-09-01 15:04:15 +02:00
using UnityEngine.InputSystem ;
2025-09-25 10:06:51 +00:00
using UnityEngine.SceneManagement ;
2025-09-01 15:04:15 +02:00
2025-09-25 10:06:51 +00:00
namespace Input
2025-09-01 15:04:15 +02:00
{
2025-09-25 10:06:51 +00:00
public enum InputMode
{
Game ,
UI ,
GameAndUI ,
InputDisabled
}
/// <summary>
/// Handles input events and dispatches them to the appropriate ITouchInputConsumer.
/// Supports tap and hold/drag logic, with interactable delegation and debug logging.
/// </summary>
public class InputManager : MonoBehaviour
2025-09-03 16:55:21 +02:00
{
2025-09-25 10:06:51 +00:00
private const string UiActions = "UI" ;
private const string GameActions = "PlayerTouch" ;
private static InputManager _instance ;
private static bool _isQuitting = false ;
public static InputManager Instance
2025-09-03 16:55:21 +02:00
{
2025-09-25 10:06:51 +00:00
get
2025-09-03 16:55:21 +02:00
{
2025-09-25 10:06:51 +00:00
if ( _instance = = null & & Application . isPlaying & & ! _isQuitting )
2025-09-03 16:55:21 +02:00
{
2025-09-25 10:06:51 +00:00
_instance = FindAnyObjectByType < InputManager > ( ) ;
if ( _instance = = null )
{
var go = new GameObject ( "InputManager" ) ;
_instance = go . AddComponent < InputManager > ( ) ;
// DontDestroyOnLoad(go);
}
2025-09-03 16:55:21 +02:00
}
2025-09-25 10:06:51 +00:00
return _instance ;
2025-09-03 16:55:21 +02:00
}
}
2025-09-25 10:06:51 +00:00
private PlayerInput playerInput ;
private InputAction tapMoveAction ;
private InputAction holdMoveAction ;
private InputAction positionAction ;
private ITouchInputConsumer defaultConsumer ;
private bool isHoldActive ;
2025-09-05 14:10:42 +02:00
2025-09-25 10:06:51 +00:00
void Awake ( )
2025-09-01 15:04:15 +02:00
{
2025-09-25 10:06:51 +00:00
_instance = this ;
// DontDestroyOnLoad(gameObject);
playerInput = GetComponent < PlayerInput > ( ) ;
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 ) ;
2025-09-01 15:04:15 +02:00
}
2025-09-25 10:06:51 +00:00
private void Start ( )
2025-09-02 15:12:36 +02:00
{
2025-09-25 10:06:51 +00:00
SceneManagerService . Instance . SceneLoadCompleted + = SwitchInputOnSceneLoaded ;
SwitchInputOnSceneLoaded ( SceneManager . GetActiveScene ( ) . name ) ;
2025-09-02 15:12:36 +02:00
}
2025-09-01 15:04:15 +02:00
2025-09-25 10:06:51 +00:00
private void SwitchInputOnSceneLoaded ( string sceneName )
2025-09-02 15:12:36 +02:00
{
2025-09-25 10:06:51 +00:00
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 ) ;
}
2025-09-02 15:12:36 +02:00
}
2025-09-01 15:04:15 +02:00
2025-09-25 10:06:51 +00:00
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 ;
}
}
2025-09-08 08:45:13 +02:00
2025-09-25 10:06:51 +00:00
void OnDisable ( )
{
if ( tapMoveAction ! = null )
tapMoveAction . performed - = OnTapMovePerformed ;
if ( holdMoveAction ! = null )
{
holdMoveAction . performed - = OnHoldMoveStarted ;
holdMoveAction . canceled - = OnHoldMoveCanceled ;
}
}
2025-09-01 15:04:15 +02:00
2025-09-25 10:06:51 +00:00
void OnApplicationQuit ( )
2025-09-05 14:10:42 +02:00
{
2025-09-25 10:06:51 +00:00
_isQuitting = true ;
2025-09-05 15:03:52 +02:00
}
2025-09-25 10:06:51 +00:00
/// <summary>
/// Sets the default ITouchInputConsumer to receive input events.
/// </summary>
public void SetDefaultConsumer ( ITouchInputConsumer consumer )
2025-09-05 15:03:52 +02:00
{
2025-09-25 10:06:51 +00:00
defaultConsumer = consumer ;
2025-09-05 14:10:42 +02:00
}
2025-09-01 15:04:15 +02:00
2025-09-25 10:06:51 +00:00
/// <summary>
/// Handles tap input, delegates to interactable if present, otherwise to default consumer.
/// </summary>
private void OnTapMovePerformed ( InputAction . CallbackContext ctx )
{
if ( EventSystem . current . IsPointerOverGameObject ( ) )
{
return ;
}
Vector2 screenPos = positionAction . ReadValue < Vector2 > ( ) ;
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" ) ;
}
}
2025-09-02 15:12:36 +02:00
2025-09-25 10:06:51 +00:00
/// <summary>
/// Handles the start of a hold input.
/// </summary>
private void OnHoldMoveStarted ( InputAction . CallbackContext ctx )
{
isHoldActive = true ;
Vector2 screenPos = positionAction . ReadValue < Vector2 > ( ) ;
Vector3 worldPos = Camera . main . ScreenToWorldPoint ( screenPos ) ;
Vector2 worldPos2D = new Vector2 ( worldPos . x , worldPos . y ) ;
Debug . Log ( $"[InputManager] HoldMove started at {worldPos2D}" ) ;
defaultConsumer ? . OnHoldStart ( worldPos2D ) ;
}
2025-09-02 15:12:36 +02:00
2025-09-25 10:06:51 +00:00
/// <summary>
/// Handles the end of a hold input.
/// </summary>
private void OnHoldMoveCanceled ( InputAction . CallbackContext ctx )
2025-09-02 15:12:36 +02:00
{
2025-09-25 10:06:51 +00:00
if ( ! isHoldActive ) return ;
isHoldActive = false ;
2025-09-05 15:03:52 +02:00
Vector2 screenPos = positionAction . ReadValue < Vector2 > ( ) ;
2025-09-05 14:10:42 +02:00
Vector3 worldPos = Camera . main . ScreenToWorldPoint ( screenPos ) ;
2025-09-02 15:12:36 +02:00
Vector2 worldPos2D = new Vector2 ( worldPos . x , worldPos . y ) ;
2025-09-25 10:06:51 +00:00
Debug . Log ( $"[InputManager] HoldMove canceled at {worldPos2D}" ) ;
defaultConsumer ? . OnHoldEnd ( worldPos2D ) ;
2025-09-02 15:12:36 +02:00
}
2025-09-01 15:04:15 +02:00
2025-09-25 10:06:51 +00:00
/// <summary>
/// Continuously updates hold move input while active.
/// </summary>
void Update ( )
2025-09-01 16:14:21 +02:00
{
2025-09-25 10:06:51 +00:00
if ( isHoldActive & & holdMoveAction ! = null & & holdMoveAction . phase = = InputActionPhase . Performed )
2025-09-10 16:42:43 +02:00
{
2025-09-25 10:06:51 +00:00
Vector2 screenPos = positionAction . ReadValue < Vector2 > ( ) ;
Vector3 worldPos = Camera . main . ScreenToWorldPoint ( screenPos ) ;
Vector2 worldPos2D = new Vector2 ( worldPos . x , worldPos . y ) ;
// Debug.Log($"[InputManager] HoldMove update at {worldPos2D}");
defaultConsumer ? . OnHoldMove ( worldPos2D ) ;
2025-09-01 16:14:21 +02:00
}
2025-09-08 12:44:31 +02:00
}
2025-09-25 10:06:51 +00:00
/// <summary>
/// Attempts to delegate a tap to an interactable at the given world position.
/// Traces on the "Interactable" channel and logs detailed info.
/// </summary>
private bool TryDelegateToInteractable ( Vector2 worldPos )
2025-09-08 12:44:31 +02:00
{
2025-09-25 10:06:51 +00:00
LayerMask mask = GameManager . Instance ! = null ? GameManager . Instance . InteractableLayerMask : - 1 ;
Collider2D hit = Physics2D . OverlapPoint ( worldPos , mask ) ;
if ( hit ! = null )
{
var consumer = hit . GetComponent < ITouchInputConsumer > ( ) ;
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 ;
2025-09-01 16:14:21 +02:00
}
2025-09-01 15:04:15 +02:00
}
}