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:
+57
-7
@@ -34,6 +34,18 @@ 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
|
||||
## 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,
|
||||
## altrimenti la schermata iniziale non e' cliccabile.
|
||||
var playing := false
|
||||
@@ -95,7 +107,7 @@ var death_note: Label
|
||||
## viene mai catturato.
|
||||
func begin_play() -> void:
|
||||
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
|
||||
|
||||
## 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_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:
|
||||
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:
|
||||
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)
|
||||
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
|
||||
|
||||
## Ricarica: tempi invariati, riusata da tastiera (R) e dal pulsante on-screen.
|
||||
@@ -144,7 +173,7 @@ func start_reload() -> void:
|
||||
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):
|
||||
if Input.is_action_just_pressed("grenade") and not death_started and _can_aim():
|
||||
throw_grenade()
|
||||
|
||||
## 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)
|
||||
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:
|
||||
cooldown -= dt
|
||||
grenade_cooldown = maxf(0, grenade_cooldown - dt)
|
||||
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
|
||||
# R (tastiera) oppure X/A del pad: stessi tempi, nessun menu da navigare.
|
||||
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
|
||||
_regenerate_health(dt)
|
||||
if ammo <= 0 and not reloading:
|
||||
start_reload()
|
||||
_handle_actions()
|
||||
_handle_gamepad_look(dt)
|
||||
# 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)
|
||||
@@ -184,7 +232,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 (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()
|
||||
|
||||
func shoot() -> void:
|
||||
@@ -264,7 +312,9 @@ func _die() -> void:
|
||||
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)
|
||||
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.add_theme_font_size_override("font_size", 36)
|
||||
message.modulate.a = 0
|
||||
|
||||
Reference in New Issue
Block a user