Part of these game systems:
beginner Touch & Mobile

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.

Unity 2022.3+ · 3.6 KB · TapInputHandler.cs

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

TapInputHandler.cs
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;
    }
}
Ready for more? Touch Joystick Virtual on-screen joystick for mobile touch input with dynamic or fixed positioning.