Files
zombie-fps/tools/test_touch_controls.gd
T

205 lines
7.7 KiB
GDScript

extends SceneTree
## Verifica i controlli on-screen e il loro rilevamento automatico.
##
## --touch forza la UI touch e prova joystick, area di puntamento e pulsanti
## --no-touch verifica che su un contesto non mobile/touch la UI NON compaia
##
## Gli eventi sono iniettati con Input.parse_input_event come fa il browser
## (DisplayServerWeb invia InputEventScreenTouch/Drag), quindi passa la stessa
## pipeline di multitouch usata su dispositivo reale.
##
## Nota sull'ambiente: in --headless la finestra del DisplayServer e' 64x36,
## quindi la stretch transform (canvas_items) riscala le coordinate iniettate e
## una posizione di (200,500) arriva al Control come (4000,9720). In quel caso il
## test pilota direttamente i gestori in spazio viewport; con un display reale
## usa la pipeline eventi completa. La logica verificata e' la stessa.
const TouchScript = preload("res://scripts/touch_controls.gd")
var _direct := false
var world: Node
var touch: Node
var player: Node
var _log: Array[String] = []
func _initialize() -> void:
call_deferred("run_test")
func _touch(index: int, pos: Vector2, pressed: bool) -> void:
if _direct:
if pressed:
touch._press(index, pos)
else:
touch._release(index)
return
var ev := InputEventScreenTouch.new()
ev.index = index
ev.position = pos
ev.pressed = pressed
Input.parse_input_event(ev)
func _drag(index: int, pos: Vector2, relative: Vector2) -> void:
if _direct:
touch._drag(index, pos, relative)
return
var ev := InputEventScreenDrag.new()
ev.index = index
ev.position = pos
ev.relative = relative
ev.screen_relative = relative
Input.parse_input_event(ev)
Input.flush_buffered_events()
func _steps(count: int) -> void:
for i in count:
await physics_frame
func _frame() -> void:
await process_frame
func run_test() -> void:
var args := OS.get_cmdline_user_args()
world = load("res://scenes/main.tscn").instantiate()
root.add_child(world)
player = world.player
if "--no-touch" in args:
await _frame()
assert(world.touch == null)
assert(not player.touch_mode)
print("PASS: nessuna UI touch quando il contesto non e' mobile ne' touchscreen")
world.queue_free()
await _frame()
quit()
return
await _frame()
# --- presenza e costruzione della UI ---
assert(world.touch != null)
assert(player.touch_mode)
touch = world.touch
_direct = DisplayServer.get_name() == "headless"
assert(touch._buttons.size() == 4)
for role in [TouchScript.Role.FIRE, TouchScript.Role.GRENADE, TouchScript.Role.RELOAD, TouchScript.Role.JUMP]:
assert(touch._buttons.has(role))
print("PASS: UI touch creata con joystick, area di puntamento e 4 pulsanti%s" % (" [modalita' diretta: headless]" if _direct else " [pipeline eventi: display reale]"))
# --- joystick: trascinamento nella meta' sinistra -> movimento ---
var start_pos := Vector2(200, 500)
var before: Vector3 = player.global_position
_touch(0, start_pos, true)
await _frame()
_drag(0, start_pos + Vector2(140, -120), Vector2(140, -120))
await _frame()
assert(touch.move_vector.length() > 0.5)
await _steps(12)
var moved: Vector3 = player.global_position
assert(moved.x > before.x + 0.05) # verso destra (asse +X)
assert(moved.z < before.z - 0.05) # verso avanti (asse -Z)
print("PASS: joystick muovi -> delta (%.2f, %.2f) con vettore %.2f" % [moved.x - before.x, moved.z - before.z, touch.move_vector.length()])
# --- area di puntamento: trascinamento nella meta' destra -> rotazione ---
var yaw_before: float = player.rotation.y
_touch(1, Vector2(900, 300), true)
await _frame()
_drag(1, Vector2(960, 320), Vector2(60, 20))
await _frame()
assert(player.rotation.y < yaw_before - 0.001) # trascinare a destra ruota a destra
assert(absf(player.camera.rotation.x) <= 1.4)
print("PASS: area di puntamento -> rotazione visuale (yaw %.3f -> %.3f)" % [yaw_before, player.rotation.y])
# --- multitouch: i due tocchi coesistono, il rilascio di uno non ferma l'altro ---
assert(touch._touches.values().has(TouchScript.Role.JOY))
assert(touch._touches.values().has(TouchScript.Role.LOOK))
_touch(1, Vector2(960, 320), false)
await _frame()
assert(touch._touches.values().has(TouchScript.Role.JOY)) # il movimento resta attivo
print("PASS: multitouch simultaneo (movimento + puntamento) con indici separati")
_touch(0, start_pos + Vector2(140, -120), false)
await _frame()
assert(touch.move_vector == Vector2.ZERO)
await _steps(6)
assert(absf(player.velocity.x) < 0.01 and absf(player.velocity.z) < 0.01)
print("PASS: rilascio del joystick -> vettore azzerato e giocatore fermo")
# --- pulsante FUOCO: tenere premuto spara in automatico ---
player.ammo = 30
player.cooldown = 0.0
var fire_pos: Vector2 = touch._buttons[TouchScript.Role.FIRE].center
_touch(0, fire_pos, true)
await _steps(10)
assert(player.ammo < 30)
var fire_yaw: float = player.rotation.y
_drag(0, fire_pos + Vector2(-80, 10), Vector2(-80, 10))
await _frame()
assert(player.rotation.y > fire_yaw + 0.001)
assert(Input.is_action_pressed("shoot"))
print("PASS: trascinare FUOCO mira senza interrompere lo sparo")
var after_fire: int = player.ammo
_touch(0, fire_pos, false)
await _steps(10)
assert(player.ammo == after_fire) # rilasciato: non spara piu'
print("PASS: FUOCO tiene premuto -> 30 -> %d colpi, si ferma al rilascio" % after_fire)
# --- pulsante RICARICA ---
_touch(0, touch._buttons[TouchScript.Role.RELOAD].center, true)
await _steps(2) # l'edge dell'azione e' visto dal _physics_process
_touch(0, touch._buttons[TouchScript.Role.RELOAD].center, false)
assert(player.reloading)
await create_timer(1.8).timeout
assert(player.ammo == 30 and not player.reloading)
print("PASS: RICARICA -> caricatore pieno (30)")
# --- pulsante GRANATA ---
assert(player.grenade_cooldown <= 0)
_touch(0, touch._buttons[TouchScript.Role.GRENADE].center, true)
await _steps(2)
_touch(0, touch._buttons[TouchScript.Role.GRENADE].center, false)
assert(player.grenade_cooldown > 0)
assert(get_nodes_in_group("grenades").size() >= 1)
print("PASS: GRANATA -> lancio eseguito (cooldown %.0f s)" % player.grenade_cooldown)
player.grenade_cooldown = 0.0
# --- pulsante SALTO ---
await _steps(10)
assert(player.is_on_floor())
_touch(0, touch._buttons[TouchScript.Role.JUMP].center, true)
await _steps(2)
_touch(0, touch._buttons[TouchScript.Role.JUMP].center, false)
await _steps(2)
assert(player.velocity.y > 0.5)
print("PASS: SALTO -> velocita' verticale %.1f m/s" % player.velocity.y)
# --- rilevamento: su questo contesto (desktop headless) la UI non e' automatica ---
assert(not TouchScript.should_auto_enable())
print("PASS: TouchControls.should_auto_enable() = false su desktop -> la UI compare solo se serve")
assert(TouchScript._look_delta(Vector2(100, 20), 1000).is_equal_approx(TouchScript._look_delta(Vector2(200, 40), 2000)))
print("PASS: sensibilita' invariata per swipe proporzionali su schermi diversi")
_touch(0, start_pos, true)
player.take_damage(1000)
await _frame()
await _frame()
assert(not touch.enabled and touch._touches.is_empty())
assert(not Input.is_action_pressed("shoot"))
assert(is_instance_valid(player.restart_button))
await create_timer(0.7).timeout
assert(not player.restart_button.disabled)
current_scene = world
# Verifica anche il reload effettivo, non solo il pulsante.
var restart_event := InputEventScreenTouch.new()
restart_event.pressed = true
restart_event.position = player.restart_button.get_global_rect().get_center()
player._input(restart_event)
assert(player.restart_button.disabled)
await _frame()
await _frame()
assert(current_scene != world)
assert(current_scene.player.hp > 0)
world = current_scene
print("PASS: morte libera input, RICOMINCIA touch ricarica davvero la partita")
print("PASS: controlli touch completi")
world.queue_free()
await _frame()
quit()