Input checking with actions - best practice

Godot Version

4.5.1 Stable

Question

I’ve been scouring the documentation on input.

I figured the most modular way to go about getting user input for my game is to let each node that needs input handle its own input checking.

But something feels wrong about checking input from the InputMap using a string - what if I decide to change the name of the input action in the project settings? Or decide to reuse a node in another project?

I sort of solved this by creating @export variables for the different inputs that I want a node to check for to make it easier to update the names of each input action. Is this generally the recommended way to handle predefined input actions?

Lastly, if I do change an input action name or make an error when typing the name into the @export variables, there will be an error when the node attempts to check that input in the script. Is it better to leave this as is, or should I check InputMap.has_input() every time before actually checking the input? In the second case I could use push_warning() to notify me that the input action doesn’t exist without crashing the game.

Is there a preferred “standard” way to manage input actions? The documentation is quite simplified in the examples.

1 Like

Yes you will get a warning when checking for inputs that do not exist in the project settings.

If you do intend to reuse your scripts in other projects then yes adding a @export for the desired input action name sounds good, otherwise most projects do not spend a lot of time renaming actions.

As @gertkeno said, it’s not common to rename input actions. Misspelling them shouldn’t be a problem either, as most functions that take them will provide auto-completion.

Still, if you don’t want to use strings then you could generate a script with the input event names as static variables. For example, you could use an EditorScript like:

@tool
extends EditorScript


func _run() -> void:
    InputMap.load_from_project_settings()
    var inputs = InputMap.get_actions().filter(func(input_name:StringName): return input_name.find(".") == -1)

    var inputs_string = "\n".join(inputs.map(func(input): return 'static var {name}:StringName = &"{name}"'.format({"name": input})))

    var script_content = \
"""
class_name InputActions extends RefCounted

{inputs}

""".format({"inputs": inputs_string})

    print(script_content)

    var script = GDScript.new()
    script.source_code = script_content
    ResourceSaver.save(script, 'res://input_actions.gd')

Will create a script input_actions.gd like:

class_name InputActions extends RefCounted

static var ui_accept:StringName = &"ui_accept"
static var ui_select:StringName = &"ui_select"
static var ui_cancel:StringName = &"ui_cancel"
static var ui_focus_next:StringName = &"ui_focus_next"
# ... more here

This wouldn’t update when you change them because EditorScripts are short-lived and will be deleted once they run. You would need to make a plugin so you can hook into the ProjectSettings.settings_changed and generate the script when they change.

Another option, if you still want to export them in nodes, is to generate the list of actions in Object._get_property_list() like:

@tool
extends Node


var left_action: String
var right_action: String


func _get_property_list() -> Array[Dictionary]:
	var actions = []
	for prop in ProjectSettings.get_property_list():
		var prop_name:String = prop.get("name", "")
		if prop_name.begins_with('input/'):
			prop_name = prop_name.replace('input/', '')
			prop_name = prop_name.substr(0, prop_name.find("."))
			if not actions.has(prop_name):
				actions.append(prop_name)

	var hint_string = ",".join(actions)

	var properties: Array[Dictionary] = []
	properties.append({
		"name": "left_action",
		"type": TYPE_STRING_NAME,
		"hint": PROPERTY_HINT_ENUM,
		"hint_string": hint_string
	})
	properties.append({
		"name": "right_action",
		"type": TYPE_STRING_NAME,
		"hint": PROPERTY_HINT_ENUM,
		"hint_string": hint_string
	})
	return properties

(You could use PROPERTY_HINT_ENUM_SUGGESTION instead if you want to have the option to type them)

This won’t help you if you rename them later though.

1 Like