259 lines
9.2 KiB
C#
259 lines
9.2 KiB
C#
using UnityEngine;
|
|
#if ENABLE_INPUT_SYSTEM
|
|
using UnityEngine.InputSystem;
|
|
#endif
|
|
using Pathfinding;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
{
|
|
// --- 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)
|
|
|
|
// --- Unity/Component References ---
|
|
private AIPath aiPath;
|
|
private Animator animator;
|
|
private Transform artTransform;
|
|
|
|
// --- MoveToAndNotify State ---
|
|
public delegate void ArrivedAtTargetHandler();
|
|
public event ArrivedAtTargetHandler OnArrivedAtTarget;
|
|
public event System.Action OnMoveToCancelled;
|
|
private Coroutine moveToCoroutine;
|
|
private bool interruptMoveTo = false;
|
|
|
|
void Awake()
|
|
{
|
|
aiPath = GetComponent<AIPath>();
|
|
artTransform = transform.Find("CharacterArt");
|
|
if (artTransform != null)
|
|
animator = artTransform.GetComponent<Animator>();
|
|
else
|
|
animator = GetComponentInChildren<Animator>();
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
targetPosition = transform.position;
|
|
hasTarget = false;
|
|
InputManager.Instance?.SetDefaultConsumer(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles tap input. Always uses pathfinding to move to the tapped location.
|
|
/// Cancels any in-progress MoveToAndNotify.
|
|
/// </summary>
|
|
public void OnTap(Vector2 worldPosition)
|
|
{
|
|
InterruptMoveTo();
|
|
Debug.Log($"[PlayerTouchController] OnTap at {worldPosition}");
|
|
if (aiPath != null)
|
|
{
|
|
aiPath.enabled = true;
|
|
aiPath.canMove = true;
|
|
aiPath.isStopped = false;
|
|
SetTargetPosition(worldPosition);
|
|
directMoveVelocity = Vector3.zero;
|
|
isHolding = false;
|
|
}
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
InterruptMoveTo();
|
|
Debug.Log($"[PlayerTouchController] OnHoldStart at {worldPosition}");
|
|
lastHoldPosition = worldPosition;
|
|
isHolding = 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles hold move input. Updates the target position for direct or pathfinding movement.
|
|
/// </summary>
|
|
public void OnHoldMove(Vector2 worldPosition)
|
|
{
|
|
if (!isHolding) return;
|
|
lastHoldPosition = worldPosition;
|
|
if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct)
|
|
{
|
|
if (aiPath != null && aiPath.enabled) aiPath.enabled = false;
|
|
MoveDirectlyTo(worldPosition);
|
|
}
|
|
// 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)
|
|
{
|
|
Debug.Log($"[PlayerTouchController] OnHoldEnd at {worldPosition}");
|
|
isHolding = false;
|
|
directMoveVelocity = Vector3.zero;
|
|
if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Pathfinding)
|
|
{
|
|
if (pathfindingDragCoroutine != null)
|
|
{
|
|
StopCoroutine(pathfindingDragCoroutine);
|
|
pathfindingDragCoroutine = null;
|
|
}
|
|
}
|
|
if (aiPath != null && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct)
|
|
{
|
|
aiPath.enabled = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the target position for pathfinding movement.
|
|
/// </summary>
|
|
private void SetTargetPosition(Vector2 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves the player directly towards the specified world position.
|
|
/// </summary>
|
|
private 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;
|
|
directMoveVelocity = Vector3.MoveTowards(directMoveVelocity, direction * maxSpeed, acceleration * Time.deltaTime);
|
|
if (directMoveVelocity.magnitude > maxSpeed)
|
|
directMoveVelocity = directMoveVelocity.normalized * maxSpeed;
|
|
Vector3 move = directMoveVelocity * Time.deltaTime;
|
|
if (move.magnitude > toTarget.magnitude)
|
|
move = toTarget;
|
|
transform.position += move;
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (animator != null && aiPath != null)
|
|
{
|
|
float normalizedSpeed = 0f;
|
|
if (isHolding && GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct)
|
|
{
|
|
normalizedSpeed = directMoveVelocity.magnitude / aiPath.maxSpeed;
|
|
}
|
|
else if (aiPath.enabled)
|
|
{
|
|
normalizedSpeed = aiPath.velocity.magnitude / aiPath.maxSpeed;
|
|
}
|
|
animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed));
|
|
}
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
// Cancel any previous move-to coroutine
|
|
if (moveToCoroutine != null)
|
|
{
|
|
StopCoroutine(moveToCoroutine);
|
|
}
|
|
interruptMoveTo = false;
|
|
moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancels any in-progress MoveToAndNotify operation and fires the cancellation event.
|
|
/// </summary>
|
|
public void InterruptMoveTo()
|
|
{
|
|
interruptMoveTo = true;
|
|
isHolding = false;
|
|
directMoveVelocity = Vector3.zero;
|
|
if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && aiPath != null) aiPath.enabled = false;
|
|
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)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
// --- 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.
|
|
}
|