Big Input refactor

This commit is contained in:
Michal Pikulski
2025-09-05 15:03:52 +02:00
parent c6a5d6329f
commit 1489dbda05
6 changed files with 193 additions and 179 deletions

View File

@@ -16,7 +16,7 @@
"initialStateCheck": true "initialStateCheck": true
}, },
{ {
"name": "TouchPress", "name": "TapMove",
"type": "Button", "type": "Button",
"id": "a77a5b0d-26e8-4b6c-b23b-828c50b3108f", "id": "a77a5b0d-26e8-4b6c-b23b-828c50b3108f",
"expectedControlType": "", "expectedControlType": "",
@@ -25,10 +25,10 @@
"initialStateCheck": false "initialStateCheck": false
}, },
{ {
"name": "TouchDelta", "name": "HoldMove",
"type": "Value", "type": "Button",
"id": "99ae0aa3-a70e-4afc-8a8a-c7d6144fa75b", "id": "99ae0aa3-a70e-4afc-8a8a-c7d6144fa75b",
"expectedControlType": "Vector2", "expectedControlType": "",
"processors": "", "processors": "",
"interactions": "", "interactions": "",
"initialStateCheck": true "initialStateCheck": true
@@ -50,21 +50,21 @@
"name": "", "name": "",
"id": "f3dcd77f-15e5-4621-af67-001e6b08e3e6", "id": "f3dcd77f-15e5-4621-af67-001e6b08e3e6",
"path": "<Touchscreen>/Press", "path": "<Touchscreen>/Press",
"interactions": "Press(behavior=2)", "interactions": "Tap(duration=0.2)",
"processors": "", "processors": "",
"groups": "", "groups": "",
"action": "TouchPress", "action": "TapMove",
"isComposite": false, "isComposite": false,
"isPartOfComposite": false "isPartOfComposite": false
}, },
{ {
"name": "", "name": "",
"id": "19cc6a3f-9316-4da5-8e35-8297e02b8f6c", "id": "19cc6a3f-9316-4da5-8e35-8297e02b8f6c",
"path": "<Touchscreen>/delta", "path": "<Touchscreen>/Press",
"interactions": "", "interactions": "Hold(duration=0.2)",
"processors": "", "processors": "",
"groups": "", "groups": "",
"action": "TouchDelta", "action": "HoldMove",
"isComposite": false, "isComposite": false,
"isPartOfComposite": false "isPartOfComposite": false
} }

View File

@@ -1,5 +1,9 @@
using UnityEngine; using UnityEngine;
/// <summary>
/// Handles endless descender movement in response to tap and hold input events.
/// Moves the character horizontally to follow the finger or tap position.
/// </summary>
public class EndlessDescenderController : MonoBehaviour, ITouchInputConsumer public class EndlessDescenderController : MonoBehaviour, ITouchInputConsumer
{ {
private float targetFingerX; private float targetFingerX;
@@ -20,45 +24,41 @@ public class EndlessDescenderController : MonoBehaviour, ITouchInputConsumer
isTouchActive = false; isTouchActive = false;
} }
// Implement new ITouchInputConsumer contract /// <summary>
/// Handles tap input. Moves to the tapped X position.
/// </summary>
public void OnTap(Vector2 worldPosition) 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); targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
isTouchActive = true; isTouchActive = true;
} }
public void OnDragStart(Vector2 position) /// <summary>
{ /// Handles the start of a hold input. Begins tracking the finger.
// /// </summary>
}
public void OnDrag(Vector2 position)
{
//
}
public void OnDragEnd(Vector2 position)
{
//
}
public void OnHoldStart(Vector2 worldPosition) 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); targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
isTouchActive = true; isTouchActive = true;
} }
public void OnHold(Vector2 worldPosition) /// <summary>
/// Handles hold move input. Updates the target X position as the finger moves.
/// </summary>
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); targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
} }
/// <summary>
/// Handles the end of a hold input. Stops tracking.
/// </summary>
public void OnHoldEnd(Vector2 worldPosition) public void OnHoldEnd(Vector2 worldPosition)
{ {
// Stop hold Debug.Log($"[EndlessDescenderController] OnHoldEnd at {worldPosition}");
isTouchActive = false; isTouchActive = false;
} }
@@ -87,9 +87,5 @@ public class EndlessDescenderController : MonoBehaviour, ITouchInputConsumer
newY += wobble.VerticalOffset; newY += wobble.VerticalOffset;
} }
transform.position = new Vector3(newX, newY, transform.position.z); 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
} }

