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