Godot Version
4.2.2
Question
I have an issue with my game using a state machine. All my characters have 5 states. Idle, Walk, Attack, Special, and Hitstun. After using your special, if you hold down and spam attack, all states stop working. What could be causing this? Here’s all my relevant code.
Special attack
extends State
class_name BobSpecial1
@export var Bob : CharacterBody2D
@export var fsm : StateMachine
func Enter():
Bob.special()
func Update(delta):
if PlayerVariables.p1_knockback == true:
fsm.force_change_state("Hitstun")
func _on_animation_player_animation_finished(anim_name):
if anim_name == "Dash Attack" and !Input.get_axis("p1_Left", "p1_Right"):
fsm.change_state(self, "Idle")
if anim_name == "Dash Attack" and Input.get_axis("p1_Left", "p1_Right"):
fsm.change_state(self, "Walk")
if anim_name == "Dash Attack" and Input.is_action_just_pressed("p1_Attack") and !Input.is_action_pressed("p1_Down"):
fsm.change_state(self, "Attack")
if anim_name == "Dash Attack" and Input.is_action_pressed("p1_Down") and Input.is_action_just_pressed("p1_Attack"):
fsm.change_state(self, "Special")
Idle state
extends State
class_name BobIdle1
@export var Bob : CharacterBody2D
@export var fsm : StateMachine
func Enter():
Bob.idle()
func Update(delta):
if Input.get_axis("p1_Left", "p1_Right"):
fsm.change_state(self, "Walk")
if Input.is_action_just_pressed("p1_Attack") and !Input.is_action_pressed("p1_Down"):
fsm.change_state(self, "Attack")
if Input.is_action_pressed("p1_Down") and Input.is_action_just_pressed("p1_Attack"):
fsm.change_state(self, "Special")
if PlayerVariables.p1_knockback == true:
fsm.force_change_state("Hitstun")
Hitstun state
extends State
class_name BobHitstun1
@export var Bob : CharacterBody2D
@export var fsm : StateMachine
func Enter():
Bob.hitstun()
func Update(delta):
if PlayerVariables.p1_knockback == true:
fsm.force_change_state("Hitstun")
func _on_animation_player_animation_finished(anim_name):
if anim_name == "Hitstun" and !Input.get_axis("p1_Left", "p1_Right"):
fsm.change_state(self, "Idle")
if anim_name == "Hitstun" and Input.get_axis("p1_Left", "p1_Right"):
fsm.change_state(self, "Walk")
if anim_name == "Hitstun" and Input.is_action_pressed("p1_Down") and Input.is_action_just_pressed("p1_Attack") and !Input.get_axis("p1_Left", "p1_Right"):
fsm.change_state(self, "Special")
if anim_name == "Hitstun" and Input.is_action_just_pressed("p1_Attack") and !Input.is_action_pressed("p1_Down"):
fsm.change_state(self, "Attack")
Walk state
extends State
class_name BobWalk1
@export var Bob : CharacterBody2D
@export var fsm : StateMachine
func Physics_Update(delta):
var direction = Input.get_axis("p1_Left", "p1_Right")
Bob.walk(direction)
if direction == 0:
fsm.change_state(self, "Idle")
if Input.is_action_just_pressed("p1_Attack") and !Input.is_action_pressed("p1_Down"):
fsm.change_state(self, "Attack")
if Input.is_action_pressed("p1_Down") and Input.is_action_just_pressed("p1_Attack"):
fsm.change_state(self, "Special")
if PlayerVariables.p1_knockback == true:
fsm.force_change_state("Hitstun")
Attack state
extends State
class_name BobAttack1
@export var Bob : CharacterBody2D
@export var fsm : StateMachine
func Enter():
Bob.attack()
func Update(delta):
if PlayerVariables.p1_knockback == true:
fsm.force_change_state("Hitstun")
func _on_animation_player_animation_finished(anim_name):
if anim_name == "Light" and !Input.get_axis("p1_Left", "p1_Right"):
fsm.change_state(self, "Idle")
if anim_name == "Light" and Input.get_axis("p1_Left", "p1_Right"):
fsm.change_state(self, "Walk")
if anim_name == "Light" and Input.is_action_pressed("p1_Down") and Input.is_action_just_pressed("p1_Attack") and !Input.get_axis("p1_Left", "p1_Right"):
fsm.change_state(self, "Special")
if anim_name == "Light" and Input.is_action_just_pressed("p1_Attack") and !Input.is_action_pressed("p1_Down"):
fsm.change_state(self, "Attack")
State machine
extends Node
class_name StateMachine
var states: Dictionary = {}
var current_state : State
@export var initial_state : State
func _ready():
for child in get_children():
if child is State:
states[child.name.to_lower()] = child
child.state_transition.connect(change_state)
if initial_state:
initial_state.Enter()
current_state = initial_state
func _process(delta):
if current_state:
current_state.Update(delta)
func _physics_process(delta):
if current_state:
current_state.Physics_Update(delta)
func force_change_state(new_state : String):
var newState = states.get(new_state.to_lower())
if !newState:
return
if current_state == newState:
return
if current_state:
var exit_callable = Callable(current_state, "Exit")
exit_callable.call_deferred()
newState.Enter()
current_state = newState
func change_state(source_state : State, new_state_name : String):
if source_state != current_state:
return
var new_state = states.get(new_state_name.to_lower())
if !new_state:
print("New state is empty")
return
if current_state:
current_state.Exit()
new_state.Enter()
current_state = new_state
Relevant character (Bob) script
func idle():
anim.play("Idle")
velocity.x = move_toward(velocity.x, 0, SPEED)
func special():
if can_dash == true:
anim.play("Dash Attack")
timer.start()
can_dash = false
dash_timer.start()
velocity.x = move_toward(velocity.x, 0, SPEED)
func walk(direction):
velocity.x = direction * SPEED
anim.play("Walk")
if direction > 0:
anim_sprite.scale.x = 1
PlayerVariables.p1_dir = 1
if direction < 0:
anim_sprite.scale.x = -1
PlayerVariables.p1_dir = -1
func attack():
anim.play("Light")
velocity.x = move_toward(velocity.x, 0, SPEED)
func hitstun():
velocity.y = -500
velocity.x = 100 * PlayerVariables.p2_dir
anim.play("Hitstun")
PlayerVariables.p1_knockback = false