Timer not working correctly

Godot Version

4.3

Question

I’m trying to remake cuphead’s movement and I’m using a timer for the dash’s duration. It sometimes looks like it’s working perfectly, but other times it just cuts mid-air. This only happens when I do the dash in the air and going in the same direction as the last dash. Here’s a video of what happens:

As you can see, this only happens sometimes. Here’s the code:

extends CharacterBody2D


@onready var anim = $AnimatedSprite2D
@onready var max_jump_timer = $Timers/max_jump_timer
@onready var coyote_timer = $Timers/coyote_timer
@onready var dash_timer = $Timers/dash_timer

var speed = 110
var max_speed = 550
var gravity = 0
var jump_force = 1000
var gravity_force = 70
var gravity_force_default = 70
var gravity_force_during_coyote = 40
var dash_speed = 1300

var direction = Vector2.ZERO
var grounded = true
var jump_started = false
var coyote_started = false
var can_coyote = false
var dash_started = false
var can_dash = true

var anim_crouch_started = false
var anim_dash_started = false

var can_move = true
var affected_by_gravity = true
var can_chage_direction = true

var cur_state = STATE.idle

enum STATE {
	idle,
	idle_shoot,
	run,
	run_shoot,
	jump,
	fall,
	dash,
	parry,
	crouch,
	crouch_shoot
}

func _process(delta: float) -> void:
	
	if Input.is_action_just_pressed("restart"):
		position = Vector2(185, 300)
	
	
	direction = Input.get_vector("left", "right", "up", "down")
	if can_move:
		if abs(velocity.x) < max_speed:
			velocity.x += speed * direction.x
		else:
			velocity.x = max_speed * direction.x
	else:
		velocity.x = 0
	
	if can_chage_direction:
		if direction.x > 0:
			anim.flip_h = false
		elif direction.x < 0:
			anim.flip_h = true
	
	grounded = is_on_floor()
	
	#State Machine
	match cur_state:
		STATE.idle:
			can_move = true
			affected_by_gravity = true
			
			velocity.x = 0
			anim.play("idle")
			
			if not grounded:
				cur_state = STATE.fall
			else:
				if direction.x != 0:
					cur_state = STATE.run
				if direction.y > 0.3:
					cur_state = STATE.crouch
			
			if Input.is_action_just_pressed("jump"):
				cur_state = STATE.jump
			
			#if Input.is_action_just_pressed("shoot"):
				#cur_state = STATE.idle_shoot
			
			if Input.is_action_just_pressed("dash") and can_dash:
				cur_state = STATE.dash
			
			if Input.is_action_just_pressed("down"):
				cur_state = STATE.crouch
			
		STATE.idle_shoot:
			pass
		STATE.run:
			can_move = true
			affected_by_gravity = true
			
			anim.play("run")
			
			if direction.x == 0:
				cur_state = STATE.idle
			
			#if Input.is_action_just_pressed("shoot"):
				#cur_state = STATE.run_shoot
			
			if Input.is_action_just_pressed("jump"):
				cur_state = STATE.jump
			
			if Input.is_action_just_pressed("dash") and can_dash:
				cur_state = STATE.dash
			
			if Input.is_action_just_pressed("down"):
				cur_state = STATE.crouch
			
			if not grounded:
				can_coyote = true
				cur_state = STATE.fall
			
		STATE.run_shoot:
			pass
		STATE.jump:
			affected_by_gravity = true
			can_move = true
			anim.play("jump")
			gravity = -jump_force
			
			if Input.is_action_just_released("jump"):
				cur_state = STATE.fall
			
			if not jump_started:
				jump_started = true
				max_jump_timer.start()
			
			if Input.is_action_just_pressed("dash") and can_dash:
				cur_state = STATE.dash
			
		STATE.fall:
			affected_by_gravity = true
			can_move = true
			anim.play("jump")
			
			if can_coyote:
				gravity_force = gravity_force_during_coyote
				if Input.is_action_just_pressed("jump"):
					cur_state = STATE.jump
				if not coyote_started:
					coyote_started = true
					coyote_timer.start()
			else:
				gravity_force = gravity_force_default
			
			gravity += gravity_force
			
			if grounded:
				can_dash = true
				gravity = 0
				if direction.y == 0:
					if direction.x == 0:
						cur_state = STATE.idle
					else:
						cur_state = STATE.run
				elif direction.y > 0.3:
					cur_state = STATE.crouch
			
			if Input.is_action_just_pressed("dash") and can_dash:
				cur_state = STATE.dash
			
			
		STATE.dash:
			print("dashing")
			can_dash = false
			can_chage_direction = false
			gravity = 0
			affected_by_gravity = false
			can_move = false
			
			if not anim_dash_started:
				print("start dash animation")
				
				anim_dash_started = true
				play_animation_once("dash")
			
			
			if not dash_started:
				dash_started = true
				dash_timer.start()
			
			if anim.flip_h:
				velocity.x = -dash_speed
			else:
				velocity.x = dash_speed
			
			
		STATE.parry:
			pass
		STATE.crouch:
			affected_by_gravity = true
			can_move = false
			
			if not anim_crouch_started:
				anim_crouch_started = true
				play_animation_once("crouch")
			
			
			if Input.is_action_just_released("down"):
				anim_crouch_started = false
				if direction.x == 0:
					cur_state = STATE.idle
				else:
					cur_state = STATE.run
			
			if Input.is_action_just_pressed("dash") and can_dash:
				anim_crouch_started = false
				cur_state = STATE.dash
			
		STATE.crouch_shoot:
			pass
	
	if affected_by_gravity:
		velocity.y = gravity
	else:
		velocity.y = 0
	
	move_and_slide()
	$Label.text = anim.animation + ", " + str(cur_state)

func cancel_jump():
	cur_state = STATE.fall

func play_animation_once(animation):
	anim.play(animation + "_start")
	await anim.animation_finished
	anim.play(animation)
	print("first animation finished")

func _on_max_jump_timer_timeout() -> void:
	cancel_jump()
	jump_started = false

func _on_coyote_timer_timeout() -> void:
	coyote_started = false
	can_coyote = false

func _on_dash_timer_timeout() -> void:
	dash_timer.stop()
	dash_started = false
	can_chage_direction = true
	anim_dash_started = false
	cur_state = STATE.fall
	print("dash finished")

I put some prints to know if the dash is being interrupted by its timer or something else, and it confirmed it was the timer.
Any idea what’s happening? Thanks :slight_smile:

1 Like

I havent fully read your code, but you set your timer to “one_shot” which means it wont reset back to 0 after its done and you can not start it again, since it hasnt reset

1 Like

I already did, but it doesn’t work :frowning:

have you turned off “one_shot”?

Oh, let me try

Still doesn’t work

This issue might be due to a second timer emitting a timeout signal during your dash, for example your max_jump-timer _on_max_jump_timer_timeout(), which calls “cancel_jump()”

try the following:

func _on_max_jump_timer_timeout() -> void:
	if cur_state == STATE.dash:
		return
	cancel_jump()
	jump_started = false
1 Like

I think that worked! I added an extra print saying error or something when it returns, and some of the times it was printed, so that means the jump timer was messing with the dash.
Maybe instead of doing that I could stop the jump timer when it starts dashing.
Thanks a lot! :smiley:

1 Like