Godot Touch Input for Mobile Web: Events, Emulation, and UI Scaling
Godot Touch Input for Mobile Web: Events, Emulation, and UI Scaling
Honesty note: this is Godot’s documented input model as of 4.x, mapped for mobile-web use. I built my earlier Godot prototype with desktop-style input and hit the touch problems this post fixes — the honest framing is “what to check before your first phone test,” not a claim of shipped mobile games.
TL;DR
- Godot input is unified:
InputEventScreenTouch(tap) andInputEventScreenDrag(drag) are the touch events;_input()/_unhandled_input()receive them. The engine emulates mouse from touch by default (emulate_mouse_from_touch), so mouse-based code often works on mobile without changes — until multi-touch or precision matters. - The three touch gotchas: (1)
emulate_mouse_from_touchcan fire both touch and mouse events — guard against double-handling; (2) multi-touch needsemulate_touch_from_mouseoff and real touch events — a pinch/dual-tap needsInputEventScreenTouch.index; (3) touch targets must be big — a 40px button is a fat-finger miss; design for ~48px+ on mobile. - Actions are the right interface: map your controls to
InputMapactions (e.g.swipe_left,tap_play) once, then trigger them from both mouse and touch in one place — your game logic never cares about the input source (action-based pattern). - UI scaling is the second half: Godot’s stretch modes (
canvas_itemswithexpand, orviewportkeep-aspect) keep your game fitting phones and desktops; without the right stretch settings, touch coordinates and UI anchors drift apart on different screens (responsive reality).
The event model (30 seconds)
func _input(event):
if event is InputEventScreenTouch:
if event.pressed:
_on_tap(event.position, event.index) # index = which finger
elif event is InputEventScreenDrag:
_on_drag(event.position, event.relative, event.index)
_input()gets events before nodes;_unhandled_input()gets what nodes didn’t consume — use the latter for UI-first handling.emulate_mouse_from_touch(default on) turns taps into mouse clicks for compatibility — great for desktop-tested code, but see gotcha 1.emulate_touch_from_mousedoes the reverse for desktop testing — turn it on in Project Settings to debug touch code without a phone.
The three gotchas (with fixes)
- Double-handling — with emulation on, a tap can arrive as both
InputEventScreenTouchand a synthesizedInputEventMouseButton. Fix: handle either mouse or touch, not both; or detect and ignore the synthetic pair (checkevent.deviceand your settings). - Multi-touch — emulation is single-finger; pinch/two-thumb needs real touch events and per-finger
index. Turn emulation off where multi-touch matters. - Touch target size — a desktop-ported 32px button is a mobile miss. Enforce ~48px minimum targets and spacing in your UI layout (size guide).
Actions: the one interface that survives ports
# Project Settings → Input Map
# tap_play: mouse left + touch tap
# swipe_left: touch drag left + arrow key (desktop testing)
func _unhandled_input(event):
if event.is_action_pressed("tap_play"):
_start_game()
Mapping inputs to actions once, then checking actions in game code, means your logic is input-source-agnostic — the same build works on touch mobile and mouse desktop without if event is ... forks everywhere (the pattern).
UI scaling (the second half)
Godot’s display/window settings decide how your game fits a phone:
- Stretch mode
canvas_items(recommended for 2D): UI and world scale together with the viewport — set base size, thenexpandaspect for phones. - Stretch mode
viewport: renders at fixed resolution, scales the whole output (crisper/denser on mobile, but text can shrink). - Anchors (Control nodes) should do the layout math — no absolute positions for UI that must survive rotation and aspect changes.
Test on: a phone-sized window, a tablet ratio, and desktop — the same game should be playable and touchable in all three (60fps budget).
Pitfalls
- Trusting desktop tests — mouse works; touch needs real-device or emulation testing; enable
emulate_touch_from_mouseand test in the browser. - Double-firing input — emulation + manual touch handling = two actions per tap; pick one path.
- Small touch targets — 32px buttons are unplayable on mobile; size for fingers.
- No action map —
if event is InputEventMouseButtonforks in game logic make input-source changes a rewrite. - Wrong stretch mode — the game renders off-screen or UI anchors drift on phones; set
canvas_items+expandand test ratios.
Bottom line
Godot touch input for mobile web is: real touch events (ScreenTouch/ScreenDrag) under a mouse-emulation default that works for simple ports, actions as the single input interface, and stretch mode + anchors for the UI half. Test on real mobile or with emulate_touch_from_mouse, guard the double-fire, size targets for fingers — and your Godot game plays the same on a phone and a desktop (the full engine comparison).