From 5d3a587b5e75e4b5183d9ff031b3b2a5943dc15c Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Fri, 5 Sep 2025 14:10:42 +0200 Subject: [PATCH] Update touch input settings --- Assets/Data/Settings/DefaultSettings.asset | 1 + Assets/Input/PlayerTouchActions.inputactions | 22 ++- Assets/Scripts/EndlessDescenderController.cs | 35 ++++- Assets/Scripts/GameManager.cs | 1 + Assets/Scripts/GameSettings.cs | 2 + Assets/Scripts/ITouchInputConsumer.cs | 10 +- Assets/Scripts/InputManager.cs | 65 +++++---- Assets/Scripts/Interactable.cs | 17 +-- Assets/Scripts/PlayerTouchController.cs | 138 ++++++++++++++++--- 9 files changed, 235 insertions(+), 56 deletions(-) diff --git a/Assets/Data/Settings/DefaultSettings.asset b/Assets/Data/Settings/DefaultSettings.asset index 4d01243c..762b7c89 100644 --- a/Assets/Data/Settings/DefaultSettings.asset +++ b/Assets/Data/Settings/DefaultSettings.asset @@ -22,6 +22,7 @@ MonoBehaviour: moveSpeed: 20 stopDistance: 0.1 useRigidbody: 1 + defaultHoldMovementMode: 1 followUpdateInterval: 0.1 followerSpeedMultiplier: 1.2 heldIconDisplayHeight: 2 diff --git a/Assets/Input/PlayerTouchActions.inputactions b/Assets/Input/PlayerTouchActions.inputactions index 15b6e2a5..e618ba7f 100644 --- a/Assets/Input/PlayerTouchActions.inputactions +++ b/Assets/Input/PlayerTouchActions.inputactions @@ -23,6 +23,15 @@ "processors": "", "interactions": "", "initialStateCheck": false + }, + { + "name": "TouchDelta", + "type": "Value", + "id": "99ae0aa3-a70e-4afc-8a8a-c7d6144fa75b", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true } ], "bindings": [ @@ -41,12 +50,23 @@ "name": "", "id": "f3dcd77f-15e5-4621-af67-001e6b08e3e6", "path": "/Press", - "interactions": "Hold", + "interactions": "Press(behavior=2)", "processors": "", "groups": "", "action": "TouchPress", "isComposite": false, "isPartOfComposite": false + }, + { + "name": "", + "id": "19cc6a3f-9316-4da5-8e35-8297e02b8f6c", + "path": "/delta", + "interactions": "", + "processors": "", + "groups": "", + "action": "TouchDelta", + "isComposite": false, + "isPartOfComposite": false } ] } diff --git a/Assets/Scripts/EndlessDescenderController.cs b/Assets/Scripts/EndlessDescenderController.cs index cffd0b76..f8864e26 100644 --- a/Assets/Scripts/EndlessDescenderController.cs +++ b/Assets/Scripts/EndlessDescenderController.cs @@ -20,19 +20,48 @@ public class EndlessDescenderController : MonoBehaviour, ITouchInputConsumer isTouchActive = false; } - public void OnTouchPress(Vector2 worldPosition) + // Implement new ITouchInputConsumer contract + public void OnTap(Vector2 worldPosition) { - // Only update horizontal position + // Treat tap as a quick move to the tapped X position targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); isTouchActive = true; } - public void OnTouchPosition(Vector2 worldPosition) + public void OnDragStart(Vector2 position) + { + // + } + + public void OnDrag(Vector2 position) + { + // + } + + public void OnDragEnd(Vector2 position) + { + // + } + + public void OnHoldStart(Vector2 worldPosition) + { + // Start hold, update target X + targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); + isTouchActive = true; + } + + public void OnHold(Vector2 worldPosition) { // Update target x as finger moves targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); } + public void OnHoldEnd(Vector2 worldPosition) + { + // Stop hold + isTouchActive = false; + } + void Update() { if (!isTouchActive) return; diff --git a/Assets/Scripts/GameManager.cs b/Assets/Scripts/GameManager.cs index 67d585fc..369cda86 100644 --- a/Assets/Scripts/GameManager.cs +++ b/Assets/Scripts/GameManager.cs @@ -74,4 +74,5 @@ public class GameManager : MonoBehaviour public float EndlessDescenderClampXMin => gameSettings != null ? gameSettings.endlessDescenderClampXMin : -5f; public float EndlessDescenderClampXMax => gameSettings != null ? gameSettings.endlessDescenderClampXMax : 5f; public float EndlessDescenderSpeedExponent => gameSettings != null ? gameSettings.endlessDescenderSpeedExponent : 2.5f; + public GameSettings.HoldMovementMode DefaultHoldMovementMode => gameSettings != null ? gameSettings.defaultHoldMovementMode : GameSettings.HoldMovementMode.Pathfinding; } diff --git a/Assets/Scripts/GameSettings.cs b/Assets/Scripts/GameSettings.cs index d490446d..cdec7962 100644 --- a/Assets/Scripts/GameSettings.cs +++ b/Assets/Scripts/GameSettings.cs @@ -18,6 +18,8 @@ public class GameSettings : ScriptableObject public float moveSpeed = 5f; public float stopDistance = 0.1f; public bool useRigidbody = true; + public enum HoldMovementMode { Pathfinding, Direct } + public HoldMovementMode defaultHoldMovementMode = HoldMovementMode.Pathfinding; [Header("Backend Settings")] [Tooltip("Technical parameters, not for design tuning")] diff --git a/Assets/Scripts/ITouchInputConsumer.cs b/Assets/Scripts/ITouchInputConsumer.cs index 9595c4b3..d4510ae9 100644 --- a/Assets/Scripts/ITouchInputConsumer.cs +++ b/Assets/Scripts/ITouchInputConsumer.cs @@ -2,7 +2,11 @@ public interface ITouchInputConsumer { - void OnTouchPress(Vector2 screenPosition); - void OnTouchPosition(Vector2 screenPosition); + void OnTap(Vector2 position); + void OnDragStart(Vector2 position); + void OnDrag(Vector2 position); + void OnDragEnd(Vector2 position); + void OnHoldStart(Vector2 position); + void OnHold(Vector2 position); + void OnHoldEnd(Vector2 position); } - diff --git a/Assets/Scripts/InputManager.cs b/Assets/Scripts/InputManager.cs index e4372189..1795bf39 100644 --- a/Assets/Scripts/InputManager.cs +++ b/Assets/Scripts/InputManager.cs @@ -31,6 +31,14 @@ public class InputManager : MonoBehaviour 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() { _instance = this; @@ -76,46 +84,53 @@ public class InputManager : MonoBehaviour private void OnTouchPressStarted(InputAction.CallbackContext ctx) { // Touch started (finger down) - Vector3 _screenPos = Camera.main.ScreenToWorldPoint(touchPositionAction.ReadValue()); - Vector2 screenPos = new Vector2(_screenPos.x, _screenPos.y); - lastFrameInteracted = TryDelegateToInteractable(screenPos); + Vector2 screenPos = touchPositionAction.ReadValue(); + Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); + Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); + lastFrameInteracted = TryDelegateToInteractable(worldPos2D); if (!lastFrameInteracted) - defaultConsumer?.OnTouchPress(screenPos); - isTouchHeld = true; + { + pressStartPosition = screenPos; + pressStartTime = Time.time; + isPressed = true; + isDragging = false; + defaultConsumer?.OnHoldStart(worldPos2D); + } } private void OnTouchPressCanceled(InputAction.CallbackContext ctx) { - // Touch released (finger up) - isTouchHeld = false; - // Reset lastFrameInteracted for next frame + Vector2 screenPos = touchPositionAction.ReadValue(); + Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); + Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); + if (!lastFrameInteracted) + { + 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; - // Optionally, you can notify consumers of release if needed } private void OnTouchPositionPerformed(InputAction.CallbackContext ctx) { - Vector2 pos = ctx.ReadValue(); - if (isTouchHeld) - { - // Convert to world position - Vector3 worldPos = Camera.main.ScreenToWorldPoint(pos); - Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); - if (!lastFrameInteracted) - defaultConsumer?.OnTouchPress(worldPos2D); // Move continuously to finger position - } + // No longer needed, OnTouchHeld will be handled in Update } void Update() { - // Continuously advertise the last touch position while held - if (isTouchHeld && touchPositionAction != null) + if (isPressed && touchPositionAction != null) { - Vector2 pos = touchPositionAction.ReadValue(); - Vector3 worldPos = Camera.main.ScreenToWorldPoint(pos); + Vector2 screenPos = touchPositionAction.ReadValue(); + Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); - if (!lastFrameInteracted) - defaultConsumer?.OnTouchPress(worldPos2D); + defaultConsumer?.OnHold(worldPos2D); } } @@ -128,7 +143,7 @@ public class InputManager : MonoBehaviour var interactable = hit.GetComponent(); if (interactable != null) { - interactable.OnTouchPress(worldPos); + interactable.OnTap(worldPos); return true; } } diff --git a/Assets/Scripts/Interactable.cs b/Assets/Scripts/Interactable.cs index ab510edd..af58399b 100644 --- a/Assets/Scripts/Interactable.cs +++ b/Assets/Scripts/Interactable.cs @@ -13,18 +13,19 @@ public class Interactable : MonoBehaviour, ITouchInputConsumer stepBehaviour = GetComponent(); } - // Called by InputManager when this interactable is clicked/touched - public void OnTouchPress(Vector2 worldPosition) + // Implement new ITouchInputConsumer contract + public void OnTap(Vector2 worldPosition) { - // Defer lock check to follower arrival - Debug.Log($"[Interactable] OnTouchPress at {worldPosition} on {gameObject.name}"); + Debug.Log($"[Interactable] OnTap at {worldPosition} on {gameObject.name}"); StartedInteraction?.Invoke(); } - public void OnTouchPosition(Vector2 screenPosition) - { - // Optionally handle drag/move here - } + public void OnDragStart(Vector2 worldPosition) { } + public void OnDrag(Vector2 worldPosition) { } + public void OnDragEnd(Vector2 worldPosition) { } + public void OnHoldStart(Vector2 worldPosition) { /* No hold behavior for interactables */ } + public void OnHold(Vector2 worldPosition) { /* No hold behavior for interactables */ } + public void OnHoldEnd(Vector2 worldPosition) { /* No hold behavior for interactables */ } // Called when the follower arrives at this interactable public bool OnFollowerArrived(FollowerController follower) diff --git a/Assets/Scripts/PlayerTouchController.cs b/Assets/Scripts/PlayerTouchController.cs index 1a666ef4..cb74827b 100644 --- a/Assets/Scripts/PlayerTouchController.cs +++ b/Assets/Scripts/PlayerTouchController.cs @@ -17,12 +17,21 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer 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(); @@ -49,37 +58,90 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer InputManager.Instance?.SetDefaultConsumer(this); } - void OnEnable() + // Implement new ITouchInputConsumer contract + public void OnTap(Vector2 worldPosition) { - // No longer register here + 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; + } } - // Remove Update and HandleInput - - public void OnTouchPress(Vector2 worldPosition) + // --- Hold-based tracking --- + public void OnHoldStart(Vector2 worldPosition) { - // If moving to pickup, interrupt - InterruptMoveTo(); - Debug.Log($"PlayerTouchController.OnTouchPress received worldPosition: {worldPosition}"); - SetTargetPosition(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 OnTouchPosition(Vector2 screenPosition) + public void OnHold(Vector2 worldPosition) { - Debug.Log($"PlayerTouchController.OnTouchPosition called with screenPosition: {screenPosition}"); - // Optionally handle drag/move here + 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}"); + 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; - Debug.Log($"AIPath destination set to {targetPosition}"); + aiPath.canMove = true; + aiPath.isStopped = false; + Debug.Log($"[PlayerTouchController] AIPath destination set to {targetPosition}, canMove={aiPath.canMove}, isStopped={aiPath.isStopped}, enabled={aiPath.enabled}"); } else { @@ -87,12 +149,41 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer } } + 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() { - // Update animator speed parameter if (animator != null && aiPath != null) { - float normalizedSpeed = aiPath.velocity.magnitude / aiPath.maxSpeed; + 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)); } } @@ -113,6 +204,9 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer public void InterruptMoveTo() { interruptMoveTo = true; + isDragging = false; + directMoveVelocity = Vector3.zero; + if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && aiPath != null) aiPath.enabled = false; OnMoveToCancelled?.Invoke(); } @@ -143,4 +237,16 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer 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"); + } }