Godot Version
Godot 4.2-stable
Question
Hello! I’m trying to know how to improve this player input / controller class I made where the player’s inputs are cached by the class and then is processed within an unnoticeable timeframe before executing the input.
A reason why I opted for this approach is due to the fact I’m currently prototyping a beat em’ up and I wanted to send ‘events’ that the player character will react to depending on the sequence of inputs the player pressed. I initially also wanted to make it “sequence-agnostic” where, for example, pressing either up+attack or attack+up in very quick succession will result in the playable character doing an ‘up attack’.
I’m also using a plugin called ‘Godot StateCharts’ which helped me immensely in setting up the playable character’s states but made it necessary for me to opt-in into sending events this way.
Is there anything I could learn to improve this?
class_name PlayerInputScript
extends Node
@export_category("Dependency Injections")
@export var state_chart : StateChart = null
@export var horizontal_movement_script : PlayerHorizontalMovementScript = null
@export_category("Input Settings")
@export var input_timeout_time : float = 0.1
@export var maximum_inputs_in_queue : int = 2
var action_queue : Array = []
var recently_pressed_inputs : Array = []
var unhandled_actions : Array = ["move_left", "move_right"] #NOTE: THESE ARE INPUTMAP STRINGS!
var instant_actions : Array = ["jump", "attack", "dodge"]
var basic_actions : Array = ["jump", "attack", "dodge", "look_up", "look_down"] #NOTE: THESE TOO ARE INPUTMAP STRINGS!
var compound_actions : Dictionary = {
"upward_attack" : ["look_up", "attack"],
"downward_attack" : ["look_down", "attack"],
"jump_down" : ["look_down", "jump"],
}
var is_input_window_active : bool = false
var current_input_window_time : float = 0.0
# called by an 'unhandled_input' signal sent by a State in the State Chart which simulates a _unhandled_input function call
func queue_input(input : InputEvent):
#region immediately returns/cancels the function if these checks are failed
if !(input is InputEventKey):
return
if !(input.is_pressed()):
return
for action in unhandled_actions:
if input.is_action_pressed(action):
return
#endregion
for action in basic_actions:
if input.is_action_pressed(action):
recently_pressed_inputs.push_back(action)
if !is_input_window_active:
is_input_window_active = true
# called 'state_processing' signal sent by a State in the State Chart which simulates a _processing function call
func parse_input(delta : float):
if !is_input_window_active:
reset_input_arrays()
return
if current_input_window_time >= input_timeout_time:
reset_input_arrays()
return
current_input_window_time += delta
#this will add an item in the 'action_queue' array if conditions are met.
check_for_compound_actions()
if action_queue.is_empty():
return
is_input_window_active = false
current_input_window_time = 0
for compound_action in action_queue.duplicate():
match compound_action:
"jump_down":
action_queue.pop_front()
state_chart.send_event("player_jumped_down")
"upward_attack":
action_queue.pop_front()
state_chart.send_event("player_attacked_upwards")
"downward_attack":
action_queue.pop_front()
state_chart.send_event("player_attacked_downwards")
for basic_action in action_queue.duplicate():
match basic_action:
"jump":
action_queue.pop_front()
state_chart.send_event("player_jumped")
"dodge":
action_queue.pop_front()
state_chart.send_event("player_dodged")
"attack":
action_queue.pop_front()
state_chart.send_event("player_attacked")
func check_for_compound_actions():
if recently_pressed_inputs.is_empty():
return
if recently_pressed_inputs.front() in instant_actions:
action_queue.push_front(recently_pressed_inputs[0])
return
for compound_action in compound_actions.keys():
var required_inputs = compound_actions[compound_action]
var input_sequence = []
for required_input in required_inputs:
if required_input in recently_pressed_inputs.duplicate():
input_sequence.push_back(required_input)
input_sequence.sort()
required_inputs.sort()
if input_sequence == required_inputs:
reset_input_arrays()
action_queue.push_back(compound_action)
return
func disable_input_parsing():
horizontal_movement_script.is_input_disabled = true
state_chart.send_event("input_disabled")
func enable_input_parsing():
horizontal_movement_script.is_input_disabled = false
state_chart.send_event("input_enabled")
func reset_input_arrays():
recently_pressed_inputs.clear()
action_queue.clear()
current_input_window_time = 0