Files
zombie-fps/docs/TOUCH-CONTROLS.md
T
enne2 6b2779223f Add on-screen touch controls for mobile browsers
- 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.
2026-10-10 23:52:27 +02:00

66 lines
3.5 KiB
Markdown

# 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:
1. `OS.has_feature("web_android") or OS.has_feature("web_ios")` — browser on a phone/tablet.
This is the documented way: in a Web export `OS.has_feature("mobile")` is **false**, and the
`web_*` tags are the only ones that describe the browser's host OS
([feature tags](https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html)).
2. `DisplayServer.is_touchscreen_available()` — native mobile, or a desktop/laptop touchscreen.
Careful: `display_server_web.cpp` returns true here also when `emulate_touch_from_mouse` is on,
so it is never used alone.
3. Overrides: `--touch` / `--no-touch` on the command line, and `?touch=1` / `?touch=0` on the Web
URL. With `--touch` on 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
```bash
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.gd` accepts 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.