Gamepad viewer
Sticks, triggers and buttons read straight from Input and drawn as they move. With nothing plugged in it says so, and the arrow keys still drive the ui vector.
The editor above is running this project. Change a line and it reloads.
Godot classes used
InputProgressBarPanelStyleBoxFlat
The code
scripts/main.gd
extends Node
const STICK_RADIUS: float = 76.0
const KNOB_RADIUS: float = 20.0
const DEAD_ZONE: float = 0.18
const BUTTONS: Array[Dictionary] = [
{name = 'A', id = JOY_BUTTON_A},
{name = 'B', id = JOY_BUTTON_B},
{name = 'X', id = JOY_BUTTON_X},
{name = 'Y', id = JOY_BUTTON_Y},
{name = 'LB', id = JOY_BUTTON_LEFT_SHOULDER},
{name = 'RB', id = JOY_BUTTON_RIGHT_SHOULDER},
{name = 'L3', id = JOY_BUTTON_LEFT_STICK},
{name = 'R3', id = JOY_BUTTON_RIGHT_STICK},
{name = 'Back', id = JOY_BUTTON_BACK},
{name = 'Start', id = JOY_BUTTON_START},
{name = 'Up', id = JOY_BUTTON_DPAD_UP},
{name = 'Down', id = JOY_BUTTON_DPAD_DOWN},
{name = 'Left', id = JOY_BUTTON_DPAD_LEFT},
{name = 'Right', id = JOY_BUTTON_DPAD_RIGHT},
]
var device: int = -1
var status: Label
var readout: Label
var knobs: Array[Panel] = []
var pills: Array[StyleBoxFlat] = []
var triggers: Array[ProgressBar] = []
func _ready() -> void:
var column: VBoxContainer = _root()
var title: Label = Label.new()
title.text = 'Gamepad viewer'
title.add_theme_font_size_override('font_size', 28)
column.add_child(title)
status = Label.new()
column.add_child(status)
var decks: HBoxContainer = HBoxContainer.new()
decks.add_theme_constant_override('separation', 40)
decks.alignment = BoxContainer.ALIGNMENT_CENTER
column.add_child(decks)
decks.add_child(_stick('left stick'))
decks.add_child(_trigger_deck())
decks.add_child(_stick('right stick'))
var grid: GridContainer = GridContainer.new()
grid.columns = 7
grid.add_theme_constant_override('h_separation', 10)
grid.add_theme_constant_override('v_separation', 10)
column.add_child(grid)
for entry: Dictionary in BUTTONS:
grid.add_child(_pill(entry.name))
readout = Label.new()
readout.add_theme_color_override('font_color', Color('#8da5f3'))
column.add_child(readout)
Input.joy_connection_changed.connect(_on_connection_changed)
_refresh_device()
print('move the sticks and press the buttons, the arrow keys also drive the ui vector')
func _process(_delta: float) -> void:
var left: Vector2 = _stick_value(JOY_AXIS_LEFT_X, JOY_AXIS_LEFT_Y)
var right: Vector2 = _stick_value(JOY_AXIS_RIGHT_X, JOY_AXIS_RIGHT_Y)
_move_knob(knobs[0], left)
_move_knob(knobs[1], right)
triggers[0].value = _axis(JOY_AXIS_TRIGGER_LEFT)
triggers[1].value = _axis(JOY_AXIS_TRIGGER_RIGHT)
for i in BUTTONS.size():
var down: bool = device >= 0 and Input.is_joy_button_pressed(device, BUTTONS[i].id)
pills[i].bg_color = Color('#8eef97') if down else Color('#2a2f3d')
var digital: Vector2 = Input.get_vector(&'ui_left', &'ui_right', &'ui_up', &'ui_down')
readout.text = 'left %.2f, %.2f right %.2f, %.2f ui_* vector %.2f, %.2f' % [left.x, left.y, right.x, right.y, digital.x, digital.y]
func _on_connection_changed(_which: int, _connected: bool) -> void:
_refresh_device()
func _refresh_device() -> void:
var pads: Array[int] = Input.get_connected_joypads()
device = pads[0] if pads.size() > 0 else -1
if device < 0:
status.text = 'no gamepad connected, plug one in and press a button'
status.add_theme_color_override('font_color', Color('#fc7f7f'))
return
status.text = 'device %d: %s' % [device, Input.get_joy_name(device)]
status.add_theme_color_override('font_color', Color('#8eef97'))
func _axis(axis: JoyAxis) -> float:
return Input.get_joy_axis(device, axis) if device >= 0 else 0.0
func _stick_value(x_axis: JoyAxis, y_axis: JoyAxis) -> Vector2:
var raw: Vector2 = Vector2(_axis(x_axis), _axis(y_axis))
return Vector2.ZERO if raw.length() < DEAD_ZONE else raw
func _move_knob(knob: Panel, value: Vector2) -> void:
knob.position = Vector2(STICK_RADIUS, STICK_RADIUS) + value * (STICK_RADIUS - KNOB_RADIUS) - Vector2(KNOB_RADIUS, KNOB_RADIUS)
func _stick(caption: String) -> VBoxContainer:
var column: VBoxContainer = VBoxContainer.new()
column.add_theme_constant_override('separation', 10)
var disc: Panel = _disc(STICK_RADIUS, Color('#1a1e28'), Color('#2a2f3d'))
disc.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
column.add_child(disc)
var knob: Panel = _disc(KNOB_RADIUS, Color('#8da5f3'), Color('#8da5f3'))
disc.add_child(knob)
knobs.append(knob)
_move_knob(knob, Vector2.ZERO)
var label: Label = Label.new()
label.text = caption
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.add_theme_color_override('font_color', Color('#9aa3b5'))
column.add_child(label)
return column
func _trigger_deck() -> VBoxContainer:
var column: VBoxContainer = VBoxContainer.new()
column.add_theme_constant_override('separation', 10)
column.alignment = BoxContainer.ALIGNMENT_CENTER
for caption in ['LT', 'RT']:
var label: Label = Label.new()
label.text = caption
label.add_theme_color_override('font_color', Color('#9aa3b5'))
column.add_child(label)
var bar: ProgressBar = ProgressBar.new()
bar.custom_minimum_size = Vector2(170, 20)
bar.max_value = 1.0
bar.show_percentage = false
bar.add_theme_stylebox_override('background', _bar_style(Color('#1a1e28')))
bar.add_theme_stylebox_override('fill', _bar_style(Color('#f3d48d')))
column.add_child(bar)
triggers.append(bar)
return column
func _pill(caption: String) -> PanelContainer:
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = Color('#2a2f3d')
style.set_corner_radius_all(8)
style.set_content_margin_all(10)
var box: PanelContainer = PanelContainer.new()
box.custom_minimum_size = Vector2(74, 0)
box.add_theme_stylebox_override('panel', style)
pills.append(style)
var label: Label = Label.new()
label.text = caption
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(label)
return box
func _disc(radius: float, fill: Color, border: Color) -> Panel:
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = fill
style.border_color = border
style.set_border_width_all(2)
style.set_corner_radius_all(int(radius))
var disc: Panel = Panel.new()
disc.custom_minimum_size = Vector2(radius * 2, radius * 2)
disc.size = Vector2(radius * 2, radius * 2)
disc.add_theme_stylebox_override('panel', style)
return disc
func _bar_style(color: Color) -> StyleBoxFlat:
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = color
style.set_corner_radius_all(6)
return style
func _root() -> VBoxContainer:
var background: ColorRect = ColorRect.new()
background.color = Color('#12141a')
add_child(background)
background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
var center: CenterContainer = CenterContainer.new()
add_child(center)
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
var column: VBoxContainer = VBoxContainer.new()
column.add_theme_constant_override('separation', 20)
center.add_child(column)
return column