using UnityEngine; #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem; #endif using Pathfinding; // Add this at the top // Basic touch/mouse movement controller suitable for top-down 2D or 3D overworld // Attach to the player GameObject. Works with or without Rigidbody/Rigidbody2D. public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer { Vector3 targetPosition; bool hasTarget = false; Rigidbody rb3d; Rigidbody2D rb2d; AIPath aiPath; // Reference to AIPath private Animator animator; private Transform artTransform; // For direct movement mode private Vector3 directMoveVelocity = Vector3.zero; 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 artTransform = transform.Find("CharacterArt"); if (artTransform != null) { animator = artTransform.GetComponent(); } else { animator = GetComponentInChildren(); // fallback } } 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 public void OnTap(Vector2 worldPosition) { Debug.Log($"[PlayerTouchController] OnTap at {worldPosition}"); pendingTap = true; if (aiPath != null) { aiPath.enabled = true; aiPath.canMove = true; aiPath.isStopped = false; SetTargetPosition(worldPosition); directMoveVelocity = Vector3.zero; isDragging = false; } } // --- Hold-based tracking --- public void OnHoldStart(Vector2 worldPosition) { pendingTap = false; lastDragPosition = worldPosition; isDragging = true; if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding && aiPath != null) { aiPath.enabled = true; if (pathfindingDragCoroutine != null) StopCoroutine(pathfindingDragCoroutine); pathfindingDragCoroutine = StartCoroutine(PathfindingDragUpdateCoroutine()); } else // Direct movement { if (aiPath != null) aiPath.enabled = false; directMoveVelocity = Vector3.zero; } } public void OnHold(Vector2 worldPosition) { if (!isDragging) return; lastDragPosition = worldPosition; if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct) { if (aiPath != null && aiPath.enabled) aiPath.enabled = false; MoveDirectlyTo(worldPosition); } // If pathfinding, coroutine will update destination } public void OnHoldEnd(Vector2 worldPosition) { isDragging = false; directMoveVelocity = Vector3.zero; if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding) { if (pathfindingDragCoroutine != null) { StopCoroutine(pathfindingDragCoroutine); 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) { 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) { Debug.Log($"[PlayerTouchController] SetTargetPosition: worldPosition={worldPosition}"); targetPosition = new Vector3(worldPosition.x, worldPosition.y, transform.position.z); hasTarget = true; if (aiPath != null) { aiPath.destination = targetPosition; 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) { if (aiPath == null) return; Vector3 current = transform.position; Vector3 target = new Vector3(worldPosition.x, worldPosition.y, current.z); Vector3 toTarget = (target - current); 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; } void Update() { if (animator != null && aiPath != null) { float normalizedSpeed = 0f; if (isDragging) { normalizedSpeed = directMoveVelocity.magnitude / aiPath.maxSpeed; } else if (aiPath.enabled) { normalizedSpeed = aiPath.velocity.magnitude / aiPath.maxSpeed; } animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed)); } } // Remove FixedUpdate and MoveTowardsTarget, as AIPath handles movement // Move to a target position, notify when arrived public void MoveToAndNotify(Vector3 target) { if (moveToCoroutine != null) { StopCoroutine(moveToCoroutine); } interruptMoveTo = false; moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target)); } public void InterruptMoveTo() { interruptMoveTo = true; isDragging = false; directMoveVelocity = Vector3.zero; if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && aiPath != null) aiPath.enabled = false; OnMoveToCancelled?.Invoke(); } private System.Collections.IEnumerator MoveToTargetCoroutine(Vector3 target) { hasTarget = true; targetPosition = target; if (aiPath != null) { aiPath.destination = target; aiPath.maxSpeed = GameManager.Instance.MoveSpeed; } while (!interruptMoveTo) { Vector2 current2D = new Vector2(transform.position.x, transform.position.y); Vector2 target2D = new Vector2(target.x, target.y); float dist = Vector2.Distance(current2D, target2D); if (dist <= GameManager.Instance.StopDistance + 0.2f) { break; } yield return null; } hasTarget = false; moveToCoroutine = null; if (!interruptMoveTo) { OnArrivedAtTarget?.Invoke(); } } 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"); } }