Why the character, enters in stunned state two times?

Godot Version

4.2.2

Question

Hi, i need help, my character for some reason enters on the stunned state two times, i added some prints in there to debug the change of the states, and i tried add some boolean variables to stop the change but i keeps changing the state again, the code of the state machine is this one:

class_name CharacterStateMachine
extends Node

@export var rising_gravity : int = 15
@export var fast_falling_gravity : int = 20
@export var _character : CharacterBody2D
@export var current_state : State
@export var attacked_state : State
var _states : Array[State]

func _ready():
for child in get_children():
if (child is State):
_states.append(child)
child._character = _character
else:
push_warning("Child " + child.name + “is not in State for this Character”)

func _physics_process(delta):
if(current_state.next_state != null):
switch_states(current_state.next_state)
current_state.state_process(delta)

func switch_states(next_state : State):
if(current_state != null):
current_state.on_enter_state()
current_state.next_state = null
current_state = next_state
current_state.on_enter_state()

func check_if_can_move():
return current_state.can_move

func check_if_can_jump():
return current_state.can_jump

func check_if_is_stunned():
return current_state.is_stunned

func apply_gravity():
if(current_state.apply_gravity):
if(!_character.is_on_floor()):
_character.velocity.y += rising_gravity
if(_character.velocity.y > 0):
_character.velocity.y += fast_falling_gravity
_character.move_and_slide()

func _input(_event : InputEvent):
current_state.state_input(_event)

and this one is the idle state, where it returns:

class_name IdleState
extends State


@export var brax_actions_animation_player : AnimationPlayer
@export var brax_state_machine_functions: CharacterStateMachine
@export var air_state : State
@export var walking_state : State
@export var attack_state : State
@export var idle_state : State
@export var brax_jump_sound_effect : AudioStreamPlayer2D


func state_process(delta):
	brax_actions_animation_player.speed_scale = 1.0;
	_apply_state_gravity()
	_walk()
	_when_character_is_on_floor()


func state_input(_event : InputEvent):
	if (
		_event.is_action_pressed("JUMP") and
		brax_state_machine_functions.check_if_can_jump()
		):
		_jump()
	
	elif (
		_event.is_action_pressed("NORMAL_ATTACK") and
		_character.velocity.y == 0
		):
		_floor_attack()
		
	if (
		Input.is_action_pressed("UP") and
		_event.is_action_pressed("SECONDARY_ATTACK") and
		_character.is_on_floor()
		):
		_uppercut_attack()
	
	
func _apply_state_gravity():
	brax_state_machine_functions.apply_gravity()


func _when_character_is_on_floor():
	if _character.is_on_floor():
		_character.velocity = Vector2.ZERO


func _walk():
	if brax_state_machine_functions.check_if_can_move():
		if Input.is_action_pressed("RIGHT"):
			next_state = walking_state
		elif Input.is_action_pressed("LEFT"):
			next_state = walking_state
	
	
func _uppercut_attack():
	brax_actions_animation_player.play("up_punch_buildup")
	brax_actions_animation_player.speed_scale = 3.0
	next_state = attack_state
	
	
func _jump():
	brax_jump_sound_effect.play()
	_character.velocity.y = base_jump_height
	next_state = air_state
	
	
func _floor_attack():
	next_state = attack_state
	brax_actions_animation_player.speed_scale = 2.0
	brax_actions_animation_player.play("punch_normal")

and this one is the, walk state, where it enters to the stunned state:

class_name WalkingState
extends State




@export var getting_hit_area : Area2D

#Enemigos que seran detectados por el jugador
@onready var lurker_enemy = get_tree().get_first_node_in_group("Enemies")

@export var brax_state_machine_functions : CharacterStateMachine
@export var brax_sprite : Sprite2D

#Estados Cambio
@export var idle_state : State
@export var air_state : State
@export var dash_state : State
@export var attack_state : State
@export var stunned_state : State

@export var brax_jump_sound_effect : AudioStreamPlayer2D
@export var brax_actions_animation_player : AnimationPlayer


func state_process(delta):
	_apply_state_gravity()
	_walk()
	_stop_walk()
	_after_falling_of_a_platform()
	_attack_while_walking()


