this post was submitted on 12 May 2026
15 points (100.0% liked)

Godot

7610 readers
6 users here now

Welcome to the programming.dev Godot community!

This is a place where you can discuss about anything relating to the Godot game engine. Feel free to ask questions, post tutorials, show off your godot game, etc.

Make sure to follow the Godot CoC while chatting

We have a matrix room that can be used for chatting with other members of the community here

Links

Other Communities

Rules

We have a four strike system in this community where you get warned the first time you break a rule, then given a week ban, then given a year ban, then a permanent ban. Certain actions may bypass this and go straight to permanent ban if severe enough and done with malicious intent

Wormhole

!roguelikedev@programming.dev

Credits

founded 2 years ago
MODERATORS
 

I am working on the input and it feels like its getting out of hand. I wanted to check in with people and see how people structure their input handling.

Currently I have 1 function _unhandled_unput(event) and inside there I have a ton of elif statements trying to handle every possible situation and event. Its manageable at the moment but I only have like 4 events so its going to get very out of hand if I continue.

I need to have 100s of these events based on whats selected and what mouse/keyboard buttons are being pressed and I need some way to resuse the actions.

spoiler

func _unhandled_input(event):
	if event is InputEventMouseButton and event.pressed:
		if event.button_index == MOUSE_BUTTON_RIGHT:
			clear_selection()
			gui.queue_redraw()
			get_viewport().set_input_as_handled()
			return
	if selected_item == "colonist": #broken
		if event is InputEventKey:
			if event.OS.get_keycode_string() == "r":
				for colonist in selected_group:
					colonist.set_state("DRAFT")
					get_viewport().set_input_as_handled()
					gui.queue_redraw()
	#nothing selected dragbox to select things and single click to select things - does not work at the moment
	elif selected_type == "" or selected_type == "basic":
		if is_dragging and event is InputEventMouseMotion:
				drag_end = camera.get_global_mouse_position()
				cam_drag_end= get_viewport().get_mouse_position()
				get_selection(drag_start, drag_end)
				gui.queue_redraw()
				get_viewport().set_input_as_handled()
				return
		elif event is InputEventMouseButton and not event.pressed:
			is_dragging = false
			gui.queue_redraw()
			drag_start = null
			drag_end = null
			get_viewport().set_input_as_handled()
			return
		elif event is InputEventMouseButton and event.pressed:
			if event.button_index == MOUSE_BUTTON_LEFT:
				selected_type = "basic"
				is_dragging = true
				drag_start = camera.get_global_mouse_position()
				cam_drag_start = get_viewport().get_mouse_position()
				gui.queue_redraw()
				get_viewport().set_input_as_handled()
				return
	#command flow for dragging a selection box
	elif selected_type == "command":
		if selected_item == "structure_dict_growing":
			if is_dragging and event is InputEventMouseMotion:
				var grid_pos = tilemap.local_to_map(camera.get_global_mouse_position())
				var local_pos = tilemap.map_to_local(grid_pos)
				drag_end = local_pos + Vector2(32, 32)
				cam_drag_end = get_viewport().get_mouse_position()
				gui.queue_redraw()
				get_viewport().set_input_as_handled()
				return
			elif event is InputEventMouseButton and not event.pressed:
				if event.button_index == MOUSE_BUTTON_LEFT:
					is_dragging = false
					gui.queue_redraw()
					get_viewport().set_input_as_handled()
					MessageBus.rpc_id(1, "request_zone_growing", selected_item ,drag_start, drag_end, multiplayer.get_unique_id())
					drag_start = null
					drag_end = null
					return
			elif event is InputEventMouseButton and event.pressed:
				if event.button_index == MOUSE_BUTTON_LEFT:
					is_dragging = true
					#to snap to grid
					var grid_pos = tilemap.local_to_map(camera.get_global_mouse_position())
					var local_pos = tilemap.map_to_local(grid_pos)
					drag_start = local_pos - Vector2(32, 32)
					cam_drag_start = get_viewport().get_mouse_position() #this is broken cbf fixing maybe one day after selection is working 
					gui.queue_redraw()
					get_viewport().set_input_as_handled()
					return
	elif selected_type == "floor":
		if event is InputEventMouseButton and event.pressed:
			if event.button_index == MOUSE_BUTTON_LEFT:
				var global_mouse_pos = camera.get_global_mouse_position()
				var grid_pos = tilemap.local_to_map(global_mouse_pos)
				if selected_item == "":
					return
				MessageBus.rpc_id(1, "request_build_floor", selected_item, grid_pos, multiplayer.get_unique_id())
				get_viewport().set_input_as_handled()
				return
	elif selected_type == "building":
		if event is InputEventMouseButton and event.pressed:
			if event.button_index == MOUSE_BUTTON_LEFT:
				var global_mouse_pos = camera.get_global_mouse_position()
				var grid_pos = tilemap.local_to_map(global_mouse_pos)
				if selected_item == "":
					return
				MessageBus.rpc_id(1, "request_build_structure", selected_item, grid_pos, multiplayer.get_unique_id())
				get_viewport().set_input_as_handled()
				return

top 10 comments
sorted by: hot top controversial new old
[–] krow@lemmy.world 11 points 1 day ago (2 children)

