Update touch input settings

This commit is contained in:
Michal Pikulski
2025-09-05 14:10:42 +02:00
parent 32f09f4755
commit 5d3a587b5e
9 changed files with 235 additions and 56 deletions

View File

@@ -22,6 +22,7 @@ MonoBehaviour:
moveSpeed: 20 moveSpeed: 20
stopDistance: 0.1 stopDistance: 0.1
useRigidbody: 1 useRigidbody: 1
defaultHoldMovementMode: 1
followUpdateInterval: 0.1 followUpdateInterval: 0.1
followerSpeedMultiplier: 1.2 followerSpeedMultiplier: 1.2
heldIconDisplayHeight: 2 heldIconDisplayHeight: 2

View File

@@ -23,6 +23,15 @@
"processors": "", "processors": "",
"interactions": "", "interactions": "",
"initialStateCheck": false "initialStateCheck": false
},
{
"name": "TouchDelta",
"type": "Value",
"id": "99ae0aa3-a70e-4afc-8a8a-c7d6144fa75b",
"expectedControlType": "Vector2",
"processors": "",
"interactions": "",
"initialStateCheck": true
} }
], ],
"bindings": [ "bindings": [
@@ -41,12 +50,23 @@
"name": "", "name": "",
"id": "f3dcd77f-15e5-4621-af67-001e6b08e3e6", "id": "f3dcd77f-15e5-4621-af67-001e6b08e3e6",
"path": "<Touchscreen>/Press", "path": "<Touchscreen>/Press",
"interactions": "Hold", "interactions": "Press(behavior=2)",
"processors": "", "processors": "",
"groups": "", "groups": "",
"action": "TouchPress", "action": "TouchPress",
"isComposite": false, "isComposite": false,
"isPartOfComposite": false "isPartOfComposite": false
},
{
"name": "",
"id": "19cc6a3f-9316-4da5-8e35-8297e02b8f6c",
"path": "<Touchscreen>/delta",
"interactions": "",
"processors": "",
"groups": "",
"action": "TouchDelta",
"isComposite": false,
"isPartOfComposite": false
} }
] ]
} }

View File

@@ -20,19 +20,48 @@ public class EndlessDescenderController : MonoBehaviour, ITouchInputConsumer
isTouchActive = false; 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); targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
isTouchActive = true; 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 // Update target x as finger moves
targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax); targetFingerX = Mathf.Clamp(worldPosition.x, GameManager.Instance.EndlessDescenderClampXMin, GameManager.Instance.EndlessDescenderClampXMax);
} }
public void OnHoldEnd(Vector2 worldPosition)
{
// Stop hold
isTouchActive = false;
}
void Update() void Update()
{ {
if (!isTouchActive) return; if (!isTouchActive) return;

View File

@@ -74,4 +74,5 @@ public class GameManager : MonoBehaviour
public float EndlessDescenderClampXMin => gameSettings != null ? gameSettings.endlessDescenderClampXMin : -5f; public float EndlessDescenderClampXMin => gameSettings != null ? gameSettings.endlessDescenderClampXMin : -5f;
public float EndlessDescenderClampXMax => gameSettings != null ? gameSettings.endlessDescenderClampXMax : 5f; public float EndlessDescenderClampXMax => gameSettings != null ? gameSettings.endlessDescenderClampXMax : 5f;
public float EndlessDescenderSpeedExponent => gameSettings != null ? gameSettings.endlessDescenderSpeedExponent : 2.5f; public float EndlessDescenderSpeedExponent => gameSettings != null ? gameSettings.endlessDescenderSpeedExponent : 2.5f;
public GameSettings.HoldMovementMode DefaultHoldMovementMode => gameSettings != null ? gameSettings.defaultHoldMovementMode : GameSettings.HoldMovementMode.Pathfinding;
} }

View File

@@ -18,6 +18,8 @@ public class GameSettings : ScriptableObject
public float moveSpeed = 5f; public float moveSpeed = 5f;
public float stopDistance = 0.1f; public float stopDistance = 0.1f;
public bool useRigidbody = true; public bool useRigidbody = true;
public enum HoldMovementMode { Pathfinding, Direct }
public HoldMovementMode defaultHoldMovementMode = HoldMovementMode.Pathfinding;
[Header("Backend Settings")] [Header("Backend Settings")]
[Tooltip("Technical parameters, not for design tuning")] [Tooltip("Technical parameters, not for design tuning")]

View File

@@ -2,7 +2,11 @@
public interface ITouchInputConsumer public interface ITouchInputConsumer
{ {
void OnTouchPress(Vector2 screenPosition); void OnTap(Vector2 position);
void OnTouchPosition(Vector2 screenPosition); void OnDragStart(Vector2 position);
void OnDrag(Vector2 position);
void OnDragEnd(Vector2 position);
void OnHoldStart(Vector2 position);
void OnHold(Vector2 position);
void OnHoldEnd(Vector2 position);
} }

View File

