- scripts/touch_controls.gd: full-screen layer routing multi-touch by touch index: floating joystick on the left half (analog move_* actions), look area on the right half, FIRE/GRENADE/RELOAD/JUMP buttons. Shown automatically on web_android/web_ios or a touchscreen; overridable with --touch/--no-touch and ?touch=1/?touch=0. The root Control uses MOUSE_FILTER_IGNORE on purpose: a STOP control swallows the first click and the Web start gate never fires. - scripts/player.gd: touch_mode disables mouse look and pointer lock; view rotation moved to apply_look() shared by mouse and touch; reload and grenade are polled now (is_action_just_pressed) like shoot/jump, because an InputEventAction synthesised inside another event's dispatch does not reach _unhandled_input; start_reload() extracted; get_vector uses an explicit 0.2 dead zone so the joystick keeps its full travel. - scripts/main.gd: creates the layer when needed, accepts a screen touch as the start gesture (which also unlocks browser audio) and enables the layer only afterwards. - tools/test_touch_controls.gd: 11 assertions (joystick movement, look rotation, simultaneous multi-touch, fire hold/release, reload, grenade, jump, detection) runnable headless and on a real display. - docs/TOUCH-CONTROLS.md: detection rules, input model, verification.
3.5 KiB
Touch controls (mobile browser)
On-screen controls for phones and tablets, shown automatically when needed.
When they appear
scripts/touch_controls.gd decides, in this order:
OS.has_feature("web_android") or OS.has_feature("web_ios")— browser on a phone/tablet. This is the documented way: in a Web exportOS.has_feature("mobile")is false, and theweb_*tags are the only ones that describe the browser's host OS (feature tags).DisplayServer.is_touchscreen_available()— native mobile, or a desktop/laptop touchscreen. Careful:display_server_web.cppreturns true here also whenemulate_touch_from_mouseis on, so it is never used alone.- Overrides:
--touch/--no-touchon the command line, and?touch=1/?touch=0on the Web URL. With--touchon a desktop the mouse drags are treated as touches, so the UI can be tried without a touch device.
When touch mode is on, main.gd sets player.touch_mode, no pointer lock is requested and
Input.set_emulate_mouse_from_touch(false) is applied: the emulated mouse would otherwise move the
view twice and fire the weapon on any touch.
Layout and input model
One full-screen Control routes multi-touch by touch index (InputEventScreenTouch /
InputEventScreenDrag), which is what the browser sends (display_server_web.cpp feeds exactly
these events into parse_input_event); emulated mouse events are not used, because mouse emulation
carries a single pointer and would break simultaneous aiming and firing.
- left half: floating joystick (its base recenters where the finger lands), feeds the analog
move_*actions; - right half: look area, drag rotates the view through
player.apply_look(); - bottom right: FIRE (hold to keep shooting), GRENADE, RELOAD, JUMP.
Movement uses the existing InputMap actions, so keyboard, gamepad and touch share one path. Reload,
grenade and jump are read by polling (Input.is_action_just_pressed) instead of by event handling:
an InputEventAction synthesised inside the dispatch of another event (which is what a touch looks
like) does not reliably reach _unhandled_input. Note that such a press is seen by
_physics_process one physics frame later.
Input.get_vector(..., 0.2) uses an explicit small dead zone: the 0.5 dead zone configured for the
actions would waste half of the joystick travel (keyboard input is digital, so nothing changes for
it).
Verification
godot --headless --path . --script res://tools/test_touch_controls.gd -- --touch
godot --headless --path . --script res://tools/test_touch_controls.gd -- --no-touch
DISPLAY=:0 godot --display-driver x11 --rendering-driver opengl3 --path . \
--script res://tools/test_touch_controls.gd -- --touch
The first and third run the same assertions (joystick movement, look rotation, simultaneous
multi-touch, fire hold/release, reload, grenade, jump); the third goes through the real event
pipeline. In --headless the DisplayServer window is 64x36, so the canvas_items stretch transform
rescales injected coordinates (a (200,500) touch arrives as (4000,9720)); the test detects this and
drives the handlers directly in viewport space instead.
Web build notes
- The first tap also unlocks audio in the browser, so
main.gdaccepts either a mouse press or a screen touch as the start gesture and enables the touch layer only after it. - The HTML shell already ships
user-scalable=no; no extra page-level CSS is required.