2025-09-01 11:51:01 +02:00
using UnityEngine ;
2025-09-01 13:14:21 +02:00
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem ;
#endif
2025-09-02 11:42:25 +02:00
using Pathfinding ; // Add this at the top
2025-09-01 11:51:01 +02:00
// Basic touch/mouse movement controller suitable for top-down 2D or 3D overworld
// Attach to the player GameObject. Works with or without Rigidbody/Rigidbody2D.
2025-09-01 15:04:15 +02:00
public class PlayerTouchController : MonoBehaviour , ITouchInputConsumer
2025-09-01 11:51:01 +02:00
{
Vector3 targetPosition ;
bool hasTarget = false ;
Rigidbody rb3d ;
Rigidbody2D rb2d ;
2025-09-02 11:42:25 +02:00
AIPath aiPath ; // Reference to AIPath
2025-09-02 15:49:21 +02:00
private Animator animator ;
private Transform artTransform ;
2025-09-01 11:51:01 +02:00
2025-09-05 14:10:42 +02:00
// For direct movement mode
private Vector3 directMoveVelocity = Vector3 . zero ;
2025-09-04 00:00:46 +02:00
public delegate void ArrivedAtTargetHandler ( ) ;
public event ArrivedAtTargetHandler OnArrivedAtTarget ;
2025-09-04 11:19:05 +02:00
public event System . Action OnMoveToCancelled ;
2025-09-04 00:00:46 +02:00
private Coroutine moveToCoroutine ;
private bool interruptMoveTo = false ;
2025-09-05 14:10:42 +02:00
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
2025-09-01 11:51:01 +02:00
void Awake ( )
{
rb3d = GetComponent < Rigidbody > ( ) ;
rb2d = GetComponent < Rigidbody2D > ( ) ;
2025-09-02 11:42:25 +02:00
aiPath = GetComponent < AIPath > ( ) ; // Get AIPath component
2025-09-02 15:49:21 +02:00
// Find art prefab and animator
artTransform = transform . Find ( "CharacterArt" ) ;
if ( artTransform ! = null )
{
animator = artTransform . GetComponent < Animator > ( ) ;
}
else
{
animator = GetComponentInChildren < Animator > ( ) ; // fallback
}
2025-09-01 11:51:01 +02:00
}
void Start ( )
{
// Initialize target to current position so object doesn't snap
targetPosition = transform . position ;
hasTarget = false ;
2025-09-01 16:57:01 +02:00
// Register as default consumer in Start, after InputManager is likely initialized
InputManager . Instance ? . SetDefaultConsumer ( this ) ;
2025-09-01 11:51:01 +02:00
}
2025-09-05 14:10:42 +02:00
// Implement new ITouchInputConsumer contract
public void OnTap ( Vector2 worldPosition )
2025-09-01 11:51:01 +02:00
{
2025-09-05 14:10:42 +02:00
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 ;
}
2025-09-01 11:51:01 +02:00
}
2025-09-05 14:10:42 +02:00
// --- 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 ;
}
}
2025-09-01 15:04:15 +02:00
2025-09-05 14:10:42 +02:00
public void OnHold ( Vector2 worldPosition )
2025-09-01 11:51:01 +02:00
{
2025-09-05 14:10:42 +02:00
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
2025-09-01 15:04:15 +02:00
}
2025-09-01 13:14:21 +02:00
2025-09-05 14:10:42 +02:00
public void OnHoldEnd ( Vector2 worldPosition )
2025-09-01 15:04:15 +02:00
{
2025-09-05 14:10:42 +02:00
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
2025-09-01 11:51:01 +02:00
}
2025-09-05 14:10:42 +02:00
// --- Drag methods are now no-ops for movement tracking ---
public void OnDragStart ( Vector2 worldPosition ) { }
public void OnDrag ( Vector2 worldPosition ) { }
public void OnDragEnd ( Vector2 worldPosition ) { }
2025-09-01 15:04:15 +02:00
void SetTargetPosition ( Vector2 worldPosition )
2025-09-01 11:51:01 +02:00
{
2025-09-05 14:10:42 +02:00
Debug . Log ( $"[PlayerTouchController] SetTargetPosition: worldPosition={worldPosition}" ) ;
2025-09-01 15:04:15 +02:00
targetPosition = new Vector3 ( worldPosition . x , worldPosition . y , transform . position . z ) ;
hasTarget = true ;
2025-09-02 11:42:25 +02:00
if ( aiPath ! = null )
2025-09-01 11:51:01 +02:00
{
2025-09-02 11:42:25 +02:00
aiPath . destination = targetPosition ;
2025-09-04 11:12:19 +02:00
aiPath . maxSpeed = GameManager . Instance . MoveSpeed ;
2025-09-05 14:10:42 +02:00
aiPath . canMove = true ;
aiPath . isStopped = false ;
Debug . Log ( $"[PlayerTouchController] AIPath destination set to {targetPosition}, canMove={aiPath.canMove}, isStopped={aiPath.isStopped}, enabled={aiPath.enabled}" ) ;
2025-09-01 11:51:01 +02:00
}
else
{
2025-09-02 11:42:25 +02:00
Debug . LogWarning ( "AIPath component not found, falling back to direct movement" ) ;
2025-09-01 11:51:01 +02:00
}
}
2025-09-02 11:42:25 +02:00
2025-09-05 14:10:42 +02:00
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 ;
}
2025-09-02 15:49:21 +02:00
void Update ( )
{
if ( animator ! = null & & aiPath ! = null )
{
2025-09-05 14:10:42 +02:00
float normalizedSpeed = 0f ;
if ( isDragging )
{
normalizedSpeed = directMoveVelocity . magnitude / aiPath . maxSpeed ;
}
else if ( aiPath . enabled )
{
normalizedSpeed = aiPath . velocity . magnitude / aiPath . maxSpeed ;
}
2025-09-02 15:49:21 +02:00
animator . SetFloat ( "Speed" , Mathf . Clamp01 ( normalizedSpeed ) ) ;
}
}
2025-09-02 11:42:25 +02:00
// Remove FixedUpdate and MoveTowardsTarget, as AIPath handles movement
2025-09-04 00:00:46 +02:00
// 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 ;
2025-09-05 14:10:42 +02:00
isDragging = false ;
directMoveVelocity = Vector3 . zero ;
if ( GameManager . Instance . DefaultHoldMovementMode = = GameSettings . HoldMovementMode . Direct & & aiPath ! = null ) aiPath . enabled = false ;
2025-09-04 11:19:05 +02:00
OnMoveToCancelled ? . Invoke ( ) ;
2025-09-04 00:00:46 +02:00
}
private System . Collections . IEnumerator MoveToTargetCoroutine ( Vector3 target )
{
hasTarget = true ;
targetPosition = target ;
if ( aiPath ! = null )
{
aiPath . destination = target ;
2025-09-04 11:12:19 +02:00
aiPath . maxSpeed = GameManager . Instance . MoveSpeed ;
2025-09-04 00:00:46 +02:00
}
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 ) ;
2025-09-04 11:12:19 +02:00
if ( dist < = GameManager . Instance . StopDistance + 0.2f )
2025-09-04 00:00:46 +02:00
{
break ;
}
yield return null ;
}
hasTarget = false ;
moveToCoroutine = null ;
if ( ! interruptMoveTo )
{
OnArrivedAtTarget ? . Invoke ( ) ;
}
}
2025-09-05 14:10:42 +02:00
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" ) ;
}
2025-09-01 11:51:01 +02:00
}