Implementing Touch Controls for Mobile Games (Taps, Swipes, Gestures)
Unity handles touches on mobile devices through Input.GetTouch() or the new InputSystem—and there's a big gap between them. The old API doesn't distinguish a tap from the start of a swipe until the finger lifts. For games where response to touch must be instantaneous, this is unacceptable.
Where Most Implementations Break
The most common mistake: recognizing gestures based on TouchPhase.Ended. Developers compare positions at Began and Ended, calculate a vector—and get a swipe. Works on the developer's device at 60 FPS. On a 30 FPS phone with throttling due to heat (typical for budget Android devices), the delta between frames grows, and a short tap gets classified as a swipe based on a timer.
The correct approach: track movement in Stationary and Moved phases, add a distance threshold (sqrMagnitude > threshold) and time threshold (Time.time - touchStartTime < tapMaxDuration). Unity doesn't provide this out of the box—you need your own GestureRecognizer.
In a Godot 4 project with InputEventScreenTouch and InputEventScreenDrag, the situation is slightly better: the engine separates events at the API level. But multitouch is a different story. The index in InputEventScreenTouch gives the finger order, and if you remove a finger quickly, the index-es can remap, breaking two-finger gesture logic.
Building a Gesture System
For Unity, write a TouchInputManager as a singleton MonoBehaviour that iterates through Input.touches on each frame in Update() and maps touches to state machines—one FSM per active fingerId. States: Idle → Pressing → Tapping/Swiping/Holding. Transitions depend on distance and time.
Output: events OnTap(Vector2 position), OnSwipe(Vector2 direction, float velocity), OnHold(Vector2 position, float duration), OnPinch(float delta). Game systems subscribe to these events through C# delegates or UnityEvent, unaware of Input.GetTouch.
For Flutter games using the flame engine, use TapDetector, PanDetector, ScaleDetector from the flame package. They work correctly on top of Flutter's GestureArena—each detector participates in gesture arbitration, and the winner is determined by priority. Important: ScaleDetector and PanDetector conflict unless you set behavior: HitTestBehavior.opaque on the parent widget.
Real Case: Swipe Attacks in RPGs
In one project (top-down RPG, Unity 2022.3 LTS), we needed directional swipe attacks with eight directions. Simple vector normalization gave unstable results—diagonal directions registered less often because users rarely swipe exactly at 45°. Solution: ±30° tolerance zones instead of standard ±22.5°, plus weighting by swipe speed—fast swipes less accurate, slow swipes more accurate. After this, correct recognition rates jumped from 78% to 94% according to Firebase Analytics (custom event gesture_recognized).
Additionally, we added visual feedback through LineRenderer that draws a swipe trail with fade. Without it, players don't know if the game accepted their gesture.
Multitouch and Edge Cases
Pinch-zoom is implemented via the distance between two Touch objects with different fingerIds. The main pitfall: when a third finger touches the screen during a pinch. Without handling this scenario, fingerIds shift and zoom jumps. Solution: lock the two finger fingerIds at pinch start and ignore all new touches until the gesture completes.
On Android additionally check MotionEvent.ACTION_POINTER_DOWN and ACTION_POINTER_UP via a native plugin if you need access to pressure and contact area—Unity Input.GetTouch doesn't provide this data in full.
Timeline and Scope
Basic system (tap, swipe, hold) for one platform: 2–4 days. Full system with multitouch, pinch, custom gestures, and game mechanic integration: 1–2 weeks. Cost calculated individually after analyzing project requirements.







