Big script cleanup. Remove the examples from Ropes' external package
This commit is contained in:
9
Assets/Scripts/Input/ITouchInputConsumer.cs
Normal file
9
Assets/Scripts/Input/ITouchInputConsumer.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
public interface ITouchInputConsumer
|
||||
{
|
||||
void OnTap(Vector2 position);
|
||||
void OnHoldStart(Vector2 position);
|
||||
void OnHoldMove(Vector2 position);
|
||||
void OnHoldEnd(Vector2 position);
|
||||
}
|
||||
3
Assets/Scripts/Input/ITouchInputConsumer.cs.meta
Normal file
3
Assets/Scripts/Input/ITouchInputConsumer.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84aa53c8b48d4c46a2d10611f4987550
|
||||
timeCreated: 1756729216
|
||||
160
Assets/Scripts/Input/InputManager.cs
Normal file
160
Assets/Scripts/Input/InputManager.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
/// <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
|
||||
{
|
||||
private static InputManager _instance;
|
||||
public static InputManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = FindAnyObjectByType<InputManager>();
|
||||
if (_instance == null)
|
||||
{
|
||||
var go = new GameObject("InputManager");
|
||||
_instance = go.AddComponent<InputManager>();
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private PlayerInput playerInput;
|
||||
private InputAction tapMoveAction;
|
||||
private InputAction holdMoveAction;
|
||||
private InputAction positionAction;
|
||||
private ITouchInputConsumer defaultConsumer;
|
||||
private bool isHoldActive;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
playerInput = GetComponent<PlayerInput>();
|
||||
if (playerInput == null)
|
||||
{
|
||||
Debug.LogError("[InputManager] InputManager requires a PlayerInput component attached to the same GameObject.");
|
||||
return;
|
||||
}
|
||||
tapMoveAction = playerInput.actions.FindAction("TapMove", false);
|
||||
holdMoveAction = playerInput.actions.FindAction("HoldMove", false);
|
||||
positionAction = playerInput.actions.FindAction("TouchPosition", false);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (tapMoveAction != null)
|
||||
tapMoveAction.performed += OnTapMovePerformed;
|
||||
if (holdMoveAction != null)
|
||||
{
|
||||
holdMoveAction.performed += OnHoldMoveStarted;
|
||||
holdMoveAction.canceled += OnHoldMoveCanceled;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (tapMoveAction != null)
|
||||
tapMoveAction.performed -= OnTapMovePerformed;
|
||||
if (holdMoveAction != null)
|
||||
{
|
||||
holdMoveAction.performed -= OnHoldMoveStarted;
|
||||
holdMoveAction.canceled -= OnHoldMoveCanceled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default ITouchInputConsumer to receive input events.
|
||||
/// </summary>
|
||||
public void SetDefaultConsumer(ITouchInputConsumer consumer)
|
||||
{
|
||||
defaultConsumer = consumer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles tap input, delegates to interactable if present, otherwise to default consumer.
|
||||
/// </summary>
|
||||
private void OnTapMovePerformed(InputAction.CallbackContext ctx)
|
||||
{
|
||||
Vector2 screenPos = positionAction.ReadValue<Vector2>();
|
||||
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
|
||||
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
|
||||
Debug.Log($"[InputManager] TapMove performed at {worldPos2D}");
|
||||
if (!TryDelegateToInteractable(worldPos2D))
|
||||
{
|
||||
Debug.Log("[InputManager] No interactable found, forwarding tap to default consumer");
|
||||
defaultConsumer?.OnTap(worldPos2D);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[InputManager] Tap delegated to interactable");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the start of a hold input.
|
||||
/// </summary>
|
||||
private void OnHoldMoveStarted(InputAction.CallbackContext ctx)
|
||||
{
|
||||
isHoldActive = true;
|
||||
Vector2 screenPos = positionAction.ReadValue<Vector2>();
|
||||
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
|
||||
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
|
||||
Debug.Log($"[InputManager] HoldMove started at {worldPos2D}");
|
||||
defaultConsumer?.OnHoldStart(worldPos2D);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the end of a hold input.
|
||||
/// </summary>
|
||||
private void OnHoldMoveCanceled(InputAction.CallbackContext ctx)
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (isHoldActive && holdMoveAction != null && holdMoveAction.phase == InputActionPhase.Performed)
|
||||
{
|
||||
Vector2 screenPos = positionAction.ReadValue<Vector2>();
|
||||
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
|
||||
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
|
||||
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)
|
||||
{
|
||||
Collider2D hit = Physics2D.OverlapPoint(worldPos);
|
||||
if (hit != null)
|
||||
{
|
||||
var interactable = hit.GetComponent<ITouchInputConsumer>();
|
||||
if (interactable != null)
|
||||
{
|
||||
interactable.OnTap(worldPos);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Input/InputManager.cs.meta
Normal file
3
Assets/Scripts/Input/InputManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eaca18daca6d4bfd8026afb6dd5f8c61
|
||||
timeCreated: 1756729225
|
||||
261
Assets/Scripts/Input/PlayerTouchController.cs
Normal file
261
Assets/Scripts/Input/PlayerTouchController.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using UnityEngine;
|
||||
using Pathfinding;
|
||||
|
||||
namespace Input
|
||||
{
|
||||
/// <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 Vector3 directMoveVelocity; // default is Vector3.zero
|
||||
private bool isHolding;
|
||||
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;
|
||||
|
||||
// Note: String-based property lookup is flagged as inefficient, but is common in Unity for dynamic children.
|
||||
private Animator animator;
|
||||
private Transform artTransform;
|
||||
|
||||
// --- MoveToAndNotify State ---
|
||||
public delegate void ArrivedAtTargetHandler();
|
||||
private Coroutine moveToCoroutine;
|
||||
public event ArrivedAtTargetHandler OnArrivedAtTarget;
|
||||
public event System.Action OnMoveToCancelled;
|
||||
private bool interruptMoveTo;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
aiPath = GetComponent<AIPath>();
|
||||
artTransform = transform.Find("CharacterArt");
|
||||
if (artTransform != null)
|
||||
animator = artTransform.GetComponent<Animator>();
|
||||
else
|
||||
animator = GetComponentInChildren<Animator>();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (aiPath != null)
|
||||
{
|
||||
aiPath.destination = worldPosition;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Input/PlayerTouchController.cs.meta
Normal file
3
Assets/Scripts/Input/PlayerTouchController.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ecb5bf6d8f447368687404e1b24278d
|
||||
timeCreated: 1756719186
|
||||
Reference in New Issue
Block a user