how to implement Jump Buffering and Control jump height

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By YZNhamzeh

I’m following Game Endeavor 2D platformer series, in his “Improve Your Game Feel With Coyote Time and Jump Buffering” tutorial I’m trying to add Jump Buffering and Let the player control the height of the jump, The problem is when player jump in mid-air it jumps to max height even when he barely press the jump key, but when it grounded he can control jump height normally

This is the whole project if you want to see it: https://github.com/yznhamzeh/my_first_platformer/tree/master/platformer_0.1

This is the player code :

extends KinematicBody2D

signal grounded_updated(is_grounded)

const UP = Vector2(0, -1)

var velocity = Vector2()
var move_speed = 100
var gravity 
var max_jump_velocity 
var min_jump_velocity
var is_grounded
var is_jumping

onready var raycasts = $RayCasts
onready var anim_player = $AnimationPlayer
onready var coyote_time = $CoyoteTimer
onready var jump_buffer = $JumpBuffer

var max_jump_height = 5.25 * 16
var min_jump_height = 2 * 16
var jump_duration = 0.5


func _ready():
	gravity = 2 * max_jump_height / pow(jump_duration, 2)
	max_jump_velocity = -sqrt(2 * gravity * max_jump_height)
	min_jump_velocity = -sqrt(2 * gravity * min_jump_height)

func _physics_process(delta):
	
	_apply_gravity(delta)
	
	var was_on_floor = is_grounded
	
	_get_input()
	
	velocity = move_and_slide(velocity, UP)
	
	var was_grounded = is_grounded
	
	is_grounded = _check_is_grounded()
	
	if was_grounded == null || is_grounded != was_grounded:
		emit_signal("grounded_updated", is_grounded)
	
	_assign_animation()
	
	if !is_grounded && was_on_floor && !is_jumping:
		coyote_time.start()
		velocity.y = 0
	if is_grounded && !jump_buffer.is_stopped():
		jump_buffer.stop()
		jump()
		
		
	
	

func _apply_gravity(delta):
	if coyote_time.is_stopped():
		velocity.y += gravity * delta
		if is_jumping && velocity.y >= 0:
			is_jumping = false
	


func _input(event):
	if event.is_action_pressed("jump") :
		if is_grounded || !coyote_time.is_stopped():
			coyote_time.stop()
			jump()
		else:
			jump_buffer.start()
	
	if event.is_action_released("jump") && velocity.y < min_jump_velocity:
		velocity.y = min_jump_velocity
		


func _get_input():
	var move_direction = -int(Input.is_action_pressed("move_left")) + int(Input.is_action_pressed("move_right"))
	velocity.x = lerp(velocity.x, move_speed * move_direction, 0.15)
	
	if move_direction != 0:
		$Body.scale.x = move_direction


func _get_h_weight():
	if is_grounded || !coyote_time.is_stopped():
		return 0.2
	else:
		return 1


func _check_is_grounded():
	for raycast in raycasts.get_children():
		if raycast.is_colliding():
			
			return true
		
	return false

func _assign_animation():
	var anim = "idle"
	
	if !is_grounded && coyote_time.is_stopped():
		anim = "jump"
	elif velocity.x > 20 or velocity.x < -20:
		anim = "run"

	if anim_player.assigned_animation != anim:
		anim_player.play(anim)

func jump():
	velocity.y = max_jump_velocity
	is_jumping = true
:bust_in_silhouette: Reply From: Simon S

The problem occurs when the jump key is released before starting the jump, therefore the jump cannot be cancelled and will go to the max height.

You can fix this by tracking how many frames there are between the jump key being pressed and the jump key being released. When the jump starts, increment a counter each frame until the counter equals the amount of frames the jump key was held for, then cancel the jump velocity.

Here is an example (Doesn’t involve coyote time however):

extends KinematicBody2D

```
var frame_time: int = 0

export var floor_direction = Vector2(0, -1)

export var max_speed: int = 1500
export var acceleration: int = 50

export var jump_height: int = -2150
export var gravity: int = 80

var jump_buffered = false

var jump_start_frame : int
var jump_length : int
var jump_release_counter: int = 0
var is_jumping : bool = false

var motion = Vector2()

func _physics_process(delta):
	frame_time += 1

	motion.y += gravity
	var friction = false
	
	# Left and Right movement
	if Input.is_action_pressed("ui_right"):
		motion.x += max_speed
		$MainSprite.flip_h = false
	elif Input.is_action_pressed("ui_left"):
		motion.x -= max_speed
		$MainSprite.flip_h =  true
	else:
		friction = true
	
	#Jumping and buffers
	
	if Input.is_action_just_pressed("ui_up"):
		jump_start_frame = frame_time
		startBuffer()
		
	if Input.is_action_just_released("ui_up"):
		jump_length = frame_time - jump_start_frame
		
	if is_jumping:
		jump_length -= 1
		if jump_length == 0:
			is_jumping = false
			jump_length = 0
			if motion.y < 0: #Check needs to be made so jump cannot be cancelled when falling
				motion.y *= 0.5 #Set the Jump Short
				
	
	if is_on_floor():		
		if friction == true:
			motion.x = lerp(motion.x, 0, 0.2)
		if jump_buffered:
			is_jumping = true
			motion.y = jump_height
			
	else:
		if friction == true:
			motion.x = lerp(motion.x, 0, 0.1)
			
	#############################################
			
	motion.x = clamp(motion.x , -max_speed, max_speed)
	
	motion = move_and_slide(motion, floor_direction)
	
func startBuffer():
	jump_buffered = true
	yield(get_tree().create_timer(0.1), "timeout")
	jump_buffered = false
```