109 lines
3.7 KiB
C#
109 lines
3.7 KiB
C#
using System;
|
|
using Services.Interfaces;
|
|
using UnityEngine;
|
|
|
|
namespace Services {
|
|
public class InputService : MonoBehaviour, IInputService
|
|
{
|
|
public event Action<Vector2Int, Vector2Int> OnSwapRequested;
|
|
|
|
private Camera inputCamera;
|
|
private Vector2 pointerDownScreenPos;
|
|
private bool isPointerDown;
|
|
|
|
private readonly Vector2 boardOrigin = Vector2.zero;
|
|
private readonly float minDrag = 0.35f;
|
|
private readonly float cellSize = 1f;
|
|
|
|
private void Awake() {
|
|
this.inputCamera = Camera.main;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (TryGetPrimaryPointerState(out var isDown, out var screenPos))
|
|
{
|
|
if (!this.isPointerDown && isDown) {
|
|
pointerDownScreenPos = screenPos;
|
|
}
|
|
|
|
if (this.isPointerDown && !isDown) {
|
|
TryEmitSwap(pointerDownScreenPos, screenPos);
|
|
}
|
|
|
|
this.isPointerDown = isDown;
|
|
}
|
|
else
|
|
{
|
|
// No pointer available this frame (rare). Ensure we don't get stuck.
|
|
this.isPointerDown = false;
|
|
}
|
|
}
|
|
|
|
private void TryEmitSwap(Vector2 downScreen, Vector2 upScreen)
|
|
{
|
|
if (this.inputCamera is null) return;
|
|
|
|
Vector2 downWorld = this.inputCamera.ScreenToWorldPoint(downScreen);
|
|
Vector2 upWorld = this.inputCamera.ScreenToWorldPoint(upScreen);
|
|
Vector2 dragWorld = upWorld - downWorld;
|
|
|
|
if (dragWorld.magnitude < this.minDrag)
|
|
return;
|
|
|
|
Vector2Int fromCell = WorldToCell(downWorld);
|
|
Vector2Int dir = DragToCardinalDirection(dragWorld);
|
|
Vector2Int toCell = fromCell + dir;
|
|
|
|
// Adjacency is guaranteed by construction (to = from + dir).
|
|
// Debug.Log($"Swap {fromCell} -> {toCell}");
|
|
OnSwapRequested?.Invoke(fromCell, toCell);
|
|
}
|
|
|
|
private Vector2Int WorldToCell(Vector2 worldPos)
|
|
{
|
|
// Convert to board-local coords first
|
|
Vector2 local = (worldPos - this.boardOrigin) / this.cellSize;
|
|
|
|
// Assumes cell centers lie on integer coordinates in local space
|
|
int x = Mathf.FloorToInt(local.x + 0.5f);
|
|
int y = Mathf.FloorToInt(local.y + 0.5f);
|
|
|
|
return new Vector2Int(x, y);
|
|
}
|
|
|
|
private static Vector2Int DragToCardinalDirection(Vector2 dragWorld)
|
|
{
|
|
if (Mathf.Abs(dragWorld.x) >= Mathf.Abs(dragWorld.y))
|
|
return dragWorld.x >= 0 ? Vector2Int.right : Vector2Int.left;
|
|
|
|
return dragWorld.y >= 0 ? Vector2Int.up : Vector2Int.down;
|
|
}
|
|
|
|
private static bool TryGetPrimaryPointerState(out bool isDown, out Vector2 screenPosition)
|
|
{
|
|
// Prefer touch when present (mobile)
|
|
if (Input.touchCount > 0)
|
|
{
|
|
Touch touch = Input.GetTouch(0);
|
|
screenPosition = touch.position;
|
|
|
|
// Treat "Moved/Stationary" as still down, and "Began" as down.
|
|
isDown = touch.phase == TouchPhase.Began
|
|
|| touch.phase == TouchPhase.Moved
|
|
|| touch.phase == TouchPhase.Stationary;
|
|
|
|
// Ended/Canceled => not down (we still report position)
|
|
if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
|
|
isDown = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Fallback to mouse (editor/desktop)
|
|
screenPosition = Input.mousePosition;
|
|
isDown = Input.GetMouseButton(0);
|
|
return true;
|
|
}
|
|
}
|
|
} |