Touch and Gestures
Raw fingers and recognised gestures, for the cases an action map can't express — pinch-to-zoom, two-finger rotate, swipe-to-dismiss, your own hit-testing.
Most touch input shouldn't come through here
Bind Touch/Stick0 and Touch/Button0 in your .jinput and your existing action code works on a phone unchanged. This page is the advanced path. See the Input guide.
Touchscreen
Touchscreen.TouchCount
How many fingers are on the screen right now.
Touchscreen.GetTouch(int index)
The touch at index (0 to TouchCount - 1). Order is roughly arrival order among active contacts — it is not a Touch.Id. Out of range returns a neutral default touch with Id of -1.
Touchscreen.GetTouches(Touch[] buffer)
Fills your buffer and returns how many were written. This is the one to use in OnUpdate.
public class Pincher : GameEntity
{
private readonly Touch[] m_Touches = new Touch[10];
protected override void OnUpdate(float ts)
{
int count = Touchscreen.GetTouches(m_Touches);
for (int i = 0; i < count; i++)
{
Touch t = m_Touches[i];
if (t.Phase == TouchPhase.Moved)
Drag(t.Position, t.Delta);
}
}
}A buffer shorter than TouchCount truncates rather than throwing — the same contract Physics2D.RaycastAll uses.
Touchscreen.Touches
Every active touch as a fresh array.
This allocates on every call
Fine in a menu or a one-off check; wrong in OnUpdate. Use GetTouches(buffer) with a buffer kept in a field — see Performance. The array form is kept because it's much nicer to write when the allocation genuinely doesn't matter.
Touch
A plain value snapshot for one finger, for one frame. Nothing to cache — re-read it fresh each frame.
| Field | What it is |
|---|---|
Id | Stable for the life of this contact, so you can track one finger across frames |
Phase | See below |
Position | Current position in screen pixels |
Delta | Movement since last frame |
StartPosition | Where this contact began |
Pressure | Force, where the device reports it |
StartTime | When the contact began |
TouchPhase
| Value | Meaning |
|---|---|
Began | The finger just went down |
Moved | It moved this frame |
Stationary | Still down, didn't move |
Ended | The player lifted it |
Cancelled | The system took it away |
Handle Cancelled separately from Ended
Cancelled means a call came in, a system gesture started, or the app lost focus mid-touch — the player did not deliberately let go. Treating it as Ended fires the action they were in the middle of abandoning. Treat it as "abort this interaction".
Gestures
Recognised engine-side over the raw touch stream — the same on every platform, rather than per-OS.
Gestures.Count
How many gestures are active this frame.
Gestures.GetGesture(int index)
The gesture at index. Out of range returns a default with Type of None.
Gestures.GetActive(Gesture[] buffer)
Fills your buffer, returns how many were written. The allocation-free form — use this per frame.
Gestures.Active
Every active gesture as a fresh array. Allocates per call, same trade-off as Touchscreen.Touches.
public class MapCamera : GameEntity
{
private readonly Gesture[] m_Gestures = new Gesture[8];
private CameraComponent? m_Camera;
protected override void OnUpdate(float ts)
{
int count = Gestures.GetActive(m_Gestures);
for (int i = 0; i < count; i++)
{
Gesture g = m_Gestures[i];
switch (g.Type)
{
case GestureType.Pan:
Translation -= new Vector3(g.Delta.X, -g.Delta.Y, 0) * 0.01f;
break;
case GestureType.Pinch:
m_Camera!.OrthographicSize /= g.Scale;
break;
case GestureType.DoubleTap:
ResetView();
break;
}
}
}
}Gesture
| Field | What it is |
|---|---|
Type | See below |
Position | The gesture's position, or the midpoint between both fingers for Pinch and Rotate |
Delta | Pan: this frame's movement. Swipe: total displacement from start to release. Zero otherwise |
Direction | A SwipeDirection — Swipe only |
Scale | Pinch only. Cumulative since the two-finger contact began; 1.0 is no change |
Rotation | Rotate only. This frame's change, in radians — not cumulative |
Scale and Rotation count differently
Scale accumulates from the start of the pinch; Rotation reports only the current frame. Dividing by Scale every frame compounds and runs away — apply it against the size you had when the pinch began, or convert to a per-frame delta yourself.
GestureType
None, Tap, DoubleTap, LongPress, Swipe, Pan, Pinch, Rotate.
Discrete gestures — Tap, DoubleTap, LongPress, Swipe — appear for exactly one frame. Continuous ones — Pan, Pinch, Rotate — reappear every frame their contacts stay active. None never appears on a real gesture; it's only the out-of-range default.
SwipeDirection
None, Up, Down, Left, Right. Meaningful only when Type is Swipe.
See also
- Input — actions, keyboard, mouse, gamepads
- Input guide — action maps and on-screen controls
- Performance — why the buffer forms exist