Input map by code

Five actions added with InputMap.add_action at startup, none of them declared in project settings. The panel lists the keys each one answers to and lights up while it is held.

The editor above is running this project. Change a line and it reloads.

Godot classes used

InputMapInputEventKeyInputPolygon2D

The code

scripts/main.gd
extends Node

const SPEED: float = 360.0
const BOOST: float = 2.4
const MARGIN: float = 44.0

const BINDINGS: Array[Dictionary] = [
	{action = &'demo_left', label = 'move left', keys = [KEY_A, KEY_LEFT]},
	{action = &'demo_right', label = 'move right', keys = [KEY_D, KEY_RIGHT]},
	{action = &'demo_up', label = 'move up', keys = [KEY_W, KEY_UP]},
	{action = &'demo_down', label = 'move down', keys = [KEY_S, KEY_DOWN]},
	{action = &'demo_boost', label = 'boost', keys = [KEY_SHIFT]},
]

var runner: Polygon2D
var rows: Array[HBoxContainer] = []


func _ready() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	for entry: Dictionary in BINDINGS:
		_register(entry.action, entry.keys)

	var view: Vector2 = get_viewport().get_visible_rect().size

	var bounds: Line2D = Line2D.new()
	bounds.width = 2.0
	bounds.default_color = Color('#2a2f3d')
	bounds.closed = true
	bounds.points = PackedVector2Array([
		Vector2(MARGIN, MARGIN),
		Vector2(view.x - MARGIN, MARGIN),
		Vector2(view.x - MARGIN, view.y - MARGIN),
		Vector2(MARGIN, view.y - MARGIN),
	])
	add_child(bounds)

	runner = _make_square(30.0, Color('#8da5f3'))
	runner.position = Vector2(view.x * 0.68, view.y * 0.5)
	add_child(runner)

	add_child(_panel())
	_paint_rows()

	print('WASD or the arrows move the square and shift boosts, every action was built in _ready')


func _process(delta: float) -> void:
	var direction: Vector2 = Input.get_vector(&'demo_left', &'demo_right', &'demo_up', &'demo_down')
	var speed: float = SPEED * (BOOST if Input.is_action_pressed(&'demo_boost') else 1.0)
	var view: Vector2 = get_viewport().get_visible_rect().size
	var inset: Vector2 = Vector2(MARGIN + 30.0, MARGIN + 30.0)

	runner.position += direction * speed * delta
	runner.position = runner.position.clamp(inset, view - inset)

	_paint_rows()


func _register(action: StringName, keys: Array) -> void:
	if InputMap.has_action(action):
		InputMap.erase_action(action)

	InputMap.add_action(action)

	for key in keys:
		var event: InputEventKey = InputEventKey.new()
		event.keycode = key
		InputMap.action_add_event(action, event)


func _paint_rows() -> void:
	for i in BINDINGS.size():
		var color: Color = Color('#8eef97') if Input.is_action_pressed(BINDINGS[i].action) else Color('#9aa3b5')

		for child in rows[i].get_children():
			(child as Label).add_theme_color_override('font_color', color)


func _panel() -> PanelContainer:
	var style: StyleBoxFlat = StyleBoxFlat.new()
	style.bg_color = Color('#2a2f3d')
	style.set_corner_radius_all(14)
	style.set_content_margin_all(24)

	var panel: PanelContainer = PanelContainer.new()
	panel.position = Vector2(48, 48)
	panel.custom_minimum_size = Vector2(430, 0)
	panel.add_theme_stylebox_override('panel', style)

	var column: VBoxContainer = VBoxContainer.new()
	column.add_theme_constant_override('separation', 10)
	panel.add_child(column)

	var title: Label = Label.new()
	title.text = 'Actions created at runtime'
	title.add_theme_font_size_override('font_size', 22)
	column.add_child(title)

	var hint: Label = Label.new()
	hint.text = 'none of them exists in project settings'
	hint.add_theme_color_override('font_color', Color('#f3d48d'))
	column.add_child(hint)

	for entry: Dictionary in BINDINGS:
		var row: HBoxContainer = HBoxContainer.new()
		column.add_child(row)
		rows.append(row)

		var name_label: Label = Label.new()
		name_label.text = entry.label
		name_label.custom_minimum_size = Vector2(150, 0)
		row.add_child(name_label)

		var keys_label: Label = Label.new()
		keys_label.text = _keys_text(entry.keys)
		row.add_child(keys_label)

	var footer: Label = Label.new()
	footer.text = 'Input.get_vector reads the first four as one Vector2'
	footer.add_theme_color_override('font_color', Color('#8da5f3'))
	column.add_child(footer)

	return panel


func _keys_text(keys: Array) -> String:
	var names: PackedStringArray = PackedStringArray()

	for key in keys:
		names.append(OS.get_keycode_string(key))

	return ' or '.join(names)


func _make_square(half: float, color: Color) -> Polygon2D:
	var square: Polygon2D = Polygon2D.new()
	square.polygon = PackedVector2Array([Vector2(-half, -half), Vector2(half, -half), Vector2(half, half), Vector2(-half, half)])
	square.color = color

	return square

More Input examples