Add gamepad support: stick look, trigger fire, pad menu confirm
- project.godot: joypad events on the ten existing actions (left stick move, A jump, right trigger/RB fire, X reload, B/LB grenade, Start pause, Back fullscreen), four new look_* actions on the right stick (deadzone 0.2) and ui_accept/ui_cancel redefined with the pad buttons A/B while keeping Enter/KP Enter/Space/Esc: the 4.7.1 engine maps no pad button on those. - scripts/player.gd: gamepad_mode turns on by itself on the first pad input; _can_aim() replaces the "mouse captured" gate of shoot and grenade (the pad never captures the pointer, same trap already fixed for touch); _handle_gamepad_look() converts the right stick into equivalent relative mouse pixels (900 px/s at full deflection, m^1.8 response) and still goes through apply_look(); restart accepts A/X and the death text follows the device in use. - scripts/start_screen.gd: pad hint shown only when a controller is connected, refreshed on Input.joy_connection_changed. - tools/test_gamepad.gd: 33 checks on map structure, keyboard preservation, real event pipeline, gamepad_mode and analog look. - docs/GAMEPAD.md: mapping, code map, verification, known limit (kiosk name entry still needs a keyboard: on-screen keyboard is the next step). Verified: test_gamepad 33/33, test_fullscreen_shortcut 8/8, test_touch_controls 14/14 (--touch), test_grenade 24/24, test_boss 50/50, test_rebalance_branding 14/14, test_player_profile 30/30. test_start_screen keeps its 2 pre-existing headless failures (identical on HEAD).
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
# Gamepad / controller
|
||||||
|
|
||||||
|
Supporto ai controller implementato il 2026-10-24. Il gameplay non è stato
|
||||||
|
riscritto: tutte le azioni passano già dalla Input Map, quindi il lavoro è stato
|
||||||
|
mappare i binding, aggiungere il ramo analogico della visuale e aprire i gate che
|
||||||
|
prima pretendevano il mouse catturato.
|
||||||
|
|
||||||
|
## Mappatura (layout SDL/Xbox)
|
||||||
|
|
||||||
|
| Azione | Tastiera/mouse | Gamepad |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `move_forward` / `move_back` | W / S, frecce | stick sinistro Y (asse 1) |
|
||||||
|
| `move_left` / `move_right` | A / D, frecce | stick sinistro X (asse 0) |
|
||||||
|
| `jump` | Spazio | A (pulsante 0) |
|
||||||
|
| `shoot` | tasto sinistro del mouse | grilletto destro (asse 5) · RB (10) |
|
||||||
|
| `reload` | R | X (pulsante 2) |
|
||||||
|
| `grenade` | tasto destro del mouse | B (1) · LB (9) |
|
||||||
|
| `pause` | Esc | Start (6) |
|
||||||
|
| `toggle_fullscreen` | Alt+F | Back/Select (4) |
|
||||||
|
| `look_*` (nuove) | movimento del mouse | stick destro (assi 2 e 3) |
|
||||||
|
|
||||||
|
Tutti i binding joypad hanno `device = -1`, quindi funzionano con qualunque pad
|
||||||
|
collegato, non solo con il primo.
|
||||||
|
|
||||||
|
## Dove sta il codice
|
||||||
|
|
||||||
|
- `project.godot` → `[input]`: eventi `InputEventJoypadButton` / `InputEventJoypadMotion`
|
||||||
|
sulle azioni esistenti, quattro nuove azioni `look_left/right/up/down` (deadzone 0.2)
|
||||||
|
e la ridefinizione di `ui_accept`/`ui_cancel`.
|
||||||
|
- `scripts/player.gd`:
|
||||||
|
- `gamepad_mode`: si accende da solo al primo pulsante/stick usato (`_is_gamepad_activity`);
|
||||||
|
- `_can_aim()` sostituisce il gate `touch_mode or mouse_mode == CAPTURED` di sparo e
|
||||||
|
granata: senza, col pad non si sarebbe potuto né sparare né lanciare;
|
||||||
|
- `_handle_gamepad_look(dt)`: converte lo stick destro in "pixel relativi
|
||||||
|
equivalenti" (900 px/s a fondo corsa, curva `m^1.8`) e li passa ad `apply_look()`,
|
||||||
|
che resta l'unico punto con sensibilità e limiti di inclinazione;
|
||||||
|
- a partita finita ricomincia la partita anche con A/X, e il messaggio di morte
|
||||||
|
cambia testo in base al dispositivo in uso.
|
||||||
|
- `scripts/start_screen.gd`: riga di aiuto che compare **solo** se c'è un pad
|
||||||
|
collegato (aggiornata in hotplug via `Input.joy_connection_changed`).
|
||||||
|
|
||||||
|
## Perché `ui_accept` va ridefinito a mano
|
||||||
|
|
||||||
|
Nel motore 4.7.1 `ui_accept` è mappato su Invio, Invio del tastierino e Spazio, e
|
||||||
|
`ui_cancel` solo su Esc: **nessun pulsante di gamepad**. Con il pad si poteva
|
||||||
|
navigare fra i controlli (le `ui_up/down/left/right` hanno già dpad e stick) ma non
|
||||||
|
confermare. La ridefinizione in `project.godot` rimette anche i tasti di tastiera:
|
||||||
|
senza, Invio e Spazio avrebbero smesso di funzionare. Il test lo controlla.
|
||||||
|
|
||||||
|
## Verifica
|
||||||
|
|
||||||
|
```bash
|
||||||
|
godot --headless --path . --script res://tools/test_gamepad.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
33 controlli: struttura della mappa, conservazione dei tasti su `ui_accept`/`ui_cancel`,
|
||||||
|
pipeline eventi reale (`Input.parse_input_event` con un pad su device 0), accensione di
|
||||||
|
`gamepad_mode`, rotazione della visuale con lo stick destro e limiti di inclinazione.
|
||||||
|
|
||||||
|
Con un pad vero conviene rifare la stessa prova a schermo reale
|
||||||
|
(`--rendering-method gl_compatibility`) perché il puntatore e il focus non sono
|
||||||
|
verificabili in headless. Suite esistenti riverificate: `test_fullscreen_shortcut`,
|
||||||
|
`test_touch_controls -- --touch`, `test_grenade`, `test_boss`, `test_rebalance_branding`,
|
||||||
|
`test_player_profile` tutte verdi. `test_start_screen` ha 2 FAIL preesistenti
|
||||||
|
(pulsante GIOCA in headless), identici su HEAD senza queste modifiche.
|
||||||
|
|
||||||
|
## Limite noto: il nome del giocatore
|
||||||
|
|
||||||
|
La regola arcade/kiosk chiede il nome a ogni avvio, e il campo è un `LineEdit` con
|
||||||
|
`virtual_keyboard_enabled`: la tastiera su schermo del motore però esiste solo su
|
||||||
|
Android/iOS/Web. Con **solo** il pad, su desktop non c'è modo di scrivere il nome.
|
||||||
|
Fase successiva: tastiera on-screen navigabile col dpad (griglia A-Z), oppure scelta
|
||||||
|
del nome da una lista di profili recenti.
|
||||||
+51
-1
@@ -44,54 +44,104 @@ move_forward={
|
|||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
, Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion, "resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
move_back={
|
move_back={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||||
, Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
, Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion, "resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
move_left={
|
move_left={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion, "resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
move_right={
|
move_right={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion, "resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
jump={
|
jump={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
shoot={
|
shoot={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventMouseButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
"events": [Object(InputEventMouseButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||||
|
, Object(InputEventJoypadMotion, "resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null)
|
||||||
|
, Object(InputEventJoypadButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
reload={
|
reload={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":2,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
pause={
|
pause={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_fullscreen={
|
toggle_fullscreen={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
grenade={
|
grenade={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventMouseButton, "button_index":2)]
|
"events": [Object(InputEventMouseButton, "button_index":2), Object(InputEventJoypadButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null), Object(InputEventJoypadButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":false,"script":null)]
|
||||||
|
}
|
||||||
|
|
||||||
|
look_left={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventJoypadMotion, "resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
look_right={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventJoypadMotion, "resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
look_up={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventJoypadMotion, "resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
look_down={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventJoypadMotion, "resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_accept={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_cancel={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey, "resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
, Object(InputEventJoypadButton, "resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|||||||
+57
-7
@@ -34,6 +34,18 @@ var death_overlay: ColorRect
|
|||||||
## True quando la partita e' guidata dai controlli on-screen (touch): il mouse
|
## 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.
|
## emulato dai tocchi non deve muovere la visuale ne' sparare al posto del dito.
|
||||||
var touch_mode := false
|
var touch_mode := false
|
||||||
|
## True quando l'ultimo input utile arriva da un gamepad. Il pad non cattura il
|
||||||
|
## puntatore, quindi serve un flag esplicito: senza, sparo e granata resterebbero
|
||||||
|
## bloccati dal gate "mouse catturato" (la stessa trappola gia' corretta per il touch).
|
||||||
|
var gamepad_mode := false
|
||||||
|
## Punti-equivalenti di mouse al secondo con lo stick destro a fondo corsa: 900 px/s
|
||||||
|
## per gli 0.0025 rad/px di apply_look danno 2.25 rad/s (~129 gradi/s).
|
||||||
|
const GAMEPAD_LOOK_SPEED := 900.0
|
||||||
|
## Risposta dello stick: con 1.8 i piccoli spostamenti restano fini per mirare e il
|
||||||
|
## fondo corsa continua a girare veloce.
|
||||||
|
const GAMEPAD_LOOK_EXPONENT := 1.8
|
||||||
|
## Oltre questa ampiezza un movimento di stick conta come "sto usando il pad".
|
||||||
|
const GAMEPAD_ACTIVITY := 0.3
|
||||||
## Vero solo dopo l'inizio della partita: al menu il cursore deve restare libero,
|
## Vero solo dopo l'inizio della partita: al menu il cursore deve restare libero,
|
||||||
## altrimenti la schermata iniziale non e' cliccabile.
|
## altrimenti la schermata iniziale non e' cliccabile.
|
||||||
var playing := false
|
var playing := false
|
||||||
@@ -95,7 +107,7 @@ var death_note: Label
|
|||||||
## viene mai catturato.
|
## viene mai catturato.
|
||||||
func begin_play() -> void:
|
func begin_play() -> void:
|
||||||
playing = true
|
playing = true
|
||||||
if not touch_mode and DisplayServer.get_name() != "headless":
|
if not touch_mode and not gamepad_mode and DisplayServer.get_name() != "headless":
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
|
|
||||||
## Riga sotto il messaggio di morte: punteggio inviato, posizione in classifica o
|
## Riga sotto il messaggio di morte: punteggio inviato, posizione in classifica o
|
||||||
@@ -113,12 +125,29 @@ func _input(event: InputEvent) -> void:
|
|||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
get_tree().call_deferred("reload_current_scene")
|
get_tree().call_deferred("reload_current_scene")
|
||||||
|
|
||||||
|
## Riconosce l'attivita' del pad (pulsante premuto o stick fuori dalla zona morta):
|
||||||
|
## cosi' il gioco passa da mouse a gamepad senza nessuna configurazione.
|
||||||
|
func _is_gamepad_activity(event: InputEvent) -> bool:
|
||||||
|
if event is InputEventJoypadButton:
|
||||||
|
return event.pressed
|
||||||
|
if event is InputEventJoypadMotion:
|
||||||
|
return absf(event.axis_value) > GAMEPAD_ACTIVITY
|
||||||
|
return false
|
||||||
|
|
||||||
|
## Il giocatore puo' mirare/sparare quando il puntatore e' catturato, oppure con touch
|
||||||
|
## o gamepad, che non catturano nulla.
|
||||||
|
func _can_aim() -> bool:
|
||||||
|
return touch_mode or gamepad_mode or Input.mouse_mode == Input.MOUSE_MODE_CAPTURED
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if _is_gamepad_activity(event):
|
||||||
|
gamepad_mode = true
|
||||||
if playing and 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:
|
if playing and 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
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
if event is InputEventMouseMotion and not touch_mode and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
if event is InputEventMouseMotion and not touch_mode and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||||
apply_look(event.relative)
|
apply_look(event.relative)
|
||||||
if playing and event.is_action_pressed("pause") and not death_started:
|
# Con il pad il puntatore non viene catturato: il toggle del mouse non ha senso.
|
||||||
|
if playing and event.is_action_pressed("pause") and not death_started and not gamepad_mode:
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED else Input.MOUSE_MODE_CAPTURED
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED else Input.MOUSE_MODE_CAPTURED
|
||||||
|
|
||||||
## Ricarica: tempi invariati, riusata da tastiera (R) e dal pulsante on-screen.
|
## Ricarica: tempi invariati, riusata da tastiera (R) e dal pulsante on-screen.
|
||||||
@@ -144,7 +173,7 @@ func start_reload() -> void:
|
|||||||
func _handle_actions() -> void:
|
func _handle_actions() -> void:
|
||||||
if Input.is_action_just_pressed("reload") and not reloading and ammo < 30:
|
if Input.is_action_just_pressed("reload") and not reloading and ammo < 30:
|
||||||
start_reload()
|
start_reload()
|
||||||
if Input.is_action_just_pressed("grenade") and not death_started and (touch_mode or Input.mouse_mode == Input.MOUSE_MODE_CAPTURED):
|
if Input.is_action_just_pressed("grenade") and not death_started and _can_aim():
|
||||||
throw_grenade()
|
throw_grenade()
|
||||||
|
|
||||||
## Rotazione della visuale. Unico punto in cui vivono sensibilita' e limiti di
|
## Rotazione della visuale. Unico punto in cui vivono sensibilita' e limiti di
|
||||||
@@ -155,19 +184,38 @@ func apply_look(relative: Vector2) -> void:
|
|||||||
rotate_y(-relative.x * 0.0025)
|
rotate_y(-relative.x * 0.0025)
|
||||||
camera.rotation.x = clampf(camera.rotation.x - relative.y * 0.0025, -1.4, 1.4)
|
camera.rotation.x = clampf(camera.rotation.x - relative.y * 0.0025, -1.4, 1.4)
|
||||||
|
|
||||||
|
## Visuale analogica: il mouse arriva come InputEventMouseMotion relativo, lo stick
|
||||||
|
## destro come azioni. Qui lo stick diventa "pixel relativi equivalenti" e passa ad
|
||||||
|
## apply_look(), che resta l'unico posto con sensibilita' e limiti di inclinazione.
|
||||||
|
## La curva viene applicata sulla sola ampiezza, quindi la direzione resta esatta.
|
||||||
|
func _handle_gamepad_look(dt: float) -> void:
|
||||||
|
if not gamepad_mode or not playing or touch_mode:
|
||||||
|
return
|
||||||
|
if hp <= 0 or death_started:
|
||||||
|
return
|
||||||
|
# Deadzone esplicita 0.2, la stessa del movimento.
|
||||||
|
var look := Input.get_vector("look_left", "look_right", "look_up", "look_down", 0.2)
|
||||||
|
if look == Vector2.ZERO:
|
||||||
|
return
|
||||||
|
var magnitude := clampf(look.length(), 0.0, 1.0)
|
||||||
|
var shaped := look.normalized() * pow(magnitude, GAMEPAD_LOOK_EXPONENT)
|
||||||
|
apply_look(shaped * GAMEPAD_LOOK_SPEED * dt)
|
||||||
|
|
||||||
func _physics_process(dt: float) -> void:
|
func _physics_process(dt: float) -> void:
|
||||||
cooldown -= dt
|
cooldown -= dt
|
||||||
grenade_cooldown = maxf(0, grenade_cooldown - dt)
|
grenade_cooldown = maxf(0, grenade_cooldown - dt)
|
||||||
flash.light_energy = maxf(0, flash.light_energy - dt * 60)
|
flash.light_energy = maxf(0, flash.light_energy - dt * 60)
|
||||||
gun.position.z = lerpf(gun.position.z, 0, dt * 15)
|
gun.position.z = lerpf(gun.position.z, 0, dt * 15)
|
||||||
if hp <= 0:
|
if hp <= 0:
|
||||||
if Input.is_action_just_pressed("reload"):
|
# R (tastiera) oppure X/A del pad: stessi tempi, nessun menu da navigare.
|
||||||
get_tree().reload_current_scene() # R o pulsante Ricarica a partita finita
|
if Input.is_action_just_pressed("reload") or Input.is_action_just_pressed("jump"):
|
||||||
|
get_tree().reload_current_scene() # R, X/A del pad o pulsante Ricarica a partita finita
|
||||||
return
|
return
|
||||||
_regenerate_health(dt)
|
_regenerate_health(dt)
|
||||||
if ammo <= 0 and not reloading:
|
if ammo <= 0 and not reloading:
|
||||||
start_reload()
|
start_reload()
|
||||||
_handle_actions()
|
_handle_actions()
|
||||||
|
_handle_gamepad_look(dt)
|
||||||
# Deadzone esplicita piccola (0.2): con la 0.5 delle azioni la corsa utile del
|
# 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.
|
# 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 input := Input.get_vector("move_left", "move_right", "move_forward", "move_back", 0.2)
|
||||||
@@ -184,7 +232,7 @@ func _physics_process(dt: float) -> void:
|
|||||||
Audio.play(self, "player/pl_metal%d.wav" % randi_range(1, 4), false, -18)
|
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:
|
if Input.is_action_just_pressed("shoot") and ammo <= 0 and not reloading:
|
||||||
Audio.play(self, "weapons/dryfire1.wav")
|
Audio.play(self, "weapons/dryfire1.wav")
|
||||||
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):
|
if Input.is_action_pressed("shoot") and cooldown <= 0 and ammo > 0 and not reloading and _can_aim():
|
||||||
shoot()
|
shoot()
|
||||||
|
|
||||||
func shoot() -> void:
|
func shoot() -> void:
|
||||||
@@ -264,7 +312,9 @@ func _die() -> void:
|
|||||||
tween.tween_property(camera, "rotation:x", 0.08, 0.9)
|
tween.tween_property(camera, "rotation:x", 0.08, 0.9)
|
||||||
tween.tween_property(death_overlay, "color:a", 1.0, 1.8).set_delay(0.7)
|
tween.tween_property(death_overlay, "color:a", 1.0, 1.8).set_delay(0.7)
|
||||||
var message := Label.new()
|
var message := Label.new()
|
||||||
message.text = "SEI MORTO" if touch_mode else "SEI MORTO\nPremi R per ricominciare"
|
message.text = "SEI MORTO" if touch_mode else (
|
||||||
|
"SEI MORTO\nPremi A o X per ricominciare" if gamepad_mode
|
||||||
|
else "SEI MORTO\nPremi R per ricominciare")
|
||||||
message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
message.add_theme_font_size_override("font_size", 36)
|
message.add_theme_font_size_override("font_size", 36)
|
||||||
message.modulate.a = 0
|
message.modulate.a = 0
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ var player: Node
|
|||||||
var leaderboard: Node
|
var leaderboard: Node
|
||||||
var profile := {}
|
var profile := {}
|
||||||
var name_field: LineEdit
|
var name_field: LineEdit
|
||||||
|
var play_button: Button
|
||||||
|
## Riga di aiuto che compare solo quando c'e' un gamepad collegato: con mouse e
|
||||||
|
## tastiera la schermata resta identica a prima.
|
||||||
|
var pad_hint: Label
|
||||||
var rows_box: VBoxContainer
|
var rows_box: VBoxContainer
|
||||||
var status_label: Label
|
var status_label: Label
|
||||||
var me_label: Label
|
var me_label: Label
|
||||||
@@ -29,6 +33,9 @@ func _ready() -> void:
|
|||||||
name_locked = Profile.is_name_locked(profile)
|
name_locked = Profile.is_name_locked(profile)
|
||||||
rows_shown = ROWS_TOUCH if (player != null and player.touch_mode) else ROWS_DESKTOP
|
rows_shown = ROWS_TOUCH if (player != null and player.touch_mode) else ROWS_DESKTOP
|
||||||
_build_ui()
|
_build_ui()
|
||||||
|
# Hotplug: la riga di aiuto del pad compare/scompare senza riavviare il gioco.
|
||||||
|
Input.joy_connection_changed.connect(_on_joy_connection_changed)
|
||||||
|
_refresh_pad_hint()
|
||||||
if leaderboard != null:
|
if leaderboard != null:
|
||||||
leaderboard.fetch()
|
leaderboard.fetch()
|
||||||
|
|
||||||
@@ -75,6 +82,13 @@ func _build_ui() -> void:
|
|||||||
play.custom_minimum_size = Vector2(0, 56)
|
play.custom_minimum_size = Vector2(0, 56)
|
||||||
play.pressed.connect(_start)
|
play.pressed.connect(_start)
|
||||||
column.add_child(play)
|
column.add_child(play)
|
||||||
|
play_button = play
|
||||||
|
|
||||||
|
# Aiuto per il pad: visibile solo se un controller e' collegato.
|
||||||
|
pad_hint = _label("", 13, DIM, HORIZONTAL_ALIGNMENT_CENTER)
|
||||||
|
pad_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
pad_hint.visible = false
|
||||||
|
column.add_child(pad_hint)
|
||||||
|
|
||||||
var panel := PanelContainer.new()
|
var panel := PanelContainer.new()
|
||||||
var style := StyleBoxFlat.new()
|
var style := StyleBoxFlat.new()
|
||||||
@@ -140,6 +154,21 @@ func _label(text: String, size: int, color: Color, align: int) -> Label:
|
|||||||
label.add_theme_font_size_override("font_size", size)
|
label.add_theme_font_size_override("font_size", size)
|
||||||
return label
|
return label
|
||||||
|
|
||||||
|
func _on_joy_connection_changed(_device: int, _connected: bool) -> void:
|
||||||
|
_refresh_pad_hint()
|
||||||
|
|
||||||
|
## Testo e visibilita' dell'aiuto gamepad. Il nome resta da inserire: con il solo pad
|
||||||
|
## serve una tastiera su schermo (fase successiva), qui si documentano i comandi.
|
||||||
|
func _refresh_pad_hint() -> void:
|
||||||
|
if pad_hint == null:
|
||||||
|
return
|
||||||
|
var pads := Input.get_connected_joypads()
|
||||||
|
pad_hint.visible = not pads.is_empty()
|
||||||
|
if not pads.is_empty():
|
||||||
|
pad_hint.text = "PAD — stick sx muovi · stick dx visuale · RT spara · X ricarica · B/LB granata · A salta · Start pausa · Back schermo intero"
|
||||||
|
if play_button != null and not play_button.has_focus() and name_field != null and not name_field.has_focus():
|
||||||
|
play_button.grab_focus()
|
||||||
|
|
||||||
func _set_status(text: String) -> void:
|
func _set_status(text: String) -> void:
|
||||||
if status_label != null:
|
if status_label != null:
|
||||||
status_label.text = text
|
status_label.text = text
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
extends SceneTree
|
||||||
|
## Verifica il supporto gamepad:
|
||||||
|
## - ogni azione di gioco ha almeno un binding joypad, con i tasti/gli assi attesi;
|
||||||
|
## - gli assi di movimento sono sullo stick sinistro, la visuale sullo stick destro;
|
||||||
|
## - ui_accept/ui_cancel accettano A/B del pad SENZA perdere Invio/Spazio/Esc;
|
||||||
|
## - un pad reale (device != -1) attiva le azioni mappate con device -1;
|
||||||
|
## - l'attivita' del pad accende player.gamepad_mode, il puntatore non viene
|
||||||
|
## catturato e la visuale gira con lo stick destro passando da apply_look().
|
||||||
|
##
|
||||||
|
## Avviare con: --headless --path . --script res://tools/test_gamepad.gd
|
||||||
|
## Con un display reale: --rendering-method gl_compatibility
|
||||||
|
|
||||||
|
## azione -> eventi joypad attesi come stringhe leggibili (il formato del controllo)
|
||||||
|
const EXPECTED := {
|
||||||
|
"move_forward": "axis1-1.0",
|
||||||
|
"move_back": "axis1+1.0",
|
||||||
|
"move_left": "axis0-1.0",
|
||||||
|
"move_right": "axis0+1.0",
|
||||||
|
"jump": "btn0",
|
||||||
|
"reload": "btn2",
|
||||||
|
"pause": "btn6",
|
||||||
|
"toggle_fullscreen": "btn4",
|
||||||
|
"grenade": "btn1+btn9",
|
||||||
|
"shoot": "axis5+1.0+btn10",
|
||||||
|
"look_left": "axis2-1.0",
|
||||||
|
"look_right": "axis2+1.0",
|
||||||
|
"look_up": "axis3-1.0",
|
||||||
|
"look_down": "axis3+1.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
var world: Node3D
|
||||||
|
var failures := 0
|
||||||
|
var checks := 0
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("run_test")
|
||||||
|
|
||||||
|
func check(label: String, condition: bool, detail: String = "") -> void:
|
||||||
|
checks += 1
|
||||||
|
if condition:
|
||||||
|
print("PASS: " + label)
|
||||||
|
else:
|
||||||
|
failures += 1
|
||||||
|
print("FAIL: %s %s" % [label, detail])
|
||||||
|
|
||||||
|
## Rappresentazione compatta degli eventi joypad di un'azione, ordinata.
|
||||||
|
func _joypad_signature(action: String) -> String:
|
||||||
|
var parts: Array[String] = []
|
||||||
|
for event in InputMap.action_get_events(action):
|
||||||
|
if event is InputEventJoypadButton:
|
||||||
|
parts.append("btn%d" % event.button_index)
|
||||||
|
elif event is InputEventJoypadMotion:
|
||||||
|
parts.append("axis%d%+.1f" % [event.axis, event.axis_value])
|
||||||
|
parts.sort()
|
||||||
|
return "+".join(parts)
|
||||||
|
|
||||||
|
func _has_key(action: String, keycode: int) -> bool:
|
||||||
|
for event in InputMap.action_get_events(action):
|
||||||
|
if event is InputEventKey and (event.keycode == keycode or event.physical_keycode == keycode):
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
func _joypad_devices(action: String) -> Array[int]:
|
||||||
|
var devices: Array[int] = []
|
||||||
|
for event in InputMap.action_get_events(action):
|
||||||
|
if event is InputEventJoypadButton or event is InputEventJoypadMotion:
|
||||||
|
devices.append(event.device)
|
||||||
|
return devices
|
||||||
|
|
||||||
|
## Pressione/rilascio di un pulsante del pad, come un controller vero.
|
||||||
|
func _pad_button(index: int, pressed: bool, device := 0) -> void:
|
||||||
|
var event := InputEventJoypadButton.new()
|
||||||
|
event.device = device
|
||||||
|
event.button_index = index
|
||||||
|
event.pressed = pressed
|
||||||
|
Input.parse_input_event(event)
|
||||||
|
Input.flush_buffered_events()
|
||||||
|
|
||||||
|
## Movimento di un asse del pad (valore fra -1 e 1).
|
||||||
|
func _pad_axis(axis: int, value: float, device := 0) -> void:
|
||||||
|
var event := InputEventJoypadMotion.new()
|
||||||
|
event.device = device
|
||||||
|
event.axis = axis
|
||||||
|
event.axis_value = value
|
||||||
|
Input.parse_input_event(event)
|
||||||
|
Input.flush_buffered_events()
|
||||||
|
|
||||||
|
func _frames(count: int) -> void:
|
||||||
|
for i in count:
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
func _steps(count: int) -> void:
|
||||||
|
for i in count:
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
func run_test() -> void:
|
||||||
|
# --- mappa dei comandi ---
|
||||||
|
for action: String in EXPECTED:
|
||||||
|
check("\"%s\" ha i binding joypad attesi" % action,
|
||||||
|
_joypad_signature(action) == EXPECTED[action],
|
||||||
|
"trovato \"%s\" atteso \"%s\"" % [_joypad_signature(action), EXPECTED[action]])
|
||||||
|
|
||||||
|
var all_any := true
|
||||||
|
var offenders: Array[String] = []
|
||||||
|
for action: String in EXPECTED:
|
||||||
|
for device in _joypad_devices(action):
|
||||||
|
if device != -1:
|
||||||
|
all_any = false
|
||||||
|
offenders.append("%s(device %d)" % [action, device])
|
||||||
|
check("i binding joypad accettano qualunque pad (device -1)", all_any, str(offenders))
|
||||||
|
|
||||||
|
# ui_*: il motore 4.7.1 non mappa alcun pulsante su ui_accept/ui_cancel, quindi
|
||||||
|
# vanno aggiunti a mano tenendo i tasti di tastiera gia' presenti.
|
||||||
|
check("ui_accept accetta il pulsante A del pad",
|
||||||
|
_joypad_signature("ui_accept") == "btn0", _joypad_signature("ui_accept"))
|
||||||
|
check("ui_accept conserva Invio", _has_key("ui_accept", KEY_ENTER))
|
||||||
|
check("ui_accept conserva Invio del tastierino", _has_key("ui_accept", KEY_KP_ENTER))
|
||||||
|
check("ui_accept conserva Spazio", _has_key("ui_accept", KEY_SPACE))
|
||||||
|
check("ui_cancel accetta il pulsante B del pad",
|
||||||
|
_joypad_signature("ui_cancel") == "btn1", _joypad_signature("ui_cancel"))
|
||||||
|
check("ui_cancel conserva Esc", _has_key("ui_cancel", KEY_ESCAPE))
|
||||||
|
|
||||||
|
# --- pipeline eventi: un pad qualsiasi (device 0) deve attivare le azioni ---
|
||||||
|
_pad_button(JOY_BUTTON_A, true)
|
||||||
|
await _frames(1)
|
||||||
|
check("A del pad attiva l'azione \"jump\"", Input.is_action_pressed("jump"))
|
||||||
|
check("A del pad attiva ui_accept", Input.is_action_pressed("ui_accept"))
|
||||||
|
_pad_button(JOY_BUTTON_A, false)
|
||||||
|
await _frames(1)
|
||||||
|
check("il rilascio di A spegne \"jump\"", not Input.is_action_pressed("jump"))
|
||||||
|
|
||||||
|
_pad_axis(JOY_AXIS_LEFT_Y, -1.0)
|
||||||
|
await _frames(1)
|
||||||
|
check("stick sinistro in alto attiva \"move_forward\"",
|
||||||
|
Input.is_action_pressed("move_forward"))
|
||||||
|
_pad_axis(JOY_AXIS_LEFT_Y, 0.0)
|
||||||
|
await _frames(1)
|
||||||
|
|
||||||
|
_pad_axis(JOY_AXIS_RIGHT_X, 1.0)
|
||||||
|
await _frames(1)
|
||||||
|
check("stick destro a destra attiva \"look_right\"", Input.is_action_pressed("look_right"))
|
||||||
|
_pad_axis(JOY_AXIS_RIGHT_X, 0.0)
|
||||||
|
await _frames(1)
|
||||||
|
|
||||||
|
# --- mondo di gioco: gamepad_mode e visuale analogica ---
|
||||||
|
world = load("res://scenes/main.tscn").instantiate()
|
||||||
|
root.add_child(world)
|
||||||
|
await _frames(2)
|
||||||
|
var player: Node = world.player
|
||||||
|
var screen = world.start_screen
|
||||||
|
var gioca: Button = null
|
||||||
|
if screen != null:
|
||||||
|
for button in screen.find_children("*", "Button", true, false):
|
||||||
|
if button.text == "GIOCA":
|
||||||
|
gioca = button
|
||||||
|
if screen != null and screen.get("name_field") != null:
|
||||||
|
screen.name_field.text = "PadTest"
|
||||||
|
if gioca != null:
|
||||||
|
gioca.pressed.emit()
|
||||||
|
await _frames(2)
|
||||||
|
check("la partita e' iniziata", player.playing == true, "playing=%s" % str(player.playing))
|
||||||
|
check("senza attivita' di pad gamepad_mode resta falso", player.gamepad_mode == false)
|
||||||
|
|
||||||
|
_pad_button(JOY_BUTTON_A, true)
|
||||||
|
_pad_button(JOY_BUTTON_A, false)
|
||||||
|
await _frames(1)
|
||||||
|
check("un pulsante del pad accende player.gamepad_mode", player.gamepad_mode == true)
|
||||||
|
|
||||||
|
var yaw_before: float = player.rotation.y
|
||||||
|
_pad_axis(JOY_AXIS_RIGHT_X, 1.0)
|
||||||
|
await _steps(12)
|
||||||
|
_pad_axis(JOY_AXIS_RIGHT_X, 0.0)
|
||||||
|
await _steps(2)
|
||||||
|
check("lo stick destro fa ruotare la visuale verso destra",
|
||||||
|
player.rotation.y < yaw_before - 0.01,
|
||||||
|
"prima=%.3f dopo=%.3f" % [yaw_before, player.rotation.y])
|
||||||
|
|
||||||
|
var pitch_before: float = player.camera.rotation.x
|
||||||
|
_pad_axis(JOY_AXIS_RIGHT_Y, 1.0)
|
||||||
|
await _steps(8)
|
||||||
|
_pad_axis(JOY_AXIS_RIGHT_Y, 0.0)
|
||||||
|
await _steps(2)
|
||||||
|
check("lo stick destro in basso abbassa l'inclinazione della camera",
|
||||||
|
player.camera.rotation.x < pitch_before,
|
||||||
|
"prima=%.3f dopo=%.3f" % [pitch_before, player.camera.rotation.x])
|
||||||
|
check("l'inclinazione resta dentro i limiti di apply_look",
|
||||||
|
player.camera.rotation.x >= -1.4 - 0.001, "pitch=%.3f" % player.camera.rotation.x)
|
||||||
|
|
||||||
|
# Il pad non deve catturare il puntatore (in headless non e' verificabile):
|
||||||
|
# quello che conta e' che il gate di sparo/granata non dipenda dal mouse.
|
||||||
|
check("con gamepad_mode il gate di mira e' aperto anche senza mouse catturato",
|
||||||
|
player._can_aim() == true and player.touch_mode == false,
|
||||||
|
"can_aim=%s touch=%s" % [str(player._can_aim()), str(player.touch_mode)])
|
||||||
|
|
||||||
|
print("%d controlli superati, %d falliti" % [checks - failures, failures])
|
||||||
|
quit(1 if failures > 0 else 0)
|
||||||
Reference in New Issue
Block a user