Kirby/Smash Bros. Melee style sprinting function not working.

Godot version 4.4.1 stable

So, I’m making a Kirby/Super Smash Bros. Melee style sprint function using Godot, and I have this code here:

I’m trying to get my character to sprint, but it’s not working. I’ve tried everything I could think of, and he’s not going any faster, nor is he playing the sprint animation. The Godot Discord server is not helping out much, so I’m wondering if you guys could help me out.

My main goal is to allow the character to sprint continuously whenever a direction key or D-pad is double tapped, like in the 2D Kirby games, or if the joystick is flicked like in Smash, but I also want to add a little exploit to this function by allowing players to sprint the opposite direction without skidding or sliding.

Keep in mind, I’m more of a visual learner than a reading learner, so it would be better if you show me the code and see what I could improve.

extends Node

#Variables

var previous_flick_value : float
var threshold: float = 0.8
var tap_window: float = 0.2
var time_since_previous_tap: float
var is_flicked : bool
var double_tapped : bool

#Signals

signal flick
signal double_tap

func _physics_process(delta):
	_process_flick(delta)
	_process_double_tap(delta)
	
func _process_flick(_delta: float) -> void:
	var current_value: float = Input.get_action_strength("Right")
	if current_value - previous_flick_value > threshold:
		flick.emit()
	previous_flick_value = current_value
	
func _process_double_tap(_delta: float) -> void:
	var tap: bool = Input.is_action_just_pressed("Right")
	if tap and time_since_previous_tap < tap_window:
		double_tap.emit()
		time_since_previous_tap = 0
	time_since_previous_tap += _delta

func _on_flick():
	if is_flicked == true:
		$"Flick and Double Tap Right".flick.emit()

func _on_double_tap():
	if double_tapped == true:
		$"Flick and Double Tap Right".double_tap.emit()

func _process_sprinting():
	if is_flicked == true or double_tapped == true:
		velocity.x = SPRINTING_SPEED
		$AnimatedSprite2D.play("Sprint Start")

func _physics_process(delta):
	if is_flicked == true or double_tapped == true:
		_process_sprinting()

func _ready():
	$"Flick and Double Tap Right".flick.connect(_on_flick)
	$"Flick and Double Tap Left".flick.connect(_on_flick)
	$"Flick and Double Tap Right".double_tap.connect(_on_double_tap)
	$"Flick and Double Tap Left".double_tap.connect(_on_double_tap)

Doesn’t seem like you ever change is_flicked or double_tapped, your script has other oddities like this node path which emits a different node’s signal even though the signal should be on this node, and the function is connected to the signal so it would cause an infinite loop, I think you don’t want these functions or signals

I would assume you want to set is_flicked or double_tapped to true instead of emitting a signal.

This “styles of learning” was never founded in reality, we have found telling students they are a “certain type of learner” makes them more likely to give up on learning material instead of helping. I promise you can learn anything of any “style”, not that style matters, nor you have any predetermined affectation to one or another, or that styles particularly exist in the first place.

Initially, the on_flick and on_double_tap were just using the is_flicked = true and double_tapped = true calls, and I do want them to be true on both fronts. Also, I have to have the is_flicked and double_tapped variables in all the nodes, otherwise the game will crash. Finally, someone in the Godot Discord suggested that I put the first set of code in a different node to keep things clean and organized.

Someone in the Godot Discord suggested that I create these signals and functions to detect if the Joystick is flicked or if a directional key or D-Pad is double tapped. I was merely following their instructions.

Yeah if you are making a duplicate script for the left stick then you should remove that and use just one script for both directions. The two nodes can use an @export variable for the action so each can have "Right" and "Left" assigned from the editor

@export var my_action: StringName

func _process_flick(_delta: float) -> void:
	var current_value: float = Input.get_action_strength(my_action)
	if current_value - previous_flick_value > threshold:
		flick.emit()
	previous_flick_value = current_value

Maybe using a small RefCounted script would be better than nodes and signals. I assume this needs to end up in a player script with a if is_flicked or double_tapped: run faster kind of line.


Here’s a RefCounted script that will track one action to see if it was pressed in quick succession

extends RefCounted
class_name DoubleTapInput

## assigned action to track
var action_name: StringName
## will set to true or false via [method parse_double_tap]
var is_double_tapped: bool = false

const TAP_MILLISECONDS = 250
var _last_tap_time: int = 0

## for reading any event from _input or _unhandled_input
func parse_double_tap(event: InputEvent) -> void:
	if event.is_echo():
		return # skips 'echo' events such as holding a key down

	if event.is_action(action_name):
		if event.is_pressed():
			var this_time: int = Time.get_ticks_msec()
			if this_time - _last_tap_time < TAP_MILLISECONDS:
				is_double_tapped = true
			_last_tap_time = this_time
		else: # released, so unset the variable
			is_double_tapped = false