View File

@@ -3,10 +3,7 @@
public interface ITouchInputConsumer public interface ITouchInputConsumer
{ {
void OnTap(Vector2 position); void OnTap(Vector2 position);
void OnDragStart(Vector2 position);
void OnDrag(Vector2 position);
void OnDragEnd(Vector2 position);
void OnHoldStart(Vector2 position); void OnHoldStart(Vector2 position);
void OnHold(Vector2 position); void OnHoldMove(Vector2 position);
void OnHoldEnd(Vector2 position); void OnHoldEnd(Vector2 position);
} }

View File

@@ -1,7 +1,10 @@
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using System;
/// <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 public class InputManager : MonoBehaviour
{ {
private static InputManager _instance; private static InputManager _instance;
@@ -24,20 +27,11 @@ public class InputManager : MonoBehaviour
} }
private PlayerInput playerInput; private PlayerInput playerInput;
private InputAction touchPressAction; private InputAction tapMoveAction;
private InputAction touchPositionAction; private InputAction holdMoveAction;
private InputAction positionAction;
private ITouchInputConsumer defaultConsumer; private ITouchInputConsumer defaultConsumer;
private bool isHoldActive;
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
void Awake() void Awake()
{ {
@@ -49,98 +43,112 @@ public class InputManager : MonoBehaviour
Debug.LogError("[InputManager] InputManager requires a PlayerInput component attached to the same GameObject."); Debug.LogError("[InputManager] InputManager requires a PlayerInput component attached to the same GameObject.");
return; return;
} }
// Find actions by name in the assigned action map tapMoveAction = playerInput.actions.FindAction("TapMove", false);
touchPressAction = playerInput.actions.FindAction("TouchPress", false); holdMoveAction = playerInput.actions.FindAction("HoldMove", false);
touchPositionAction = playerInput.actions.FindAction("TouchPosition", false); positionAction = playerInput.actions.FindAction("TouchPosition", false);
} }
void OnEnable() void OnEnable()
{ {
if (touchPressAction != null) if (tapMoveAction != null)
tapMoveAction.performed += OnTapMovePerformed;
if (holdMoveAction != null)
{ {
touchPressAction.started += OnTouchPressStarted; holdMoveAction.performed += OnHoldMoveStarted;
touchPressAction.canceled += OnTouchPressCanceled; holdMoveAction.canceled += OnHoldMoveCanceled;
} }
if (touchPositionAction != null)
touchPositionAction.performed += OnTouchPositionPerformed;
} }
void OnDisable() void OnDisable()
{ {
if (touchPressAction != null) if (tapMoveAction != null)
tapMoveAction.performed -= OnTapMovePerformed;
if (holdMoveAction != null)
{ {
touchPressAction.started -= OnTouchPressStarted; holdMoveAction.performed -= OnHoldMoveStarted;
touchPressAction.canceled -= OnTouchPressCanceled; holdMoveAction.canceled -= OnHoldMoveCanceled;
} }
if (touchPositionAction != null)
touchPositionAction.performed -= OnTouchPositionPerformed;
} }
/// <summary>
/// Sets the default ITouchInputConsumer to receive input events.
/// </summary>
public void SetDefaultConsumer(ITouchInputConsumer consumer) public void SetDefaultConsumer(ITouchInputConsumer consumer)
{ {
defaultConsumer = consumer; defaultConsumer = consumer;
} }
private void OnTouchPressStarted(InputAction.CallbackContext ctx) /// <summary>
/// Handles tap input, delegates to interactable if present, otherwise to default consumer.
/// </summary>
private void OnTapMovePerformed(InputAction.CallbackContext ctx)
{ {
// Touch started (finger down) Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector2 screenPos = touchPositionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
lastFrameInteracted = TryDelegateToInteractable(worldPos2D); Debug.Log($"[InputManager] TapMove performed at {worldPos2D}");
if (!lastFrameInteracted) if (!TryDelegateToInteractable(worldPos2D))
{ {
pressStartPosition = screenPos; Debug.Log("[InputManager] No interactable found, forwarding tap to default consumer");
pressStartTime = Time.time; defaultConsumer?.OnTap(worldPos2D);
isPressed = true; }
isDragging = false; else
defaultConsumer?.OnHoldStart(worldPos2D); {
Debug.Log("[InputManager] Tap delegated to interactable");
} }
} }
private void OnTouchPressCanceled(InputAction.CallbackContext ctx) /// <summary>
/// Handles the start of a hold input.
/// </summary>
private void OnHoldMoveStarted(InputAction.CallbackContext ctx)
{ {
Vector2 screenPos = touchPositionAction.ReadValue<Vector2>(); isHoldActive = true;
Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
if (!lastFrameInteracted) Debug.Log($"[InputManager] HoldMove started at {worldPos2D}");
{ defaultConsumer?.OnHoldStart(worldPos2D);
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;
} }
private void OnTouchPositionPerformed(InputAction.CallbackContext ctx) /// <summary>
/// Handles the end of a hold input.
/// </summary>
private void OnHoldMoveCanceled(InputAction.CallbackContext ctx)
{ {
// No longer needed, OnTouchHeld will be handled in Update 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() void Update()
{ {
if (isPressed && touchPositionAction != null) if (isHoldActive && holdMoveAction != null && holdMoveAction.phase == InputActionPhase.Performed)
{ {
Vector2 screenPos = touchPositionAction.ReadValue<Vector2>(); Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
defaultConsumer?.OnHold(worldPos2D); Debug.Log($"[InputManager] HoldMove update at {worldPos2D}");
defaultConsumer?.OnHoldMove(worldPos2D);
} }
} }
/// <summary>
/// Attempts to delegate a tap to an interactable at the given world position.
/// </summary>
private bool TryDelegateToInteractable(Vector2 worldPos) private bool TryDelegateToInteractable(Vector2 worldPos)
{ {
// Raycast at the world position to find an Interactable
Collider2D hit = Physics2D.OverlapPoint(worldPos); Collider2D hit = Physics2D.OverlapPoint(worldPos);
if (hit != null) if (hit != null)
{ {
var interactable = hit.GetComponent<Interactable>(); var interactable = hit.GetComponent<ITouchInputConsumer>();
if (interactable != null) if (interactable != null)
{ {
interactable.OnTap(worldPos); interactable.OnTap(worldPos);

View File

@@ -1,6 +1,9 @@
using UnityEngine; using UnityEngine;
using System; using System;
/// <summary>
/// Represents an interactable object that can respond to tap input events.
/// </summary>
public class Interactable : MonoBehaviour, ITouchInputConsumer public class Interactable : MonoBehaviour, ITouchInputConsumer
{ {
public event Action StartedInteraction; public event Action StartedInteraction;
@@ -13,21 +16,25 @@ public class Interactable : MonoBehaviour, ITouchInputConsumer
stepBehaviour = GetComponent<ObjectiveStepBehaviour>(); stepBehaviour = GetComponent<ObjectiveStepBehaviour>();
} }
// Implement new ITouchInputConsumer contract /// <summary>
/// Handles tap input. Triggers interaction logic.
/// </summary>
public void OnTap(Vector2 worldPosition) public void OnTap(Vector2 worldPosition)
{ {
Debug.Log($"[Interactable] OnTap at {worldPosition} on {gameObject.name}"); Debug.Log($"[Interactable] OnTap at {worldPosition} on {gameObject.name}");
StartedInteraction?.Invoke(); StartedInteraction?.Invoke();
} }
public void OnDragStart(Vector2 worldPosition) { } /// <summary>
public void OnDrag(Vector2 worldPosition) { } /// No hold behavior for interactables.
public void OnDragEnd(Vector2 worldPosition) { } /// </summary>
public void OnHoldStart(Vector2 worldPosition) { /* No hold behavior for interactables */ } public void OnHoldStart(Vector2 worldPosition) { }
public void OnHold(Vector2 worldPosition) { /* No hold behavior for interactables */ } public void OnHoldMove(Vector2 worldPosition) { }
public void OnHoldEnd(Vector2 worldPosition) { /* No hold behavior for interactables */ } public void OnHoldEnd(Vector2 worldPosition) { }
// Called when the follower arrives at this interactable /// <summary>
/// Called when the follower arrives at this interactable.
/// </summary>
public bool OnFollowerArrived(FollowerController follower) public bool OnFollowerArrived(FollowerController follower)
{ {
// Check if step is locked here // Check if step is locked here

View File

@@ -2,67 +2,61 @@
#if ENABLE_INPUT_SYSTEM #if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
#endif #endif
using Pathfinding; // Add this at the top using Pathfinding;
// Basic touch/mouse movement controller suitable for top-down 2D or 3D overworld /// <summary>
// 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.
/// </summary>
public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
{ {
Vector3 targetPosition; // --- Movement State ---
bool hasTarget = false; 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; // --- Unity/Component References ---
Rigidbody2D rb2d; private AIPath aiPath;
AIPath aiPath; // Reference to AIPath
private Animator animator; private Animator animator;
private Transform artTransform; private Transform artTransform;
// For direct movement mode // --- MoveToAndNotify State ---
private Vector3 directMoveVelocity = Vector3.zero;
public delegate void ArrivedAtTargetHandler(); public delegate void ArrivedAtTargetHandler();
public event ArrivedAtTargetHandler OnArrivedAtTarget; public event ArrivedAtTargetHandler OnArrivedAtTarget;
public event System.Action OnMoveToCancelled; public event System.Action OnMoveToCancelled;
private Coroutine moveToCoroutine; private Coroutine moveToCoroutine;
private bool interruptMoveTo = false; 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() void Awake()
{ {
rb3d = GetComponent<Rigidbody>(); aiPath = GetComponent<AIPath>();
rb2d = GetComponent<Rigidbody2D>();
aiPath = GetComponent<AIPath>(); // Get AIPath component
// Find art prefab and animator
artTransform = transform.Find("CharacterArt"); artTransform = transform.Find("CharacterArt");
if (artTransform != null) if (artTransform != null)
{
animator = artTransform.GetComponent<Animator>(); animator = artTransform.GetComponent<Animator>();
}
else else
{ animator = GetComponentInChildren<Animator>();
animator = GetComponentInChildren<Animator>(); // fallback
}
} }
void Start() void Start()
{ {
// Initialize target to current position so object doesn't snap
targetPosition = transform.position; targetPosition = transform.position;
hasTarget = false; hasTarget = false;
// Register as default consumer in Start, after InputManager is likely initialized
InputManager.Instance?.SetDefaultConsumer(this); InputManager.Instance?.SetDefaultConsumer(this);
} }
// Implement new ITouchInputConsumer contract /// <summary>
/// Handles tap input. Always uses pathfinding to move to the tapped location.
/// Cancels any in-progress MoveToAndNotify.
/// </summary>
public void OnTap(Vector2 worldPosition) public void OnTap(Vector2 worldPosition)
{ {
InterruptMoveTo();
Debug.Log($"[PlayerTouchController] OnTap at {worldPosition}"); Debug.Log($"[PlayerTouchController] OnTap at {worldPosition}");
pendingTap = true;
if (aiPath != null) if (aiPath != null)
{ {
aiPath.enabled = true; aiPath.enabled = true;
@@ -70,16 +64,20 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
aiPath.isStopped = false; aiPath.isStopped = false;
SetTargetPosition(worldPosition); SetTargetPosition(worldPosition);
directMoveVelocity = Vector3.zero; directMoveVelocity = Vector3.zero;
isDragging = false; isHolding = false;
} }
} }
// --- Hold-based tracking --- /// <summary>
/// Handles the start of a hold input. Begins tracking the finger and uses the correct movement mode.
/// Cancels any in-progress MoveToAndNotify.
/// </summary>
public void OnHoldStart(Vector2 worldPosition) public void OnHoldStart(Vector2 worldPosition)
{ {
pendingTap = false; InterruptMoveTo();
lastDragPosition = worldPosition; Debug.Log($"[PlayerTouchController] OnHoldStart at {worldPosition}");
isDragging = true; lastHoldPosition = worldPosition;
isHolding = true;
if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding && aiPath != null) if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding && aiPath != null)
{ {
aiPath.enabled = true; aiPath.enabled = true;
@@ -93,10 +91,13 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
} }
} }
public void OnHold(Vector2 worldPosition) /// <summary>
/// Handles hold move input. Updates the target position for direct or pathfinding movement.
/// </summary>
public void OnHoldMove(Vector2 worldPosition)
{ {
if (!isDragging) return; if (!isHolding) return;
lastDragPosition = worldPosition; lastHoldPosition = worldPosition;
if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct) if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct)
{ {
if (aiPath != null && aiPath.enabled) aiPath.enabled = false; if (aiPath != null && aiPath.enabled) aiPath.enabled = false;
@@ -105,9 +106,13 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
// If pathfinding, coroutine will update destination // If pathfinding, coroutine will update destination
} }
/// <summary>
/// Handles the end of a hold input. Stops tracking and disables movement as needed.
/// </summary>
public void OnHoldEnd(Vector2 worldPosition) public void OnHoldEnd(Vector2 worldPosition)
{ {
isDragging = false; Debug.Log($"[PlayerTouchController] OnHoldEnd at {worldPosition}");
isHolding = false;
directMoveVelocity = Vector3.zero; directMoveVelocity = Vector3.zero;
if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding) if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding)
{ {
@@ -117,22 +122,17 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
pathfindingDragCoroutine = null; 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)
if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && !pendingTap)
{ {
aiPath.enabled = false; aiPath.enabled = false;
} }
pendingTap = false; // Reset after handling
} }
// --- Drag methods are now no-ops for movement tracking --- /// <summary>
public void OnDragStart(Vector2 worldPosition) { } /// Sets the target position for pathfinding movement.
public void OnDrag(Vector2 worldPosition) { } /// </summary>
public void OnDragEnd(Vector2 worldPosition) { } private void SetTargetPosition(Vector2 worldPosition)
void SetTargetPosition(Vector2 worldPosition)
{ {
Debug.Log($"[PlayerTouchController] SetTargetPosition: worldPosition={worldPosition}");
targetPosition = new Vector3(worldPosition.x, worldPosition.y, transform.position.z); targetPosition = new Vector3(worldPosition.x, worldPosition.y, transform.position.z);
hasTarget = true; hasTarget = true;
if (aiPath != null) if (aiPath != null)
@@ -141,15 +141,13 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
aiPath.maxSpeed = GameManager.Instance.MoveSpeed; aiPath.maxSpeed = GameManager.Instance.MoveSpeed;
aiPath.canMove = true; aiPath.canMove = true;
aiPath.isStopped = false; 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) /// <summary>
/// Moves the player directly towards the specified world position.
/// </summary>
private void MoveDirectlyTo(Vector2 worldPosition)
{ {
if (aiPath == null) return; if (aiPath == null) return;
Vector3 current = transform.position; Vector3 current = transform.position;
@@ -158,14 +156,10 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
Vector3 direction = toTarget.normalized; Vector3 direction = toTarget.normalized;
float maxSpeed = aiPath.maxSpeed; float maxSpeed = aiPath.maxSpeed;
float acceleration = aiPath.maxAcceleration; float acceleration = aiPath.maxAcceleration;
// Accelerate velocity toward target direction
directMoveVelocity = Vector3.MoveTowards(directMoveVelocity, direction * maxSpeed, acceleration * Time.deltaTime); directMoveVelocity = Vector3.MoveTowards(directMoveVelocity, direction * maxSpeed, acceleration * Time.deltaTime);
// Clamp velocity to max speed
if (directMoveVelocity.magnitude > maxSpeed) if (directMoveVelocity.magnitude > maxSpeed)
directMoveVelocity = directMoveVelocity.normalized * maxSpeed; directMoveVelocity = directMoveVelocity.normalized * maxSpeed;
// Move the player
Vector3 move = directMoveVelocity * Time.deltaTime; Vector3 move = directMoveVelocity * Time.deltaTime;
// Don't overshoot the target
if (move.magnitude > toTarget.magnitude) if (move.magnitude > toTarget.magnitude)
move = toTarget; move = toTarget;
transform.position += move; transform.position += move;
@@ -176,7 +170,7 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
if (animator != null && aiPath != null) if (animator != null && aiPath != null)
{ {
float normalizedSpeed = 0f; float normalizedSpeed = 0f;
if (isDragging) if (isHolding && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct)
{ {
normalizedSpeed = directMoveVelocity.magnitude / aiPath.maxSpeed; normalizedSpeed = directMoveVelocity.magnitude / aiPath.maxSpeed;
} }
@@ -188,11 +182,25 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
} }
} }
// Remove FixedUpdate and MoveTowardsTarget, as AIPath handles movement /// <summary>
/// Coroutine for updating the AIPath destination during pathfinding hold movement.
/// </summary>
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 /// <summary>
/// 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.
/// </summary>
public void MoveToAndNotify(Vector3 target) public void MoveToAndNotify(Vector3 target)
{ {
// Cancel any previous move-to coroutine
if (moveToCoroutine != null) if (moveToCoroutine != null)
{ {
StopCoroutine(moveToCoroutine); StopCoroutine(moveToCoroutine);
@@ -201,15 +209,21 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target)); moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target));
} }
/// <summary>
/// Cancels any in-progress MoveToAndNotify operation and fires the cancellation event.
/// </summary>
public void InterruptMoveTo() public void InterruptMoveTo()
{ {
interruptMoveTo = true; interruptMoveTo = true;
isDragging = false; isHolding = false;
directMoveVelocity = Vector3.zero; directMoveVelocity = Vector3.zero;
if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && aiPath != null) aiPath.enabled = false; if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && aiPath != null) aiPath.enabled = false;
OnMoveToCancelled?.Invoke(); OnMoveToCancelled?.Invoke();
} }
/// <summary>
/// Coroutine for moving the player to a target position and firing arrival/cancel events.
/// </summary>
private System.Collections.IEnumerator MoveToTargetCoroutine(Vector3 target) private System.Collections.IEnumerator MoveToTargetCoroutine(Vector3 target)
{ {
hasTarget = true; hasTarget = true;
@@ -238,15 +252,7 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
} }
} }
private System.Collections.IEnumerator PathfindingDragUpdateCoroutine() // --- 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.
Debug.Log("[PlayerTouchController] PathfindingDragUpdateCoroutine started"); // If you want to remove it, please confirm and I will do so.
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");
}
} }