func state_input(_event : InputEvent):
	if (
		_event.is_action_pressed("JUMP") and
		_character.is_on_floor() and
		brax_state_machine_functions.check_if_can_jump()
		):
		_jump()
	if (
		Input.is_action_pressed("RIGHT") and
		_event.is_action_pressed("DASH")
		):
		next_state = dash_state
	if (
		Input.is_action_pressed("LEFT") and
		_event.is_action_pressed("DASH")
		):
		next_state = dash_state
	
	
func _walk():
	if brax_state_machine_functions.check_if_can_move():
		if Input.is_action_pressed("RIGHT"):
			brax_sprite.flip_h = false
			_character.velocity.x = walk_speed
		elif Input.is_action_pressed("LEFT"):
			brax_sprite.flip_h = true
			_character.velocity.x = -walk_speed
		
		
func _stop_walk():
	if (
		Input.is_action_just_released("RIGHT") and
		not _character.velocity.x < 0
		):
		_character.velocity = Vector2.ZERO
	elif (
		Input.is_action_just_released("LEFT") and
		not _character.velocity.y > 0
		):
		_character.velocity = Vector2.ZERO
	elif _character.velocity == Vector2.ZERO:
		next_state = idle_state
	
	
func _apply_state_gravity():
	brax_state_machine_functions.apply_gravity()
	

func _after_falling_of_a_platform():
	if _character.velocity.y > 0 :
		_character.velocity = Vector2.ZERO
		next_state = air_state
		
		
func _attack_while_walking():
	if Input.is_action_just_pressed("NORMAL_ATTACK"): #Detener el movimiento mientras brax ataca al moverse
		_character.velocity.x = 0
		next_state = attack_state
	
	
func _jump():
	brax_jump_sound_effect.play()
	_character.velocity.y = base_jump_height
	next_state = air_state


func _on_getting_hit_body_entered(body):
	print("KNOCBACK POR CAMINAR")
	body = lurker_enemy
	next_state = stunned_state

and this one is the stunned state:

class_name StunnedState
extends State

@export var brax_state_machine_functions : CharacterStateMachine
@export var brax_actions_animation_player : AnimationPlayer
@onready var lurker_enemy = get_tree().get_first_node_in_group("Enemies")
@export var idle_state : State



func on_enter_state():
	print("STUNNEADO")
	brax_actions_animation_player.play("damage_taken")
	var knockback_direction = (_character.global_position - lurker_enemy.global_position).normalized()
	_character.velocity = 170 * knockback_direction
	_character.velocity.y = -300

func state_process(delta):
	_apply_state_gravity()
	

func _apply_state_gravity():
	brax_state_machine_functions.apply_gravity()
	

func _on_brax_actions_animations_animation_finished(anim_name):
	if anim_name == "damage_taken" && _character.is_on_floor():
		_character.velocity = Vector2.ZERO
		next_state = idle_state

in the stunned state, there is a knockback effect, that is applied even, when it changes to the idle state, i would greatly appreciate the help, this bug is making me crazy

Can’t say for sure because for some reason you formatted everything properly to post except the first part of the code, and therefore I am only guessing on indentation.
However if this is what it looks like…

func switch_states(next_state : State):
   if(current_state != null):
      current_state.on_enter_state()
      current_state.next_state = null
      current_state = next_state
      current_state.on_enter_state()

then
current_state = A
The interpreter reaches this line and runs on_enter_state() for state A…

   if(current_state != null):
      current_state.on_enter_state()

Then the current state is set to the next state (lets call that B)
Then on_enter_state() is run for state B…

      current_state.next_state = null
      current_state = next_state
      current_state.on_enter_state()

Now we want to change to the next state C
We come back to the if statement. The state is still B and we run on_enter_state() again.

   if(current_state != null):
      current_state.on_enter_state()

So we end up running on_enter_state() twice for each state except possibly the very first state.

My guess is that the first call to on_enter_state() (immediately after the if statement) should actually be a call to on_exit_state(), however I don’t see that function in the listed code.

2 Likes

Hi, thanks for commenting, yeah, the first part of the code, was copied like this, because , this is my first time, asking a question here, sorry about that, but… yeah, i changed the thing you said there, and yeah¡, is working now, you see, i changed the name of some functions, because i have another scene with the same state machine, so i wanted to identify them separately, so maybe i made the mistake in replacing the function there, but yeah, it was that, thank you very much, really, you saved my life man.

You actually read the whole post :astonished:! You have an incredible amount of patience!

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.