@@ -1,175 +1,234 @@
using UnityEngine ;
using System ;
using UnityEngine ;
using UnityEngine.InputSystem ;
using UnityEngine.SceneManagement ;
/// <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
namespace Input
{
private static InputManager _instance ;
private static bool _isQuitting = false ;
public static InputManager Instance
public enum InputMode
{
get
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
{
private const string UiActions = "UI" ;
private const string GameActions = "PlayerTouch" ;
private static InputManager _instance ;
private static bool _isQuitting = false ;
public static InputManager Instance
{
if ( _instance = = null & & Application . isPlaying & & ! _isQuitting )
get
{
_instance = FindAnyObjectByType < InputManager > ( ) ;
if ( _instance = = null )
if ( _instance = = null & & Application . isPlaying & & ! _isQuitting )
{
var go = new GameObject ( " InputManager" ) ;
_instance = go . AddComponent < InputManager > ( ) ;
// DontDestroyOnLoad(go);
_instance = FindAnyObjectByType < InputManager> ( ) ;
if ( _instance = = null )
{
var go = new GameObject ( "InputManager" ) ;
_instance = go . AddComponent < InputManager > ( ) ;
// DontDestroyOnLoad(go);
}
}
return _instance ;
}
return _instance ;
}
}
private PlayerInput playerInput ;
private InputAction tapMoveAction ;
private InputAction holdMoveAction ;
private InputAction positionAction ;
private ITouchInputConsumer defaultConsumer ;
private bool isHoldActive ;
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 < PlayerInput > ( ) ;
if ( playerInput = = null )
void Awake ( )
{
Debug . LogError ( "[InputManager] InputManager requires a PlayerInput component attached to the same GameObject." ) ;
return ;
_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 ) ;
}
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 )
private void Start ( )
{
holdMoveAction . performed + = OnHoldMoveStart ed;
holdMoveAction . canceled + = OnHoldMoveCanceled ;
SceneManagerService . Instance . SceneLoadCompleted + = SwitchInputOnSceneLoad ed;
SwitchInputOnSceneLoaded ( SceneManager . GetActiveScene ( ) . name ) ;
}
}
void OnDisable ( )
{
if ( tapMoveAction ! = null )
tapMoveAction . performed - = OnTapMovePerformed ;
if ( holdMoveAction ! = null )
private void SwitchInputOnSceneLoaded ( string sceneName )
{
holdMoveAction . performed - = OnHoldMoveStarted ;
holdMoveAction . canceled - = OnHoldMoveCanceled ;
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 . Game ) ;
}
}
}
void OnApplicationQuit ( )
{
_isQuitting = true ;
}
/// <summary>
/// Sets the default ITouchInputConsumer to receive input events.
/// </summary>
public void SetDefaultConsumer ( ITouchInputConsumer consumer )
{
defaultConsumer = consumer ;
}
/// <summary>
/// Handles tap input, delegates to interactable if present, otherwise to default consumer.
/// </summary>
private void OnTapMovePerformed ( InputAction . CallbackContext ctx )
{
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 ) )
public void SetInputMode ( InputMode inputMode )
{
Debug . Log ( "[InputManager] No interactable found, forwarding tap to default consumer" ) ;
defaultConsumer ? . OnTap ( worldPos2D ) ;
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 ;
}
}
else
void OnEnable ( )
{
Debug . Log ( "[InputManager] Tap delegated to interactable" ) ;
if ( tapMoveAction ! = null )
tapMoveAction . performed + = OnTapMovePerformed ;
if ( holdMoveAction ! = null )
{
holdMoveAction . performed + = OnHoldMoveStarted ;
holdMoveAction . canceled + = OnHoldMoveCanceled ;
}
}
}
/// <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 ) ;
}
void OnDisable ( )
{
if ( tapMoveAction ! = null )
tapMoveAction . performed - = OnTapMovePerformed ;
if ( holdMoveAction ! = null )
{
holdMoveAction . performed - = OnHoldMoveStarted ;
holdMoveAction . canceled - = OnHoldMoveCanceled ;
}
}
/// <summary>
/// Handles the end of a hold input.
/// </summary>
private void OnHoldMoveCanceled ( InputAction . CallbackContext ctx )
{
if ( ! isHoldActive ) return ;
isHoldActive = false ;
Vector2 screenPos = positionAction . ReadValue < Vector2 > ( ) ;
Vector3 worldPos = Camera . main . ScreenToWorldPoint ( screenPos ) ;
Vector2 worldPos2D = new Vector2 ( worldPos . x , worldPos . y ) ;
Debug . Log ( $"[InputManager] HoldMove canceled at {worldPos2D}" ) ;
defaultConsumer ? . OnHoldEnd ( worldPos2D ) ;
}
void OnApplicationQuit ( )
{
_isQuitting = true ;
}
/// <summary>
/// Continuously updates hold move input while active .
/// </summary>
void Update ( )
{
if ( isHoldActive & & holdMoveAction ! = null & & holdMoveAction . phase = = InputActionPhase . Performed )
/// <summary>
/// Sets the default ITouchInputConsumer to receive input events .
/// </summary>
public void SetDefaultConsumer ( ITouchInputConsumer consumer )
{
defaultConsumer = consumer ;
}
/// <summary>
/// Handles tap input, delegates to interactable if present, otherwise to default consumer.
/// </summary>
private void OnTapMovePerformed ( InputAction . CallbackContext ctx )
{
Vector2 screenPos = positionAction . ReadValue < Vector2 > ( ) ;
Vector3 worldPos = Camera . main . ScreenToWorldPoint ( screenPos ) ;
Vector2 worldPos2D = new Vector2 ( worldPos . x , worldPos . y ) ;
// Debug.Log( $"[InputManager] Hold Move update at {worldPos2D}") ;
defaultConsumer ? . OnHoldMov e( worldPos2D ) ;
}
}
/// <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 )
{
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. Log ( $"[InputManager] Tap Move performed at {worldPos2D}") ;
if ( ! TryDelegateToInteractabl e( worldPos2D ) )
{
Debug . unityLogger . Log ( "Interactable" , $"[InputManager] Delegating tap to consumer at {worldPos} (GameObject: {hit.gameObject.name}) ") ;
c onsumer. OnTap ( worldPos ) ;
return true ;
Debug . Log ( "[InputManager] No interactable found, forwarding tap to default consumer ") ;
defaultC onsumer? .OnTap ( worldPos2D ) ;
}
else
{
Debug . Log ( "[InputManager] Tap delegated to interactable" ) ;
}
Debug . unityLogger . Log ( "Interactable" , $"[InputManager] Collider2D hit at {worldPos} (GameObject: {hit.gameObject.name}), but no ITouchInputConsumer found." ) ;
}
else
/// <summary>
/// Handles the start of a hold input.
/// </summary>
private void OnHoldMoveStarted ( InputAction . CallbackContext ctx )
{
Debug . unityLogger . Log ( "Interactable" , $"[InputManager] No Collider2D found at {worldPos} for interactable delegation." ) ;
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 ) ;
}
/// <summary>
/// Handles the end of a hold input.
/// </summary>
private void OnHoldMoveCanceled ( InputAction . CallbackContext ctx )
{
if ( ! isHoldActive ) return ;
isHoldActive = false ;
Vector2 screenPos = positionAction . ReadValue < Vector2 > ( ) ;
Vector3 worldPos = Camera . main . ScreenToWorldPoint ( screenPos ) ;
Vector2 worldPos2D = new Vector2 ( worldPos . x , worldPos . y ) ;
Debug . Log ( $"[InputManager] HoldMove canceled at {worldPos2D}" ) ;
defaultConsumer ? . OnHoldEnd ( worldPos2D ) ;
}
/// <summary>
/// Continuously updates hold move input while active.
/// </summary>
void Update ( )
{
if ( isHoldActive & & holdMoveAction ! = null & & holdMoveAction . phase = = InputActionPhase . Performed )
{
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 ) ;
}
}
/// <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 )
{
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 ;
}
return false ;
}
}