func _init(new_action_name: StringName) -> void:
	action_name = new_action_name

Here’s how I used it in the template player script, I still don’t like how there’s two objects one for each direction, and in your case making a third and fourth for flick-inputs. One could reduce the double-tap code to check against both actions.

extends CharacterBody3D

const SPEED = 5.0
const JUMP_VELOCITY = 4.5

# Create double tap inputs for both directions
var left_tap := DoubleTapInput.new("left")
var right_tap := DoubleTapInput.new("right")

func _physics_process(delta: float) -> void:
	if not is_on_floor():
		velocity += get_gravity() * delta

	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	var input_dir := Input.get_vector("left", "right", "forward", "backward")
	var direction := transform.basis * Vector3(input_dir.x, 0, input_dir.y)

	# We actually don't care which direction is double-tapped to increase the speed
	# so long as at lest one is true.
	var either_tap: bool = left_tap.is_double_tapped or right_tap.is_double_tapped
	var speed: float = 8.0 if either_tap else 5.0

	velocity.x = direction.x * speed
	velocity.z = direction.z * speed

	move_and_slide()


# Give the double-tap objects events to read from
func _unhandled_input(event: InputEvent) -> void:
	left_tap.parse_double_tap(event)
	right_tap.parse_double_tap(event)

My game is 2D, this is for a 3D character. I guess I forgot to add that small detail.

I just got a suggestion in the Godot Discord to make a state machine. You have any suggestions for a state machine that suits my needs? Allowing me to add more states as I get more assets?

The 3D versus 2D doesn’t really matter, you could apply the code to either situation. These are the main changes, not 3D or 2D specific

# Create double tap inputs for both directions
var left_tap := DoubleTapInput.new("left")
var right_tap := DoubleTapInput.new("right")

func _physics_process(delta: float) -> void:
	# ...

	# We actually don't care which direction is double-tapped to increase the speed
	# so long as at lest one is true.
	var either_tap: bool = left_tap.is_double_tapped or right_tap.is_double_tapped
	var speed: float = 8.0 if either_tap else 5.0
	# ...

# Give the double-tap objects events to read from
func _unhandled_input(event: InputEvent) -> void:
	left_tap.parse_double_tap(event)
	right_tap.parse_double_tap(event)

I could help you more with that if you shared some of your character script.

I don’t think a state machine is relevant to the issue, and I personally do not use state machines for my player controllers unless I have a specific use case, so I have no recommendations to do so.

Here it is:

extends CharacterBody2D

#Variables
@export var RUNNING_SPEED : int = 180
@export var JUMP_HEIGHT : int = 450
@export var Gravity : int = 1000
@export var SPRINTING_SPEED : int = 350
var current_anim : String

func _ready():
	var animated_sprite = get_node("AnimatedSprite2D")
	animated_sprite.animation_finished.connect(_on_animated_sprite_animation_finished)

func _process(delta: float) -> void:
	if velocity.x == RUNNING_SPEED:
		current_anim = ("Run")
	elif velocity.x == SPRINTING_SPEED:
		current_anim = ("Sprint_Start")

func _physics_process(delta):

	var direction = Input.get_axis("Left", "Right")
	
	#Walking
	if direction:
		velocity.x = direction * RUNNING_SPEED
		if is_on_floor():
			$AnimatedSprite2D.play(current_anim)
	else:
		velocity.x = 0
	#Flip
	if Input.is_action_pressed("Right"):
		$AnimatedSprite2D.flip_h = false
		
	elif  Input.is_action_pressed("Left"):
		$AnimatedSprite2D.flip_h = true
		
	#Jumping and Idle
	if is_on_floor():
		if velocity.x == 0:
			$AnimatedSprite2D.play("Idle")
		
		if Input.is_action_pressed("Jump"):
			$AnimatedSprite2D.play("Jump Ascend")
			velocity.y = -JUMP_HEIGHT
		
	elif velocity.y >= 0 and $AnimatedSprite2D.animation in ["Jump Ascend", "Run", "Idle"]:
		$AnimatedSprite2D.play("Jump Transition")
	#Falling
	if not is_on_floor():
		velocity.y += Gravity * delta
	move_and_slide()
	
func _on_animated_sprite_animation_finished():
	if $AnimatedSprite2D.animation == "Jump Transition":
		$AnimatedSprite2D.play("Jump Fall")
	if $AnimatedSprite2D.animation == "Sprint Start":
		$AnimatedSprite2D.play("Sprint")

Question: How do I go about creating this RefCounted? The script itself is now problem, but making a RefCounted is new to me.

RefCounted is a very basic type, it is largely used to store run-time data that will not be saved. My example DoubleTapInput script can be saved in the filesystem, it won’t be attached to any node. The class_name allows other scripts to create their own instances of it with .new("action name"). This is done at the start of my example player script.

So basically I just have to make a new script file and not make it a part of any nodes?