Creating a keyboard-only menu with a moving cursor

Godot Version

v4.4.stable.official [4c311cbee]

Question

Hello all. I am developing an old-school-style game and I would like to emulate a lot of the controls for them. One of those features is a menu that operates with keyboard only, moving a cursor to the selected option (reminiscent of FF or other NES-era RPGs).

Here is a rough-draft of the concept (please ignore the animation player; I removed that node):

I would like to make this menu interactive by allowing the player to select their choice moving the cursor up or down and pressing a “CONFIRM” key (like Spacebar or Enter, for example).

The tutorials I have looked up mostly feature the use of buttons, which is fine but require a lot of tinkering to get them to look correct with themes. Not only that but they confuse me and the player when we hover over them with the mouse and it reacts to it. I would like to keep this keyboard-only to simulate that old-school computer feel.

Any advice is greatly appreciated.

I love the old school RPG and I have built this menu many times.
Some tips:

  • Build a keyboard input component. The only thing this scene does is emit signals based on user keyboard input.
  • Build a cursor scene and script. A Sprite2D for the cursor image can be the root node or you can use a Control node.
  • The cursor scene is also a component that can be dropped into any menu that requires a cursor
  • The cursor scene acts on signals connected to the keyboard component; up/down/left/right/space or enter
  • The cursor scene can either set up its movements via dynamic math calculations but honestly, the easiest way is to simply export a cursor_stops[] array. The latter method makes paging so much easier.
  • The cursor scene emits two basic signals, accept_pressed, escape_pressed and the menu scene connects to those signals to act on them; ie start game , load game, exit
  • Drop an instance of the input component in your cursor scene. Note that it is deactivated until the menu and cursor is needed.

Here is a very basic keyboard input component.

#-------------------------------------------------------------------------------
# UserInput
#-------------------------------------------------------------------------------
signal space
signal enter
signal keypress(key:String) # you can make these individual signals as well 
signal esc

func _ready() -> void:
	set_process_unhandled_key_input(false)

func activate(process_active:bool = false)->void: 
	set_process_unhandled_key_input(true)

func deactivate()->void: 
	set_process_unhandled_key_input(false)

func _unhandled_key_input(event: InputEvent) -> void:  
	if event.is_action_pressed("ui_accept"):
		enter.emit()
	elif event.is_action_pressed("ui_down"): 
		keypress.emit("ui_down")
	elif event.is_action_pressed("ui_up"): 
		keypress.emit("ui_up")
	elif event.is_action_pressed("ui_left"): 
		keypress.emit("ui_left")
	elif event.is_action_pressed("ui_right"): 
		keypress.emit("ui_right")
	elif event.is_action_pressed("ui_select"): 
		space.emit()
	elif event.is_action_pressed("ui_cancel"): 
		esc.emit()
1 Like