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:
+44
-22
@@ -16,6 +16,9 @@ var weapon_anim: AnimationPlayer
|
||||
var step_timer := 0.0
|
||||
var death_started := false
|
||||
var death_overlay: ColorRect
|
||||
## True quando la partita e' guidata dai controlli on-screen (touch): il mouse
|
||||
## emulato dai tocchi non deve muovere la visuale ne' sparare al posto del dito.
|
||||
var touch_mode := false
|
||||
const Blood = preload("res://scripts/blood_effects.gd")
|
||||
const Audio = preload("res://scripts/hl_audio.gd")
|
||||
|
||||
@@ -66,30 +69,44 @@ func _ready() -> void:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if OS.has_feature("web") and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT and not death_started:
|
||||
if OS.has_feature("web") and not touch_mode and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT and not death_started:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and hp > 0:
|
||||
rotate_y(-event.relative.x * 0.0025)
|
||||
camera.rotation.x = clampf(camera.rotation.x - event.relative.y * 0.0025, -1.4, 1.4)
|
||||
if event is InputEventMouseMotion and not touch_mode and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
apply_look(event.relative)
|
||||
if event.is_action_pressed("pause") and not death_started:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED else Input.MOUSE_MODE_CAPTURED
|
||||
if event.is_action_pressed("grenade") and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and not death_started:
|
||||
|
||||
## Ricarica: tempi invariati, riusata da tastiera (R) e dal pulsante on-screen.
|
||||
func start_reload() -> void:
|
||||
reloading = true
|
||||
weapon_anim.play("reload")
|
||||
Audio.play(self, "items/cliprelease1.wav")
|
||||
get_tree().create_timer(0.8).timeout.connect(func():
|
||||
if not death_started:
|
||||
Audio.play(self, "items/clipinsert1.wav"))
|
||||
get_tree().create_timer(1.57).timeout.connect(func():
|
||||
if not death_started:
|
||||
ammo = 30
|
||||
reloading = false
|
||||
weapon_anim.play("idle1"))
|
||||
|
||||
## Azioni a pressione singola lette a polling: stesso percorso per tastiera,
|
||||
## joypad e pulsanti on-screen. Gli InputEventAction sintetici, generati dentro
|
||||
## il dispatch di un altro evento (come fa un tocco), non arrivano affidabilmente
|
||||
## a _unhandled_input; lo stato delle azioni invece si'.
|
||||
func _handle_actions() -> void:
|
||||
if Input.is_action_just_pressed("reload") and not reloading and ammo < 30:
|
||||
start_reload()
|
||||
if Input.is_action_just_pressed("grenade") and not death_started and (touch_mode or Input.mouse_mode == Input.MOUSE_MODE_CAPTURED):
|
||||
throw_grenade()
|
||||
if event.is_action_pressed("reload"):
|
||||
if hp <= 0:
|
||||
get_tree().reload_current_scene()
|
||||
elif not reloading and ammo < 30:
|
||||
reloading = true
|
||||
weapon_anim.play("reload")
|
||||
Audio.play(self, "items/cliprelease1.wav")
|
||||
get_tree().create_timer(0.8).timeout.connect(func():
|
||||
if not death_started:
|
||||
Audio.play(self, "items/clipinsert1.wav"))
|
||||
get_tree().create_timer(1.57).timeout.connect(func():
|
||||
if not death_started:
|
||||
ammo = 30
|
||||
reloading = false
|
||||
weapon_anim.play("idle1"))
|
||||
|
||||
## Rotazione della visuale. Unico punto in cui vivono sensibilita' e limiti di
|
||||
## inclinazione, condiviso da mouse e controlli touch.
|
||||
func apply_look(relative: Vector2) -> void:
|
||||
if hp <= 0 or death_started:
|
||||
return
|
||||
rotate_y(-relative.x * 0.0025)
|
||||
camera.rotation.x = clampf(camera.rotation.x - relative.y * 0.0025, -1.4, 1.4)
|
||||
|
||||
func _physics_process(dt: float) -> void:
|
||||
cooldown -= dt
|
||||
@@ -97,8 +114,13 @@ func _physics_process(dt: float) -> void:
|
||||
flash.light_energy = maxf(0, flash.light_energy - dt * 60)
|
||||
gun.position.z = lerpf(gun.position.z, 0, dt * 15)
|
||||
if hp <= 0:
|
||||
if Input.is_action_just_pressed("reload"):
|
||||
get_tree().reload_current_scene() # R o pulsante Ricarica a partita finita
|
||||
return
|
||||
var input := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
||||
_handle_actions()
|
||||
# Deadzone esplicita piccola (0.2): con la 0.5 delle azioni la corsa utile del
|
||||
# joystick touch si dimezzerebbe, mentre per tastiera/joypad nulla cambia.
|
||||
var input := Input.get_vector("move_left", "move_right", "move_forward", "move_back", 0.2)
|
||||
var direction := transform.basis * Vector3(input.x, 0, input.y)
|
||||
velocity.x = direction.x * 5
|
||||
velocity.z = direction.z * 5
|
||||
@@ -112,7 +134,7 @@ func _physics_process(dt: float) -> void:
|
||||
Audio.play(self, "player/pl_metal%d.wav" % randi_range(1, 4), false, -18)
|
||||
if Input.is_action_just_pressed("shoot") and ammo <= 0 and not reloading:
|
||||
Audio.play(self, "weapons/dryfire1.wav")
|
||||
if Input.is_action_pressed("shoot") and cooldown <= 0 and ammo > 0 and not reloading and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
if Input.is_action_pressed("shoot") and cooldown <= 0 and ammo > 0 and not reloading and (touch_mode or Input.mouse_mode == Input.MOUSE_MODE_CAPTURED):
|
||||
shoot()
|
||||
|
||||
func shoot() -> void:
|
||||
|
||||
Reference in New Issue
Block a user