Files
match3-unity/Assets/Scripts/Services/InputService.cs

126 lines
4.3 KiB
C#

using System;
using Services.Interfaces;
using UnityEngine;
namespace Services {
public class InputService : MonoBehaviour, IInputService
{
public event Action<Vector2Int, Vector2Int> OnSwapRequested;
public event Action<Vector2Int> OnSwitchRequested;
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 bool isDown, out Vector2 screenPos))
{
if (!this.isPointerDown && isDown) {
this.pointerDownScreenPos = screenPos;
}
if (this.isPointerDown && !isDown) {
TryEmitSwap(this.pointerDownScreenPos, screenPos);
}
this.isPointerDown = isDown;
}
else
{
// No pointer available this frame (rare). Ensure we don't get stuck.
this.isPointerDown = false;
}
#if UNITY_EDITOR
if (TryGetSecondaryPointerClick(out screenPos)) {
OnSwitchRequested?.Invoke(WorldToCell(this.inputCamera.ScreenToWorldPoint(screenPos)));
}
#endif
}
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;
}
private static bool TryGetSecondaryPointerClick(out Vector2 screenPosition) {
if (Input.GetMouseButtonDown(1)) {
screenPosition = Input.mousePosition;
return true;
}
screenPosition = Input.mousePosition;
return false;
}
}
}