Search code examples
game-developmentgodotgdscript

godot 3.5.1 going from walking to running by double tapping the movement keys doesn't seem to work


this is the 3D character controller for third person player game in godot, I'm trying to make it so that the player can run either by pressing SHIFT or by double tapping the movement keys

below the script I'm using with comments, SHIFT seems to work but but double tapping doesn't, no matter how much I increase the timing viability

I tried this script and increasing the timer for double tap, modified the engine's maximum FPS thinking it had something to do with that, but it didn't fix the problem.

extends KinematicBody

export var walk_speed = 5.0  # Speed of the character's movement when walking
export var run_speed = 10.0  # Speed of the character's movement when running
export var sprint_speed = 20.0 # Speed of the character's movement when sprinting
export var gravity = 9.8  # Gravity affecting the character's vertical movement
export var jump_power = 5.0  # Power of the character's jump
export var run_key = "run"  # Key to use for running
export var weight = 1

var velocity = Vector3.ZERO  # Velocity vector of the character's movement
var is_running = false  # Whether the character is currently running
var is_double_tapping = false # Whether the player is double-tapping to sprint
var double_tap_time = 0.5 # Time frame for double-tapping (in seconds)
var double_tap_timer = 1 # Timer for tracking double-tap time
var jumping = false # Whether the player is currently jumping

func _physics_process(delta):
    # Get the input from the player
    var move_dir = Vector3.ZERO
    var cam_transform = get_node("Camroot/Camera").global_transform
    var forward = -cam_transform.basis.z
    var right = cam_transform.basis.x
    move_dir = Input.get_action_strength("move_forward") * forward - Input.get_action_strength("move_back") * forward + Input.get_action_strength("move_right") * right - Input.get_action_strength("move_left") * right
    move_dir = move_dir.normalized()

    # Update the velocity based on the input and speed, taking into account the character's orientation and whether they are running
    var target_speed = walk_speed
    if is_running:
        target_speed = run_speed
    if is_double_tapping:
        target_speed = sprint_speed
    velocity.x = move_dir.x * target_speed
    velocity.z = move_dir.z * target_speed

    # Apply gravity only if the character is not on the ground or on a slope
    if not is_on_floor() or abs(floor(get_floor_normal().dot(Vector3.UP)) - 1) > 0.001:
        velocity.y -= gravity *weight* delta

    # Jump only if the character is on the ground
    if is_on_floor():
        if Input.is_action_just_pressed("jump"):
            jumping = true

    # Update the velocity based on whether the character is jumping
    if jumping:
        velocity.y = jump_power
        jumping = false

    # Check if the run key has been pressed or released, and update the running state accordingly
    if Input.is_action_just_pressed(run_key):
        is_running = true
        if not is_double_tapping:
            is_double_tapping = true
            double_tap_timer = double_tap_time
        else:
            is_double_tapping = false
            target_speed = sprint_speed
    elif Input.is_action_just_released(run_key):
        is_running = false

    # Check if the player is double-tapping to sprint
    if is_double_tapping:
        double_tap_timer -= delta
        if double_tap_timer < 0.0:
            is_double_tapping = false

    # Move the character
    velocity = move_and_slide(velocity, Vector3.UP)


Solution

  • I suppose this is the mechanism you want to trigger:

        var target_speed = walk_speed
        if is_running:
            target_speed = run_speed
        if is_double_tapping:
            target_speed = sprint_speed
    

    So let us see how is_double_tapping becomes true. That would be here:

        if Input.is_action_just_pressed(run_key):
            is_running = true
            if not is_double_tapping:
                is_double_tapping = true
                double_tap_timer = double_tap_time
            else:
                is_double_tapping = false
                target_speed = sprint_speed
        elif Input.is_action_just_released(run_key):
            is_running = false
    

    So, to is_double_tapping to become true, the player first needs to press the run_key action.

    That is not what you said you want. You said either:

    I'm trying to make it so that the player can run either by pressing SHIFT or by double tapping the movement keys

    But you have one mechanism gating the other. In fact, you have defined three speeds.


    Also, you seem to interpret is_double_tapping in two different ways. Sometimes it means that that the time to double tap is active, sometimes it seems to mean the character should be going faster because the player double tapped. And these can't work together, because when the time to double tap ends (and is_double_tapping becomes false) you want the character to go fast if the player did double tap (with the change of speed happening when is_double_tapping is true).

    And please notice that you are not checking if the player did double tap at all. Where is the code that checks if the player pressed the directions within the given time? It isn't there.


    So, let us rethink this.

    You need a timer solution. I'll keep the one you have. On the first input you start it. So you can check if the next input is within the appropriate time.

    Thus, you should set double_tap_timer when the player pressed the direction…

    That is, you should set double_tap_timer when move_dir became different from zero. Which is something you are not checking.

    I'll pseudo-code it first:

    • If move_dir is not zero but was zero before:
      • If is_double_tapping and the direction is roughly the same as the old one:
        • Increase speed.
      • If not is_double_tapping:
        • is_double_tapping = true.
        • Start double tap time.

    Right, I need to have some old_move_dir:

    • If move_dir is not zero and old_move_dir is zero:
      • If is_double_tapping and move_dir.dot(old_move_dir) > 0.0:
        • Increase speed.
      • If not is_double_tapping:
        • Start double tap time.
        • is_double_tapping = true
    • old_move_dir = move_dir

    Having three speeds will throw a wrench on it. So you have two reasons to increase speed: The player double tapped, or the player used the run_key action. My intuition would be to keep track of these as bool variables, and depending on them decide target_speed.

    But when do you set them to false? For the run_key action that is easy: it ends when the player released the run_key action. But there is no clear end for the double tap. Surely you want to set it to false when the player stopped. That is when move_dir is zero.

    Ok, let us add that:

    • is_fast_because_run_key = Input.is_action_pressed(run_key)
    • If move_dir is zero:
      • is_fast_because_double_tap = false
    • Else:
      • If old_move_dir is zero.
        • If is_double_tapping and move_dir.dot(old_move_dir) > 0.0:
          • is_fast_because_double_tap = true
        • If not is_double_tapping.
          • Start double tap time.
          • is_double_tapping = true.

    I'll write that as code now:

    is_fast_because_run_key = Input.is_action_pressed(run_key)
    if move_dir.is_equal_approx(Vector3.ZERO):
        is_fast_because_double_tap = false
    else:
        if old_move_dir.is_equal_approx(Vector3.ZERO):
            if is_double_tapping and move_dir.dot(old_move_dir) > 0.0:
                is_fast_because_double_tap = true
            if not is_double_tapping:
                is_double_tapping = true
                double_tap_timer = double_tap_time
    
    old_move_dir = move_dir
    

    Then you can pick target_speed like this:

    if is_fast_because_run_key and is_fast_because_double_tap:
        target_speed = sprint_speed
    elif is_fast_because_run_key or is_fast_because_double_tap:
        target_speed = run_speed
    else:
        target_speed = walk_speed
    

    You can get rid of the part where you used to check run_key.

    And, of course, you still need this code:

        # Check if the player is double-tapping to sprint
        if is_double_tapping:
            double_tap_timer -= delta
            if double_tap_timer < 0.0:
                is_double_tapping = false