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.
This commit is contained in:
+40
-4
@@ -4,10 +4,12 @@ const ZombieScript = preload("res://scripts/zombie.gd")
|
||||
const RoomScript = preload("res://scripts/room.gd")
|
||||
const MUSIC_PATH := "res://assets/audio/music/zombie_industrial_loop.ogg"
|
||||
const MUSIC_VOLUME_DB := -11.0
|
||||
const TouchControls = preload("res://scripts/touch_controls.gd")
|
||||
var player: CharacterBody3D
|
||||
var room: Node3D
|
||||
var music: AudioStreamPlayer
|
||||
var music_started := false
|
||||
var touch: Node
|
||||
var hud: Label
|
||||
var score := 0
|
||||
var wave := 0
|
||||
@@ -54,10 +56,14 @@ func _ready() -> void:
|
||||
canvas.add_child(cross)
|
||||
cross.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
|
||||
_setup_music()
|
||||
_setup_touch()
|
||||
if OS.has_feature("web"):
|
||||
awaiting_web_start = true
|
||||
if touch != null:
|
||||
touch.enabled = false # il primo tocco/click avvia la partita
|
||||
start_label = Label.new()
|
||||
start_label.text = "CLICCA PER INIZIARE\nWASD: movimento · clic sinistro: spara · destro: granata · R: ricarica"
|
||||
var howto := "trascina a sinistra: muovi · destra: guarda · pulsanti: fuoco/granata/ricarica/salto" if player.touch_mode else "WASD: movimento · clic sinistro: spara · destro: granata · R: ricarica"
|
||||
start_label.text = ("TOCCA PER INIZIARE" if player.touch_mode else "CLICCA PER INIZIARE") + "\n" + howto
|
||||
start_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
start_label.add_theme_font_size_override("font_size", 24)
|
||||
start_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
@@ -72,14 +78,42 @@ func _ready() -> void:
|
||||
get_tree().quit())
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if awaiting_web_start and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if not awaiting_web_start:
|
||||
return
|
||||
var started := false
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
started = true
|
||||
elif event is InputEventScreenTouch and event.pressed:
|
||||
started = true # primo tocco su mobile
|
||||
if started:
|
||||
awaiting_web_start = false
|
||||
start_label.queue_free()
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
# Il browser concede l'audio solo dopo un gesto dell'utente: qui il click.
|
||||
if not player.touch_mode:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
if touch != null:
|
||||
touch.enabled = true
|
||||
# Il browser concede l'audio solo dopo un gesto dell'utente: qui il click/tocco.
|
||||
_start_music()
|
||||
_next_wave()
|
||||
|
||||
## Controlli on-screen: attivi su browser mobile o touchscreen, forzabili con
|
||||
## --touch / --no-touch sulla riga di comando e con ?touch=1 / ?touch=0 sul Web.
|
||||
func _setup_touch() -> void:
|
||||
var forced := TouchControls.force_mode()
|
||||
if not (forced > 0 or (forced == 0 and TouchControls.should_auto_enable())):
|
||||
return
|
||||
player.touch_mode = true
|
||||
# Su un dispositivo touch il pointer lock non serve e su desktop forzato
|
||||
# ruberebbe il cursore: nessuna cattura del mouse in modalita' touch.
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
touch = CanvasLayer.new()
|
||||
touch.set_script(TouchControls)
|
||||
touch.player = player
|
||||
# Su desktop forzato con --touch i drag del mouse valgono come tocchi,
|
||||
# cosi' la UI e' provabile anche senza dispositivo touch.
|
||||
touch.mouse_fallback = forced > 0 and not DisplayServer.is_touchscreen_available()
|
||||
add_child(touch)
|
||||
|
||||
## Colonna sonora: industrial metal strumentale su loop senza giunzione udibile.
|
||||
func _setup_music() -> void:
|
||||
music = AudioStreamPlayer.new()
|
||||
@@ -109,6 +143,8 @@ func _process(_dt: float) -> void:
|
||||
hud.text += "\nGRANATA: " + ("PRONTA" if player.grenade_cooldown <= 0 else "%d s" % ceili(player.grenade_cooldown))
|
||||
if player.reloading:
|
||||
hud.text += "\nRICARICA..."
|
||||
if player.touch_mode:
|
||||
hud.text += "\ntrascina a sinistra: muovi · a destra: guarda"
|
||||
if player.hp <= 0:
|
||||
hud.text += "\nSEI MORTO — premi R per ricominciare"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user