Part of these game systems:
Tap Input Handler
Handles single tap, double tap, and long press gestures with configurable timing. Provides tap position in both screen and world space coordinates.
How to Use
1
Create an empty GameObject named TapManager
2
Attach the TapInputHandler script to it
3
Adjust the double-tap window and long press duration in the inspector
4
Connect onSingleTap, onDoubleTap, or onLongPress events to your game methods
5
Use LastTapWorldPosition to spawn objects or trigger actions at tap location
Source Code
C#
using UnityEngine;
using UnityEngine.Events;
public class TapInputHandler : MonoBehaviour
{
[Header("Tap Settings")]
[SerializeField] private float doubleTapWindow = 0.3f;
[SerializeField] private float longPressDuration = 0.5f;
[SerializeField] private float tapMoveTolerance = 10f;
[Header("Events")]
public UnityEvent<Vector2> onSingleTap;
public UnityEvent<Vector2> onDoubleTap;
public UnityEvent<Vector2> onLongPress;
public UnityEvent<Vector3> onSingleTapWorld;
public UnityEvent<int> onMultiTap;
public int TapCount { get; private set; }
public bool IsLongPressing { get; private set; }
public Vector2 LastTapScreenPosition { get; private set; }
public Vector3 LastTapWorldPosition { get; private set; }
private int consecutiveTaps;
private float lastTapTime;
private float touchStartTime;
private Vector2 touchStartPos;
private bool isTouching;
private bool longPressFired;
private void Update()
{
Vector2 inputPos = Vector2.zero;
bool inputDown = false;
bool inputHeld = false;
bool inputUp = false;
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
inputPos = touch.position;
inputDown = touch.phase == TouchPhase.Began;
inputHeld = touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved;
inputUp = touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled;
}
else
{
inputPos = Input.mousePosition;
inputDown = Input.GetMouseButtonDown(0);
inputHeld = Input.GetMouseButton(0);
inputUp = Input.GetMouseButtonUp(0);
}
if (inputDown)
{
touchStartPos = inputPos;
touchStartTime = Time.time;
isTouching = true;
longPressFired = false;
IsLongPressing = false;
}
if (inputHeld && isTouching && !longPressFired)
{
float held = Time.time - touchStartTime;
float moved = Vector2.Distance(inputPos, touchStartPos);
if (held >= longPressDuration && moved < tapMoveTolerance)
{
longPressFired = true;
IsLongPressing = true;
onLongPress?.Invoke(inputPos);
}
}
if (inputUp && isTouching)
{
isTouching = false;
IsLongPressing = false;
if (longPressFired) return;
float moved = Vector2.Distance(inputPos, touchStartPos);
if (moved > tapMoveTolerance) return;
LastTapScreenPosition = inputPos;
LastTapWorldPosition = GetWorldPosition(inputPos);
float timeSinceLastTap = Time.time - lastTapTime;
lastTapTime = Time.time;
if (timeSinceLastTap <= doubleTapWindow)
{
consecutiveTaps++;
TapCount = consecutiveTaps;
onDoubleTap?.Invoke(inputPos);
onMultiTap?.Invoke(consecutiveTaps);
}
else
{
consecutiveTaps = 1;
TapCount = 1;
StartCoroutine(DelayedSingleTap(inputPos));
}
}
}
private System.Collections.IEnumerator DelayedSingleTap(Vector2 pos)
{
yield return new WaitForSeconds(doubleTapWindow);
if (consecutiveTaps == 1)
{
onSingleTap?.Invoke(pos);
onSingleTapWorld?.Invoke(GetWorldPosition(pos));
}
}
private Vector3 GetWorldPosition(Vector2 screenPos)
{
Camera cam = Camera.main;
if (cam == null) return Vector3.zero;
Vector3 screenPoint = new Vector3(screenPos.x, screenPos.y, cam.nearClipPlane + 10f);
return cam.ScreenToWorldPoint(screenPoint);
}
public void ResetTapCount()
{
consecutiveTaps = 0;
TapCount = 0;
}
}