Godot Version
4.6
Question
So, I’m following a tutorial for building a top down player state machine. I have the Idle and Walk states working, but the state machine uses unhandled input to detect button press for Attack. I cannot get the function to fire. Nothing I’ve checked that’s active should be eating my inputs.
the function in the player state machine script:
func _unhandled_input(event: InputEvent) -> void:
print("unhandled input event: ", event)
change_state(current_state.handle_input(event))
pass
func change_state(new_state: State) -> void:
if new_state == null || new_state == current_state:
return
if current_state:
current_state.exit()
prev_state = current_state
current_state = new_state
current_state.enter()
It should listen for the input event, then poll the current state if it cares about that input. if so it returns the state it should switch to as below, and then kicks off that state.
func handle_input(_event: InputEvent) -> State:
print("got input: ", _event)
if _event.is_action_pressed("attack"):
return attack
return null
The issue is, I’m not even seeing the first debug print appear, so it’s never even receiving the input. I tried directly telling the state machine change_state(attack) from my player script by doing:
func _process() -> void:
if Input.is_action_pressed("attack"):
state_machine.change_state(attack)
and the state triggered properly.
A. Is there a way to tell what is eating my input so that it isn’t getting down to the state machine as unhandled?
B. From a State Machine design perspective, is there a reason that I should be using unhandled input, instead of something else? (doing if Input.is_action_pressed directly in the process function of the state, for example). The state machine tutorial I’m following is like 2 years old, but most of the newer ones are for side-scrollers and I wanted something closer to what I was making.
GUI could be handling the input, you may be able to check in the Debug panels, “Misc” tab will show you the last thing you clicked on. If your “attack” action is a mouse click then it would make sense for Control nodes to consume it’s input before reaching other nodes. If you can’t sort out what is consuming the input you can use _input instead of _unhandled_input to check all events.
What @gertkeno said. You haven’t told us what is mapped to the “attack” action. A screenshot of the InputMap would be helpful.
Attack is mapped to joypad face button bottom, space bar, and to try to make sure it wasn’t getting handled as “ui_accept”, I also mapped it to the M key which isn’t used for anything by built-ins.
As a test, I added left mouse click, and tested the Misc section and there were some full screen hidden UI menus absorbing my clicks, so I set them to ignore, and tried again and nothing showed up in Misc and it also didn’t fire the unhandled input. The only thing active other than the player that should be listening for inputs are the main.gd listening for pause, and a HUD UI element listening for dpad left and right to swap items:
func _process(delta: float) -> void:
if Input.is_action_just_pressed("pause"):
toggle_pause()
player.is_paused = !player.is_paused
and
func _process(delta: float) -> void:
#if selected_slot > active_size:
# selected_slot = active_size
if Input.is_action_just_pressed("item_select_right"):
if active_size != 0:
if selected_slot != active_size - 1:
arrow_flash(right_arrow)
selected_slot += 1
set_selected_item(selected_slot)
else:
return
if Input.is_action_just_pressed("item_select_left"):
if active_size != 0:
if selected_slot != 0:
arrow_flash(left_arrow)
selected_slot -= 1
set_selected_item(selected_slot)
else:
return
Also, I tried switching to _input instead of unhandled and it still didn’t fire:
func _input(event: InputEvent) -> void:
print("input event: ", event)
change_state(current_state.handle_input(event))
pass
but if I put a check into the process of the active state, it did fire, passing everything correctly through the same script that can’t detect the _input event :
func process(_delta) -> State:
if player.input_direction != Vector2.ZERO:
return move
if Input.is_action_pressed("attack"):
return attack
player.play_anim("idle")
player.velocity = Vector2.ZERO
return null
I can bypass the method from the tutorial, but it’s weirding me out that the script can’t seem to hear these built-in functions.
Can you share your full script? Does it extend a Node type or something else?