Show Xbox-style A/B/X/Y glyphs on the ability bar for a gamepad
Controls now tracks whether the player's last deliberate press was a gamepad button/stick or a keyboard key/mouse click (ignoring plain mouse motion, since the mouse still drives the camera regardless of which device moves the bull). The HUD's ability-bar keycaps read from that: keyboard letters normally, Xbox-style face-button glyphs while a gamepad is in use, swapping live the moment the player switches devices. Godot's joypad button indices are already Xbox-position-normalized (by SDL natively, by the browser's Standard Gamepad Mapping on web), so the glyph mapping (button 0/1/2/3 -> A/B/X/Y) reads correctly for any controller brand with no per-device special-casing. Verified headlessly: labels start as the bound keyboard letters (F/G/H/J), switch to X/A/B/Y on a synthetic joypad button press (matching project.godot's kick/dash/slam/roll bindings), and switch back on a keyboard press. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MXBNaEBmrYDSMcv4NgUWtV
This commit is contained in:
@@ -27,6 +27,64 @@ func _ready() -> void:
|
||||
_install_web_fullscreen_hook()
|
||||
|
||||
|
||||
# ── Active input device ──────────────────────────────────────────────────────────
|
||||
# Tracks whether the player's last deliberate input was a gamepad or keyboard/mouse, so the
|
||||
# HUD can swap its ability-bar keycaps to match whichever the player is actually holding.
|
||||
# Mouse motion is deliberately ignored — the mouse still drives the camera regardless of
|
||||
# which device is moving the bull, so panning the camera shouldn't flip the hint away from
|
||||
# gamepad glyphs. A small deadzone on joypad axes avoids stick drift/noise doing the same.
|
||||
const _JOY_AXIS_DEADZONE := 0.5
|
||||
var _last_input_is_gamepad: bool = false
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventJoypadButton:
|
||||
if event.pressed:
|
||||
_last_input_is_gamepad = true
|
||||
elif event is InputEventJoypadMotion:
|
||||
if absf(event.axis_value) > _JOY_AXIS_DEADZONE:
|
||||
_last_input_is_gamepad = true
|
||||
elif event is InputEventKey:
|
||||
if event.pressed:
|
||||
_last_input_is_gamepad = false
|
||||
elif event is InputEventMouseButton:
|
||||
if event.pressed:
|
||||
_last_input_is_gamepad = false
|
||||
|
||||
|
||||
## Whether the player's last button/key/stick press was on a gamepad rather than
|
||||
## keyboard/mouse. Drives which glyph set the HUD's ability keycaps show.
|
||||
func using_gamepad() -> bool:
|
||||
return _last_input_is_gamepad
|
||||
|
||||
|
||||
# Godot's joypad button indices are already Xbox-position-normalized — by SDL on native, by
|
||||
# the browser's Standard Gamepad Mapping on web — so this reads correctly for any controller
|
||||
# brand (a Nintendo pad's physically-labelled "A" is button 1 here, same as "B" on Xbox).
|
||||
const _XBOX_BUTTON_GLYPHS := {
|
||||
JOY_BUTTON_A: "A",
|
||||
JOY_BUTTON_B: "B",
|
||||
JOY_BUTTON_X: "X",
|
||||
JOY_BUTTON_Y: "Y",
|
||||
}
|
||||
|
||||
|
||||
## Xbox-style face-button glyph ("A"/"B"/"X"/"Y") for the action's bound joypad button, or
|
||||
## "—" if it has none.
|
||||
func get_joy_button_label(action: StringName) -> String:
|
||||
for event: InputEvent in InputMap.action_get_events(action):
|
||||
if event is InputEventJoypadButton:
|
||||
return _XBOX_BUTTON_GLYPHS.get(event.button_index, "?")
|
||||
return "—"
|
||||
|
||||
|
||||
## Ability-bar keycap label for `action`: the bound Xbox-style face button while a gamepad
|
||||
## is in use, else the bound keyboard key — so the on-screen prompt always matches whichever
|
||||
## the player is actually holding.
|
||||
func ability_key_label(action: StringName) -> String:
|
||||
return get_joy_button_label(action) if using_gamepad() else get_key_label(action)
|
||||
|
||||
|
||||
## Godot's built-in ui_accept (menu/button confirm) ships with zero joypad bindings —
|
||||
## checked directly: only Enter/Kp Enter/Space. ui_left/right/up/down, by contrast, do
|
||||
## default to the D-pad and stick, which is why gamepad focus navigation on a menu works out
|
||||
|
||||
@@ -44,7 +44,11 @@ var _hp_seg_fills: Array[ColorRect] = []
|
||||
var _bottom_col: VBoxContainer
|
||||
var _ability_bar: HBoxContainer
|
||||
var _ability_panels: Array[PanelContainer] = []
|
||||
var _ability_key_labels: Array[Label] = []
|
||||
var _bull_bg: TextureRect
|
||||
# Cached so the ability keycaps only get rewritten on an actual keyboard<->gamepad switch,
|
||||
# not every frame.
|
||||
var _showing_gamepad_hints: bool = false
|
||||
|
||||
# Lobby barrel-smash reward — tally the barrels present on load; when the last one bursts,
|
||||
# throw confetti + a "WOooOoW!" and grant the bull two yellow bonus pips. Latched so it
|
||||
@@ -257,6 +261,7 @@ static func _format_time(seconds: float) -> String:
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
_update_control_hints()
|
||||
_update_ability_key_labels()
|
||||
if _run_timer_label != null:
|
||||
_run_timer_label.visible = Run.run_timer_started() and not _game_over
|
||||
if _run_timer_label.visible:
|
||||
@@ -469,6 +474,18 @@ func _update_control_hints() -> void:
|
||||
_tab_hint.visible = not _controls_visible
|
||||
|
||||
|
||||
# Swaps the ability-bar keycaps between keyboard letters and Xbox-style face-button glyphs
|
||||
# (A/B/X/Y) as the player switches between keyboard and gamepad. Only rewrites the labels on
|
||||
# an actual switch, not every frame — Controls.using_gamepad() itself is cheap either way.
|
||||
func _update_ability_key_labels() -> void:
|
||||
var gamepad := Controls.using_gamepad()
|
||||
if gamepad == _showing_gamepad_hints:
|
||||
return
|
||||
_showing_gamepad_hints = gamepad
|
||||
for i in _ability_key_labels.size():
|
||||
_ability_key_labels[i].text = Controls.ability_key_label(_ABILITY_ACTIONS[i])
|
||||
|
||||
|
||||
# ── Damage vignette ───────────────────────────────────────────────────────────
|
||||
# A full-screen ColorRect driven by damage_vignette.gdshader: transparent centre, red
|
||||
# toward the edges, alpha ("flash") tweened up on a hit and eased back to nothing. Built
|
||||
@@ -902,7 +919,7 @@ func _build_ability_slot(idx: int) -> Control:
|
||||
vbox.add_child(keycap)
|
||||
|
||||
var key_lbl := Label.new()
|
||||
key_lbl.text = Controls.get_key_label(_ABILITY_ACTIONS[idx])
|
||||
key_lbl.text = Controls.ability_key_label(_ABILITY_ACTIONS[idx])
|
||||
key_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
key_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
key_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
@@ -912,6 +929,7 @@ func _build_ability_slot(idx: int) -> Control:
|
||||
key_lbl.add_theme_constant_override("outline_size", 3)
|
||||
key_lbl.add_theme_font_size_override("font_size", 14)
|
||||
keycap.add_child(key_lbl)
|
||||
_ability_key_labels.append(key_lbl)
|
||||
|
||||
return vbox
|
||||
|
||||
|
||||
Reference in New Issue
Block a user