• Godot Help
  • Is there a simple solution for a double-tap?

In my game I would like to have my character dash to the left or right if I double-tap the A or D key.
I've looked around on the internet and tried most of what I've found with no success.
Is there a simple way to achieve this? Or some resource I could be directed to?

  • A lot of redundant and overcomplicated code has been posted in this thread for what is actually a pretty simple problem. Here's a generalized simple solution that doesn't use any additional nodes and will report double tap on any key:

    const DOUBLETAP_DELAY = .25
    var doubletap_time = DOUBLETAP_DELAY
    var last_keycode = 0
    
    func _process(delta):
    	doubletap_time -= delta
    	
    func _input(event):
    	if event is InputEventKey and event.is_pressed():
    		if last_keycode == event.keycode and doubletap_time >= 0: 
    			print("DOUBLETAP: ", String.chr(event.keycode))
    			last_keycode = 0
    		else:
    			last_keycode = event.keycode
    		doubletap_time = DOUBLETAP_DELAY

    It can also be easily adapted to work with actions instead of keycodes.

The simplest way to achieve this is to use the <code>KeyDown</code> event of the input system. In this method, you check if the A or D key has been pressed twice, and if so, you can then call the dash command.
Here's a simple example of how this would work:

public class DashOnDoubleTap : MonoBehaviour
{
    [SerializeField]
    float dashSpeed;

    float keyDownTime;
    float keyPressDelay = 0.2f; // seconds

    private void OnEnable()
    {
        Input.onKeyDown += HandleKeyDown;
    }

    private void HandleKeyDown(KeyCode keyCode)
    {
        // check if the previous key press was less than keyPressDelay ago
        if (Time.time - keyDownTime < keyPressDelay)  
        {
            // it was! so this is a double tap!
            // Dash in the correct direction
            if(keyCode == KeyCode.A)
            {
                // dash left 
                Dash(Vector3.left * dashSpeed);
            } 
            else if(keyCode == KeyCode.D)
            {
                // dash right 
                Dash(Vector3.right * dashSpeed);
            }
        } 
        else
        {
            // it was a single press, so just save the time 
            // and get ready for the next press
            keyDownTime = Time.time;
        }
    }

    // Dash!
    void Dash(Vector3 direction)

This bot is going to be absolute cancer soon enough, it's spewing out stuff garbled which is going to confuse the poor newbies.

    Let's use a timer for that.

    • How will everything work?

    • Well when you only press A, the character walks left, and D for right.

    • Whenever you press either buttons, a double tap timer can start. Let's have it be 0.5 seconds.

    • If you press the button another time within this 0.5 seconds, you will dash.

    • If you press the other button, the timer restarts.

    • After 0.5, no double tap will be triggered

    • 0.5 can be changed to whatever makes sense in your game.

    • Now that we broke down how it will work, let's get to code.

    • Starting code, just to see where it started:

      extends CharacterBody2D
      
      var move_speed: float = 300.0
      var direction: Vector2 = Vector2.ZERO
      
      func _physics_process(delta: float) -> void:
      	direction.x = Input.get_axis("move_left", "move_right")
      	velocity = direction * move_speed
      	move_and_slide()
    • Character moves left and right in the previous code, nothing fancy. "move_left" and "move_right" are set in the project settings input map to A & D buttons, respectively.

    • Let's have as a child of the Player, a Timer node, whose wait time is 0.5, oneshot is true, autostart is false, and connect its timeout signal to some function like _on_double_tap_timer_timeout.

    • Let's store the last pressed button of either move_left or move_right in a variable, and whenever we press the same button, we print something.

    • This is not double tap, but we're getting somewhere:

      var last_action_string: String = ""
      func _input(event: InputEvent) -> void:
      	if Input.is_action_just_pressed("move_left"):
      		if last_action_string == "move_left":
      			print("move left was already stored")
      		else:
      			last_action_string = "move_left"
      	if Input.is_action_just_pressed("move_right"):
      		if last_action_string == "move_right":
      			print("move right was already stored")
      		else:
      			last_action_string = "move_right"
    • Running that, the lines will only be printed when you press the left button twice or more in a row, and the same for the right button. Switching between right and left won't trigger anything, sweet!

    • Let's start the timer whenever it is not a double tap, the last_action_string will only store the string while the timer is running, because once the timer times out, the function connected to the timeout signal will set the string variable to "":

      @onready var double_tap_timer: Timer = $DoubleTapTimer
      func _input(event: InputEvent) -> void:
      	if Input.is_action_just_pressed("move_left"):
      		if last_action_string == "move_left":
      			print("move left was already stored")
      		else:
      			last_action_string = "move_left"
      			double_tap_timer.start()
      	if Input.is_action_just_pressed("move_right"):
      		if last_action_string == "move_right":
      			print("move right was already stored")
      		else:
      			last_action_string = "move_right"
      			double_tap_timer.start()
      
      func _on_double_tap_timer_timeout() -> void:
      	last_action_string = ""
    • Double tap works, lines only printed when you double tap.

    • Let's now change the speed of the player whenever we double tap, full code, after refactoring would be like this:

      extends CharacterBody2D
      
      @export var dash_speed: float = 600.0
      @export var default_move_speed: float = 300.0
      
