diff --git a/Assets/Input/PlayerTouchActions.inputactions b/Assets/Input/PlayerTouchActions.inputactions index e618ba7f..5bf3e356 100644 --- a/Assets/Input/PlayerTouchActions.inputactions +++ b/Assets/Input/PlayerTouchActions.inputactions @@ -16,7 +16,7 @@ "initialStateCheck": true }, { - "name": "TouchPress", + "name": "TapMove", "type": "Button", "id": "a77a5b0d-26e8-4b6c-b23b-828c50b3108f", "expectedControlType": "", @@ -25,10 +25,10 @@ "initialStateCheck": false }, { - "name": "TouchDelta", - "type": "Value", + "name": "HoldMove", + "type": "Button", "id": "99ae0aa3-a70e-4afc-8a8a-c7d6144fa75b", - "expectedControlType": "Vector2", + "expectedControlType": "", "processors": "", "interactions": "", "initialStateCheck": true @@ -50,21 +50,21 @@ "name": "", "id": "f3dcd77f-15e5-4621-af67-001e6b08e3e6", "path": "/Press", - "interactions": "Press(behavior=2)", + "interactions": "Tap(duration=0.2)", "processors": "", "groups": "", - "action": "TouchPress", + "action": "TapMove", "isComposite": false, "isPartOfComposite": false }, { "name": "", "id": "19cc6a3f-9316-4da5-8e35-8297e02b8f6c", - "path": "/delta", - "interactions": "", + "path": "/Press", + "interactions": "Hold(duration=0.2)", "processors": "", "groups": "", - "action": "TouchDelta", + "action": "HoldMove", "isComposite": false, "isPartOfComposite": false } diff --git a/Assets/Scripts/EndlessDescenderController.cs b/Assets/Scripts/EndlessDescenderController.cs index f8864e26..b50fcf94 100644 --- a/Assets/Scripts/EndlessDescenderController.cs +++ b/Assets/Scripts/EndlessDescenderController.cs @@ -1,5 +1,9 @@ using UnityEngine; +/// +/// Handles endless descender movement in response to tap and hold input events. +/// Moves the character horizontally to follow the finger or tap position. +/// public class EndlessDescenderController : MonoBehaviour, ITouchInputConsumer { private float targetFingerX; @@ -20,45 +24,41 @@ public class EndlessDescenderController : MonoBehaviour, ITouchInputConsumer isTouchActive = false; } - // Implement new ITouchInputConsumer contract + /// + /// Handles tap input. Moves to the tapped X position. + /// public void OnTap(Vector2 worldPosition) { - // Treat tap as a quick move to the tapped X position + Debug.Log($"[EndlessDescenderController] OnTap at {worldPosition}"); targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); isTouchActive = true; } - public void OnDragStart(Vector2 position) - { - // - } - - public void OnDrag(Vector2 position) - { - // - } - - public void OnDragEnd(Vector2 position) - { - // - } - + /// + /// Handles the start of a hold input. Begins tracking the finger. + /// public void OnHoldStart(Vector2 worldPosition) { - // Start hold, update target X + Debug.Log($"[EndlessDescenderController] OnHoldStart at {worldPosition}"); targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); isTouchActive = true; } - public void OnHold(Vector2 worldPosition) + /// + /// Handles hold move input. Updates the target X position as the finger moves. + /// + public void OnHoldMove(Vector2 worldPosition) { - // Update target x as finger moves + Debug.Log($"[EndlessDescenderController] OnHoldMove at {worldPosition}"); targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); } + /// + /// Handles the end of a hold input. Stops tracking. + /// public void OnHoldEnd(Vector2 worldPosition) { - // Stop hold + Debug.Log($"[EndlessDescenderController] OnHoldEnd at {worldPosition}"); isTouchActive = false; } @@ -87,9 +87,5 @@ public class EndlessDescenderController : MonoBehaviour, ITouchInputConsumer newY += wobble.VerticalOffset; } transform.position = new Vector3(newX, newY, transform.position.z); - // Debug.Log($"EndlessDescenderController: Moved from {oldPos} to {transform.position} (targetX={targetX}, lerpSpeed={lerpSpeed}, offset={offset}, exponent={exponent}, moveStep={moveStep})"); } - - // Optionally, handle touch release if needed - // You can add a method to reset isTouchActive if desired } diff --git a/Assets/Scripts/ITouchInputConsumer.cs b/Assets/Scripts/ITouchInputConsumer.cs index d4510ae9..6d295bed 100644 --- a/Assets/Scripts/ITouchInputConsumer.cs +++ b/Assets/Scripts/ITouchInputConsumer.cs @@ -3,10 +3,7 @@ public interface ITouchInputConsumer { void OnTap(Vector2 position); - void OnDragStart(Vector2 position); - void OnDrag(Vector2 position); - void OnDragEnd(Vector2 position); void OnHoldStart(Vector2 position); - void OnHold(Vector2 position); + void OnHoldMove(Vector2 position); void OnHoldEnd(Vector2 position); } diff --git a/Assets/Scripts/InputManager.cs b/Assets/Scripts/InputManager.cs index 1795bf39..45cbb8a7 100644 --- a/Assets/Scripts/InputManager.cs +++ b/Assets/Scripts/InputManager.cs @@ -1,7 +1,10 @@ using UnityEngine; using UnityEngine.InputSystem; -using System; +/// +/// 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; @@ -24,20 +27,11 @@ public class InputManager : MonoBehaviour } private PlayerInput playerInput; - private InputAction touchPressAction; - private InputAction touchPositionAction; + private InputAction tapMoveAction; + private InputAction holdMoveAction; + private InputAction positionAction; private ITouchInputConsumer defaultConsumer; - - private bool isTouchHeld = false; - private bool lastFrameInteracted = false; - - // Tap/drag detection state - private Vector2 pressStartPosition; - private float pressStartTime; - private bool isPressed = false; - private bool isDragging = false; - private float dragThreshold = 10f; // pixels - private float tapTimeThreshold = 0.2f; // seconds + private bool isHoldActive; void Awake() { @@ -49,98 +43,112 @@ public class InputManager : MonoBehaviour Debug.LogError("[InputManager] InputManager requires a PlayerInput component attached to the same GameObject."); return; } - // Find actions by name in the assigned action map - touchPressAction = playerInput.actions.FindAction("TouchPress", false); - touchPositionAction = 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 (touchPressAction != null) + if (tapMoveAction != null) + tapMoveAction.performed += OnTapMovePerformed; + if (holdMoveAction != null) { - touchPressAction.started += OnTouchPressStarted; - touchPressAction.canceled += OnTouchPressCanceled; + holdMoveAction.performed += OnHoldMoveStarted; + holdMoveAction.canceled += OnHoldMoveCanceled; } - if (touchPositionAction != null) - touchPositionAction.performed += OnTouchPositionPerformed; } void OnDisable() { - if (touchPressAction != null) + if (tapMoveAction != null) + tapMoveAction.performed -= OnTapMovePerformed; + if (holdMoveAction != null) { - touchPressAction.started -= OnTouchPressStarted; - touchPressAction.canceled -= OnTouchPressCanceled; + holdMoveAction.performed -= OnHoldMoveStarted; + holdMoveAction.canceled -= OnHoldMoveCanceled; } - if (touchPositionAction != null) - touchPositionAction.performed -= OnTouchPositionPerformed; } + /// + /// Sets the default ITouchInputConsumer to receive input events. + /// public void SetDefaultConsumer(ITouchInputConsumer consumer) { defaultConsumer = consumer; } - private void OnTouchPressStarted(InputAction.CallbackContext ctx) + /// + /// Handles tap input, delegates to interactable if present, otherwise to default consumer. + /// + private void OnTapMovePerformed(InputAction.CallbackContext ctx) { - // Touch started (finger down) - Vector2 screenPos = touchPositionAction.ReadValue(); + Vector2 screenPos = positionAction.ReadValue(); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); - lastFrameInteracted = TryDelegateToInteractable(worldPos2D); - if (!lastFrameInteracted) + Debug.Log($"[InputManager] TapMove performed at {worldPos2D}"); + if (!TryDelegateToInteractable(worldPos2D)) { - pressStartPosition = screenPos; - pressStartTime = Time.time; - isPressed = true; - isDragging = false; - defaultConsumer?.OnHoldStart(worldPos2D); + Debug.Log("[InputManager] No interactable found, forwarding tap to default consumer"); + defaultConsumer?.OnTap(worldPos2D); + } + else + { + Debug.Log("[InputManager] Tap delegated to interactable"); } } - private void OnTouchPressCanceled(InputAction.CallbackContext ctx) + /// + /// Handles the start of a hold input. + /// + private void OnHoldMoveStarted(InputAction.CallbackContext ctx) { - Vector2 screenPos = touchPositionAction.ReadValue(); + isHoldActive = true; + Vector2 screenPos = positionAction.ReadValue(); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); - if (!lastFrameInteracted) - { - float timeHeld = Time.time - pressStartTime; - float dist = Vector2.Distance(screenPos, pressStartPosition); - if (!isDragging && timeHeld < tapTimeThreshold && dist < dragThreshold) - { - defaultConsumer?.OnTap(worldPos2D); - } - defaultConsumer?.OnHoldEnd(worldPos2D); - } - isPressed = false; - isDragging = false; - lastFrameInteracted = false; + Debug.Log($"[InputManager] HoldMove started at {worldPos2D}"); + defaultConsumer?.OnHoldStart(worldPos2D); } - private void OnTouchPositionPerformed(InputAction.CallbackContext ctx) + /// + /// Handles the end of a hold input. + /// + private void OnHoldMoveCanceled(InputAction.CallbackContext ctx) { - // No longer needed, OnTouchHeld will be handled in Update + 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 (isPressed && touchPositionAction != null) + if (isHoldActive && holdMoveAction != null && holdMoveAction.phase == InputActionPhase.Performed) { - Vector2 screenPos = touchPositionAction.ReadValue(); + Vector2 screenPos = positionAction.ReadValue(); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); - defaultConsumer?.OnHold(worldPos2D); + Debug.Log($"[InputManager] HoldMove update at {worldPos2D}"); + defaultConsumer?.OnHoldMove(worldPos2D); } } + /// + /// Attempts to delegate a tap to an interactable at the given world position. + /// private bool TryDelegateToInteractable(Vector2 worldPos) { - // Raycast at the world position to find an Interactable Collider2D hit = Physics2D.OverlapPoint(worldPos); if (hit != null) { - var interactable = hit.GetComponent(); + var interactable = hit.GetComponent(); if (interactable != null) { interactable.OnTap(worldPos); diff --git a/Assets/Scripts/Interactable.cs b/Assets/Scripts/Interactable.cs index af58399b..5966e58d 100644 --- a/Assets/Scripts/Interactable.cs +++ b/Assets/Scripts/Interactable.cs @@ -1,6 +1,9 @@ using UnityEngine; using System; +/// +/// Represents an interactable object that can respond to tap input events. +/// public class Interactable : MonoBehaviour, ITouchInputConsumer { public event Action StartedInteraction; @@ -13,21 +16,25 @@ public class Interactable : MonoBehaviour, ITouchInputConsumer stepBehaviour = GetComponent(); } - // Implement new ITouchInputConsumer contract + /// + /// Handles tap input. Triggers interaction logic. + /// public void OnTap(Vector2 worldPosition) { Debug.Log($"[Interactable] OnTap at {worldPosition} on {gameObject.name}"); StartedInteraction?.Invoke(); } - public void OnDragStart(Vector2 worldPosition) { } - public void OnDrag(Vector2 worldPosition) { } - public void OnDragEnd(Vector2 worldPosition) { } - public void OnHoldStart(Vector2 worldPosition) { /* No hold behavior for interactables */ } - public void OnHold(Vector2 worldPosition) { /* No hold behavior for interactables */ } - public void OnHoldEnd(Vector2 worldPosition) { /* No hold behavior for interactables */ } + /// + /// No hold behavior for interactables. + /// + public void OnHoldStart(Vector2 worldPosition) { } + public void OnHoldMove(Vector2 worldPosition) { } + public void OnHoldEnd(Vector2 worldPosition) { } - // Called when the follower arrives at this interactable + /// + /// Called when the follower arrives at this interactable. + /// public bool OnFollowerArrived(FollowerController follower) { // Check if step is locked here diff --git a/Assets/Scripts/PlayerTouchController.cs b/Assets/Scripts/PlayerTouchController.cs index cb74827b..1206284d 100644 --- a/Assets/Scripts/PlayerTouchController.cs +++ b/Assets/Scripts/PlayerTouchController.cs @@ -2,67 +2,61 @@ #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem; #endif -using Pathfinding; // Add this at the top +using Pathfinding; -// Basic touch/mouse movement controller suitable for top-down 2D or 3D overworld -// Attach to the player GameObject. Works with or without Rigidbody/Rigidbody2D. +/// +/// Handles player movement in response to tap and hold input events. +/// Supports both direct and pathfinding movement modes, and provides event/callbacks for arrival/cancellation. +/// public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer { - Vector3 targetPosition; - bool hasTarget = false; + // --- Movement State --- + private Vector3 targetPosition; + private bool hasTarget = false; + private Vector3 directMoveVelocity = Vector3.zero; + private bool isHolding = false; + private Vector2 lastHoldPosition; + private Coroutine pathfindingDragCoroutine; + private float pathfindingDragUpdateInterval = 0.1f; // Interval in seconds + private bool pendingTap = false; // Track if OnHoldEnd is following a tap (legacy, see below) - Rigidbody rb3d; - Rigidbody2D rb2d; - AIPath aiPath; // Reference to AIPath + // --- Unity/Component References --- + private AIPath aiPath; private Animator animator; private Transform artTransform; - // For direct movement mode - private Vector3 directMoveVelocity = Vector3.zero; - + // --- MoveToAndNotify State --- public delegate void ArrivedAtTargetHandler(); public event ArrivedAtTargetHandler OnArrivedAtTarget; public event System.Action OnMoveToCancelled; private Coroutine moveToCoroutine; private bool interruptMoveTo = false; - private bool isDragging = false; - private Coroutine pathfindingDragCoroutine; // For pathfinding drag updates - private Vector2 lastDragPosition; // Store last drag position - private float pathfindingDragUpdateInterval = 0.1f; // Interval in seconds - private bool pendingTap = false; // Track if OnHoldEnd is following a tap - void Awake() { - rb3d = GetComponent(); - rb2d = GetComponent(); - aiPath = GetComponent(); // Get AIPath component - // Find art prefab and animator + aiPath = GetComponent(); artTransform = transform.Find("CharacterArt"); if (artTransform != null) - { animator = artTransform.GetComponent(); - } else - { - animator = GetComponentInChildren(); // fallback - } + animator = GetComponentInChildren(); } void Start() { - // Initialize target to current position so object doesn't snap targetPosition = transform.position; hasTarget = false; - // Register as default consumer in Start, after InputManager is likely initialized InputManager.Instance?.SetDefaultConsumer(this); } - // Implement new ITouchInputConsumer contract + /// + /// Handles tap input. Always uses pathfinding to move to the tapped location. + /// Cancels any in-progress MoveToAndNotify. + /// public void OnTap(Vector2 worldPosition) { + InterruptMoveTo(); Debug.Log($"[PlayerTouchController] OnTap at {worldPosition}"); - pendingTap = true; if (aiPath != null) { aiPath.enabled = true; @@ -70,16 +64,20 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer aiPath.isStopped = false; SetTargetPosition(worldPosition); directMoveVelocity = Vector3.zero; - isDragging = false; + isHolding = false; } } - // --- Hold-based tracking --- + /// + /// Handles the start of a hold input. Begins tracking the finger and uses the correct movement mode. + /// Cancels any in-progress MoveToAndNotify. + /// public void OnHoldStart(Vector2 worldPosition) { - pendingTap = false; - lastDragPosition = worldPosition; - isDragging = true; + InterruptMoveTo(); + Debug.Log($"[PlayerTouchController] OnHoldStart at {worldPosition}"); + lastHoldPosition = worldPosition; + isHolding = true; if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding && aiPath != null) { aiPath.enabled = true; @@ -93,10 +91,13 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer } } - public void OnHold(Vector2 worldPosition) + /// + /// Handles hold move input. Updates the target position for direct or pathfinding movement. + /// + public void OnHoldMove(Vector2 worldPosition) { - if (!isDragging) return; - lastDragPosition = worldPosition; + if (!isHolding) return; + lastHoldPosition = worldPosition; if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct) { if (aiPath != null && aiPath.enabled) aiPath.enabled = false; @@ -105,9 +106,13 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer // If pathfinding, coroutine will update destination } + /// + /// Handles the end of a hold input. Stops tracking and disables movement as needed. + /// public void OnHoldEnd(Vector2 worldPosition) { - isDragging = false; + Debug.Log($"[PlayerTouchController] OnHoldEnd at {worldPosition}"); + isHolding = false; directMoveVelocity = Vector3.zero; if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding) { @@ -117,22 +122,17 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer pathfindingDragCoroutine = null; } } - // Only disable aiPath in direct mode if this was a hold/drag, not a tap - if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && !pendingTap) + if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct) { aiPath.enabled = false; } - pendingTap = false; // Reset after handling } - // --- Drag methods are now no-ops for movement tracking --- - public void OnDragStart(Vector2 worldPosition) { } - public void OnDrag(Vector2 worldPosition) { } - public void OnDragEnd(Vector2 worldPosition) { } - - void SetTargetPosition(Vector2 worldPosition) + /// + /// Sets the target position for pathfinding movement. + /// + private void SetTargetPosition(Vector2 worldPosition) { - Debug.Log($"[PlayerTouchController] SetTargetPosition: worldPosition={worldPosition}"); targetPosition = new Vector3(worldPosition.x, worldPosition.y, transform.position.z); hasTarget = true; if (aiPath != null) @@ -141,15 +141,13 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer aiPath.maxSpeed = GameManager.Instance.MoveSpeed; aiPath.canMove = true; aiPath.isStopped = false; - Debug.Log($"[PlayerTouchController] AIPath destination set to {targetPosition}, canMove={aiPath.canMove}, isStopped={aiPath.isStopped}, enabled={aiPath.enabled}"); - } - else - { - Debug.LogWarning("AIPath component not found, falling back to direct movement"); } } - void MoveDirectlyTo(Vector2 worldPosition) + /// + /// Moves the player directly towards the specified world position. + /// + private void MoveDirectlyTo(Vector2 worldPosition) { if (aiPath == null) return; Vector3 current = transform.position; @@ -158,14 +156,10 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer Vector3 direction = toTarget.normalized; float maxSpeed = aiPath.maxSpeed; float acceleration = aiPath.maxAcceleration; - // Accelerate velocity toward target direction directMoveVelocity = Vector3.MoveTowards(directMoveVelocity, direction * maxSpeed, acceleration * Time.deltaTime); - // Clamp velocity to max speed if (directMoveVelocity.magnitude > maxSpeed) directMoveVelocity = directMoveVelocity.normalized * maxSpeed; - // Move the player Vector3 move = directMoveVelocity * Time.deltaTime; - // Don't overshoot the target if (move.magnitude > toTarget.magnitude) move = toTarget; transform.position += move; @@ -176,7 +170,7 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer if (animator != null && aiPath != null) { float normalizedSpeed = 0f; - if (isDragging) + if (isHolding && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct) { normalizedSpeed = directMoveVelocity.magnitude / aiPath.maxSpeed; } @@ -188,11 +182,25 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer } } - // Remove FixedUpdate and MoveTowardsTarget, as AIPath handles movement + /// + /// Coroutine for updating the AIPath destination during pathfinding hold movement. + /// + private System.Collections.IEnumerator PathfindingDragUpdateCoroutine() + { + while (isHolding && aiPath != null) + { + aiPath.destination = new Vector3(lastHoldPosition.x, lastHoldPosition.y, transform.position.z); + yield return new WaitForSeconds(pathfindingDragUpdateInterval); + } + } - // Move to a target position, notify when arrived + /// + /// Moves the player to a specific target position and notifies via events when arrived or cancelled. + /// This is used by systems like Pickup.cs to orchestrate movement. + /// public void MoveToAndNotify(Vector3 target) { + // Cancel any previous move-to coroutine if (moveToCoroutine != null) { StopCoroutine(moveToCoroutine); @@ -201,15 +209,21 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target)); } + /// + /// Cancels any in-progress MoveToAndNotify operation and fires the cancellation event. + /// public void InterruptMoveTo() { interruptMoveTo = true; - isDragging = false; + isHolding = false; directMoveVelocity = Vector3.zero; if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && aiPath != null) aiPath.enabled = false; OnMoveToCancelled?.Invoke(); } + /// + /// Coroutine for moving the player to a target position and firing arrival/cancel events. + /// private System.Collections.IEnumerator MoveToTargetCoroutine(Vector3 target) { hasTarget = true; @@ -238,15 +252,7 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer } } - private System.Collections.IEnumerator PathfindingDragUpdateCoroutine() - { - Debug.Log("[PlayerTouchController] PathfindingDragUpdateCoroutine started"); - while (isDragging && aiPath != null) - { - aiPath.destination = new Vector3(lastDragPosition.x, lastDragPosition.y, transform.position.z); - Debug.Log($"[PlayerTouchController] Updating aiPath.destination to {aiPath.destination}"); - yield return new WaitForSeconds(pathfindingDragUpdateInterval); - } - Debug.Log("[PlayerTouchController] PathfindingDragUpdateCoroutine stopped"); - } + // --- Legacy/Unused fields --- + // pendingTap: This field is not used in the current input flow. If you are not using tap/hold distinction logic elsewhere, consider removing it. + // If you want to remove it, please confirm and I will do so. }