recommend using godot's action system instead of checking for specific button inputs https://docs.godotengine.org/en/stable/getting_started/first_3d_game/02.player_input.html#creating-input-actions since it can more easily allow for controller support and remapping, unless there's a reason you are checking for specific keys? also is there a reason to using _unhandled_input() over _input()? Otherwise I would also recommend moving some of this logic into separate functions as a start

[–] Fizz@lemmy.nz 2 points 1 day ago (1 children)

The reason for checking keys is it is the only way i know how to do it. This input handling script is what im finding the most trouble with, a lot of the lines are added because I read a 6yr old reddit comment that said to use it and im not sure if I should remove it because then bugs start appearing.

The reason for unhandled input was the first input handling code example I saw and honestly I forgot about input(). I'll look into the creating input actions but at a glance it doesn't seem like it could work for my game. This game is pretty much left click right click on a 2d space and depending on what tool is selected.

I really do want to move some of the code into other functions but im not sure how to make a function that handles the input over multiple different states.

For example dragging a box. needs to check for the left click then check for the dragging state and draw on drag then check for a release. I have a lot of tools that will need to implement this so I'd really like to get this logic out of the main function and into a nice reusable block.

code example

	elif selected_type == "command":
		if selected_item == "structure_dict_growing":
			if is_dragging and event is InputEventMouseMotion:
				var grid_pos = tilemap.local_to_map(camera.get_global_mouse_position())
				var local_pos = tilemap.map_to_local(grid_pos)
				drag_end = local_pos + Vector2(32, 32)
				cam_drag_end = get_viewport().get_mouse_position()
				gui.queue_redraw()
				get_viewport().set_input_as_handled()
				return
			elif event is InputEventMouseButton and not event.pressed:
				if event.button_index == MOUSE_BUTTON_LEFT:
					is_dragging = false
					gui.queue_redraw()
					get_viewport().set_input_as_handled()
					MessageBus.rpc_id(1, "request_zone_growing", selected_item ,drag_start, drag_end, multiplayer.get_unique_id())
					drag_start = null
					drag_end = null
					return
			elif event is InputEventMouseButton and event.pressed:
				if event.button_index == MOUSE_BUTTON_LEFT:
					is_dragging = true
					#to snap to grid
					var grid_pos = tilemap.local_to_map(camera.get_global_mouse_position())
					var local_pos = tilemap.map_to_local(grid_pos)
					drag_start = local_pos - Vector2(32, 32)
					cam_drag_start = get_viewport().get_mouse_position()
					gui.queue_redraw()
					get_viewport().set_input_as_handled()
					return

[–] JaN0h4ck@feddit.org 4 points 1 day ago

You could have your Drag Box use the Area2D class and listen for it's events like this: (input_pickable need to be set to true)

class_name DraggableBox
extend Area2D

var mouse_inside: bool = false

func _ready():
  mouse_entered.connect(_on_mouse_entered)
  mouse_exited.connect(_on_mouse_exited)

func _process(delta: float):
  if (not mouse_inside): reuturn
  if (Input.is_action_pressed("name_for_action_with_mouse_click"):
    #handle your object movement
  if (Input.is_action_just_released("name_for_action_with_mouse_click"):
    #handle letting go of the object

func _on_mouse_entered():
  mouse_inside = true

func _on_mouse_exited():
  mouse_inside = false
[–] NightFantom@slrpnk.net 2 points 1 day ago

Good point, this probably also allows players to remap inputs if they want.

[–] lime@feddit.nu 6 points 1 day ago

if you hardcode letters for keys, you can't handle alternate keyboard layouts. without rebinding, that means some users will have to be contortionists to play.

[–] NightFantom@slrpnk.net 5 points 1 day ago* (last edited 1 day ago)

I'm mostly experienced in c++ and python, but I would use a dictionary/map/associative array/whatever name you call it, where you put keypress/event/... as key, and a function with the result you want as value (as a first version, there's other more specialised ways, but this will be enough for most cases).

If there's a case/switch statement in gdscript (that's what you're using I think?), you could also look into that, though I think it's likely just syntactic sugar for either what you're already doing or what I suggested, but it might be easier to read, so there's that.

Edit: https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#match

Gdscript calls it match operator, and I think it works like your elif tree but just a bit cleaner and easier to read

[–] null@lemmy.org 3 points 1 day ago (1 children)

inside there I have a ton of elif statements

I don't even use godot and I can already tell you this is not how you want to do it.

[–] Fizz@lemmy.nz 2 points 1 day ago (1 children)

Haha, yeah it feels wrong but im pretty not an experienced programmer so its all I know.

I am thinking if I could figure out how to put each type of command into its own function. The main input handling loop could just be calling a single line in response to an input event and that would be more managable. However im stuck because a lot of the logic requires multiple input events.

All the godot tutorials keep talking about state and after waiting 8 I still have no idea how to use state to control this.

[–] null@lemmy.org 3 points 1 day ago

I highly, highly recommend using the built-in system for receiving inputs. From there you can build whatever abstraction you want on top of it.

[–] Hisse@programming.dev 2 points 1 day ago

I just dump a lot of if statements into _process. One thing good about it is that it gets rid of some nesting and shortens it a bit