@@ -31,6 +31,14 @@ public class InputManager : MonoBehaviour
private bool isTouchHeld = false; private bool isTouchHeld = false;
private bool lastFrameInteracted = 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() void Awake()
{ {
_instance = this; _instance = this;
@@ -76,46 +84,53 @@ public class InputManager : MonoBehaviour
private void OnTouchPressStarted(InputAction.CallbackContext ctx) private void OnTouchPressStarted(InputAction.CallbackContext ctx)
{ {
// Touch started (finger down) // Touch started (finger down)
Vector3 _screenPos = Camera.main.ScreenToWorldPoint(touchPositionAction.ReadValue<Vector2>()); Vector2 screenPos = touchPositionAction.ReadValue<Vector2>();
Vector2 screenPos = new Vector2(_screenPos.x, _screenPos.y); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
lastFrameInteracted = TryDelegateToInteractable(screenPos); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
lastFrameInteracted = TryDelegateToInteractable(worldPos2D);
if (!lastFrameInteracted) 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) private void OnTouchPressCanceled(InputAction.CallbackContext ctx)
{ {
// Touch released (finger up) Vector2 screenPos = touchPositionAction.ReadValue<Vector2>();
isTouchHeld = false; Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
// Reset lastFrameInteracted for next frame 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; lastFrameInteracted = false;
// Optionally, you can notify consumers of release if needed
} }
private void OnTouchPositionPerformed(InputAction.CallbackContext ctx) private void OnTouchPositionPerformed(InputAction.CallbackContext ctx)
{ {
Vector2 pos = ctx.ReadValue<Vector2>(); // No longer needed, OnTouchHeld will be handled in Update
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
}
} }
void Update() void Update()
{ {
// Continuously advertise the last touch position while held if (isPressed && touchPositionAction != null)
if (isTouchHeld && touchPositionAction != null)
{ {
Vector2 pos = touchPositionAction.ReadValue<Vector2>(); Vector2 screenPos = touchPositionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(pos); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
if (!lastFrameInteracted) defaultConsumer?.OnHold(worldPos2D);
defaultConsumer?.OnTouchPress(worldPos2D);
} }
} }
@@ -128,7 +143,7 @@ public class InputManager : MonoBehaviour
var interactable = hit.GetComponent<Interactable>(); var interactable = hit.GetComponent<Interactable>();
if (interactable != null) if (interactable != null)
{ {
interactable.OnTouchPress(worldPos); interactable.OnTap(worldPos);
return true; return true;
} }
} }

View File

@@ -13,18 +13,19 @@ public class Interactable : MonoBehaviour, ITouchInputConsumer
stepBehaviour = GetComponent<ObjectiveStepBehaviour>(); stepBehaviour = GetComponent<ObjectiveStepBehaviour>();
} }
// Called by InputManager when this interactable is clicked/touched // Implement new ITouchInputConsumer contract
public void OnTouchPress(Vector2 worldPosition) public void OnTap(Vector2 worldPosition)
{ {
// Defer lock check to follower arrival Debug.Log($"[Interactable] OnTap at {worldPosition} on {gameObject.name}");
Debug.Log($"[Interactable] OnTouchPress at {worldPosition} on {gameObject.name}");
StartedInteraction?.Invoke(); StartedInteraction?.Invoke();
} }
public void OnTouchPosition(Vector2 screenPosition) public void OnDragStart(Vector2 worldPosition) { }
{ public void OnDrag(Vector2 worldPosition) { }
// Optionally handle drag/move here 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 // Called when the follower arrives at this interactable
public bool OnFollowerArrived(FollowerController follower) public bool OnFollowerArrived(FollowerController follower)

View File

@@ -17,12 +17,21 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
private Animator animator; private Animator animator;
private Transform artTransform; private Transform artTransform;
// For direct movement mode
private Vector3 directMoveVelocity = Vector3.zero;
public delegate void ArrivedAtTargetHandler(); public delegate void ArrivedAtTargetHandler();
public event ArrivedAtTargetHandler OnArrivedAtTarget; public event ArrivedAtTargetHandler OnArrivedAtTarget;
public event System.Action OnMoveToCancelled; public event System.Action OnMoveToCancelled;
private Coroutine moveToCoroutine; private Coroutine moveToCoroutine;
private bool interruptMoveTo = false; 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() void Awake()
{ {
rb3d = GetComponent<Rigidbody>(); rb3d = GetComponent<Rigidbody>();
@@ -49,37 +58,90 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
InputManager.Instance?.SetDefaultConsumer(this); 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 // --- Hold-based tracking ---
public void OnHoldStart(Vector2 worldPosition)
public void OnTouchPress(Vector2 worldPosition)
{ {
// If moving to pickup, interrupt pendingTap = false;
InterruptMoveTo(); lastDragPosition = worldPosition;
Debug.Log($"PlayerTouchController.OnTouchPress received worldPosition: {worldPosition}"); isDragging = true;
SetTargetPosition(worldPosition); 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}"); if (!isDragging) return;
// Optionally handle drag/move here 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) 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); targetPosition = new Vector3(worldPosition.x, worldPosition.y, transform.position.z);
hasTarget = true; hasTarget = true;
if (aiPath != null) if (aiPath != null)
{ {
aiPath.destination = targetPosition; aiPath.destination = targetPosition;
aiPath.maxSpeed = GameManager.Instance.MoveSpeed; 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 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() void Update()
{ {
// Update animator speed parameter
if (animator != null && aiPath != null) 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)); animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed));
} }
} }
@@ -113,6 +204,9 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
public void InterruptMoveTo() public void InterruptMoveTo()
{ {
interruptMoveTo = true; interruptMoveTo = true;
isDragging = false;
directMoveVelocity = Vector3.zero;
if (GameManager.Instance.DefaultHoldMovementMode == GameSettings.HoldMovementMode.Direct && aiPath != null) aiPath.enabled = false;
OnMoveToCancelled?.Invoke(); OnMoveToCancelled?.Invoke();
} }
@@ -143,4 +237,16 @@ public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer
OnArrivedAtTarget?.Invoke(); 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");
}
} }