      var move_speed: float = default_move_speed
      var direction: Vector2 = Vector2.ZERO
      
      var last_action_string: String = ""
      
      @onready var double_tap_timer: Timer = $DoubleTapTimer
      
      func _physics_process(delta: float) -> void:
      	direction.x = Input.get_axis("move_left", "move_right")
      	velocity = direction * move_speed
      	move_and_slide()
      
      func _input(event: InputEvent) -> void:
      	_check_for_double_tap("move_left")
      	_check_for_double_tap("move_right")
      
      # Literally same code for both "move_left" and "move_right", why not make them into one function
      func _check_for_double_tap(action_string: String) -> void:
      	if Input.is_action_just_pressed(action_string):
      		if last_action_string == action_string: # If double-tapped
      			move_speed = dash_speed
      		else: # If this is not a double-tap
      			move_speed = default_move_speed
      			last_action_string = action_string
      			double_tap_timer.start()
      
      # Connected via the editor
      func _on_double_tap_timer_timeout() -> void:
      	last_action_string = ""
    • Some issues include, but not limited to:

      • Let's say you press A F A fast in sequence, double tap still triggers, and that's because the F button isn't checked for, neither stored in the last_action_string, nor the timer starts too. This is a feature 😭 ... Seriously you can dash even if you pressed the attack button mid dash double tap.
      • This only changes the speed. Dashing for me is a temporary event, where after a second, the player returns to the default speed, this can be done with timers, but that's out of scope of the original question of double taps.
    • I hope you found that helpful.
      Edit: typos

      The AI code is actually correct, but it's for Unity or MonoGame or something. The OP did not mention Godot in the topic, so she didn't know what engine it was for.

        cybereality The code doesn't detect if it was the same key twice in a row, only if the second key was A or D. So pressing W then A would count as double tapping A.

        • xyz replied to this.

          Kojack It's also incomplete and has some syntax errors.

          A lot of redundant and overcomplicated code has been posted in this thread for what is actually a pretty simple problem. Here's a generalized simple solution that doesn't use any additional nodes and will report double tap on any key:

          const DOUBLETAP_DELAY = .25
          var doubletap_time = DOUBLETAP_DELAY
          var last_keycode = 0
          
          func _process(delta):
          	doubletap_time -= delta
          	
          func _input(event):
          	if event is InputEventKey and event.is_pressed():
          		if last_keycode == event.keycode and doubletap_time >= 0: 
          			print("DOUBLETAP: ", String.chr(event.keycode))
          			last_keycode = 0
          		else:
          			last_keycode = event.keycode
          		doubletap_time = DOUBLETAP_DELAY

          It can also be easily adapted to work with actions instead of keycodes.

            xyz
            Awesome, simple, easy! Noted for the future.
            Gotta dive into the rabbit hole of InputEvent classes someday. And measuring time using delta instead of a timer node make a whole lot more sense than my previous mess.
            10/10

            The thing about AI is it gets it's 'answers' by stealing other code from the internet which also means that it's going to steal all of the mistakes and nonsense answers as well.

            • xyz replied to this.

              BroTa Amazing bro! Thank you! Some of this went over my head but I'll study it so that I can internalize the info.

                Lethn The thing about AI is it gets it's 'answers' by stealing other code from the internet which also means that it's going to steal all of the mistakes and nonsense answers as well.

                From the code integrity point of view, it's worse than just stealing. It concocts snippets from statistical analysis of "stolen" code. The more specific a problem you have, the more jumbled and weird the bot's "solution" will be. That's just the nature of LLMs. And to make things worse for learners, it all looks "legit" and "confident" at first glance, regardless of how broken the code actually is. Observing this is quite amusing 🙂

                  xyz Yes it is funny and ridiculous but at the same time it's not when you try and explain this to people and they get mad they can't fulfil their AI skynet apocalypse scenario, machine learning AI for the purposes that it's now popular to use it for is utter trash. Mind you, I shouldn't complain, it's going to make life so easy for me as competition because all the lazy people out there are going to switch to it thinking it can replace human thinkers when it can't.

                    because posts in this forum are always about Godot by default, @GodetteAI should know this.
                    she should also get context about which programming language the post is about:

                    • if it's not specified anywhere, it's about GDScript
                    • if it's specified in a tag, it's about the programming language in the tag
                    • if it's specified in the post's body text, it's about the programing language in the body text

                    Lethn I've had no luck with usable code with these current AIs, Chat GPT and Bard.
                    It seems like they are more useful for explaining things. That's mostly what I've been using them for.

                    Jakerb
                    Glad you found it helpful.
                    Although I went step by step with my answer for understanding, xyz replied with a much cleaner and simpler code.
                    If you are going to internalize some code, use xyz's one.
                    His solution is better because:

                    • if you press any button other than A or D, for example, A F A, double A will not trigger. You can say it is not dependent on you setting keys in Input Map, and checking them manually in code.
                    • No need for a timer node, time is calculated using delta of _process function, instead of using a node, which is simpler, and needs no setting up.

                      BroTa I wasn't sure how to use xyz's code but I figured it out while in the middle of responding to you. That's great it's such a compact script. Thank you for your help!

                      5 months later

                      xyz just one thing though, if you just keep holding one button it also counts as double tap.

                      • xyz replied to this.