My Jump state sometime does not work. Why? Plus how to implement double jump??

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

Hello Godot community. I have made state machine by following Youtube tutorial, however spacebar input (jump state) is not responsive and sometime does not work. I would appreciate if you could teach me how to solve this problem. Plus, I would like to know how to implement double jump state. Thanks.

Player Script:

  class_name Player

extends KinematicBody2D

var speed = 200
var jump_strength = 500
var double_jump_strength := 450
var air_jump := true
var _jumps_made := 0
var gravity = 1200
var acceleration = 60
var friction = 20
var air_friction = 10

var _velocity := Vector2.ZERO

onready var position2D = $Position2D
onready var _animation_player: AnimationPlayer = $Position2D/PlayerSkinIK/AnimationPlayer


func get_input_direction() -> float:
	var _horizontal_input = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
	
	if get_global_mouse_position().x > $Position2D/PlayerSkinIK.global_position.x:
		position2D.scale.x=1
	else:
		position2D.scale.x=-1
	
	if is_on_floor() and position2D.scale.x==1 and Input.is_action_pressed("move_right"):
		_animation_player.play("Player-Run IK")
	if is_on_floor() and position2D.scale.x==-1 and Input.is_action_pressed("move_right"):
		_animation_player.play("Player-Run IK Backward")
	if is_on_floor() and position2D.scale.x==-1 and Input.is_action_pressed("move_left"):
		_animation_player.play("Player-Run IK")
	if is_on_floor() and position2D.scale.x==1 and Input.is_action_pressed("move_left"):
		_animation_player.play("Player-Run IK Backward")
	return _horizontal_input

func player_run_animation() -> void:
	pass

Idle State:

 extends PlayerState

export (NodePath) var _animation_player
onready var animation_player:AnimationPlayer = get_node(_animation_player)


func enter(_msg := {}) -> void:
	animation_player.play("Player-Idle IK")

func physics_update(delta: float) -> void:
	if not player.is_on_floor(): #If Player is Not On Floor, the State Transition to Fall
		state_machine.transition_to("Air")
		return
	
	player._velocity.x = lerp(player._velocity.x, 0, player.friction * delta)
	player._velocity = player.move_and_slide(player._velocity, Vector2.UP)

	if Input.is_action_just_pressed("jump"): #If Player Jump, the State Transition to Jump
		state_machine.transition_to("Air", {do_jump = true})
	elif not is_zero_approx(player.get_input_direction()): #If Player is Moving, the State transition to Run
		state_machine.transition_to("Run")

Run State:

 extends PlayerState


export (NodePath) var _animation_player
onready var animation_player: AnimationPlayer = get_node(_animation_player)


func enter(_msg := {}) -> void:
	pass

func physics_update(delta: float) -> void:
	if not player.is_on_floor(): #If Player is Not On Floor, the State Transition to Fall
		state_machine.transition_to("Air")
		return
	
	if not is_zero_approx(player.get_input_direction()):
		player._velocity.x = lerp(player._velocity.x, player.get_input_direction() * player.speed, player.acceleration * delta)
	
	player._velocity.y += player.gravity * delta
	player._velocity = player.move_and_slide(player._velocity, Vector2.UP)
	
	if Input.is_action_just_pressed("jump"): #If Player Jump, the State Transition to Jump
		state_machine.transition_to("Air", {do_jump = true})
	elif is_zero_approx(player.get_input_direction()): #If Player is Not Moving, the State Transition to Idle
		state_machine.transition_to("Idle")

Air State:

 extends PlayerState


export (NodePath) var _animation_player
onready var animation_player: AnimationPlayer = get_node(_animation_player)


func enter(msg := {}) -> void:
	if msg.has("do_jump"):
		player._velocity.y = -player.jump_strength
		animation_player.play("Player-Jump IK")

func physics_update(delta: float) -> void:
	if not is_zero_approx(player.get_input_direction()):
		player._velocity.x = lerp(player._velocity.x, player.get_input_direction() * player.speed, player.acceleration * delta)
	else:
		player._velocity.x = lerp(player._velocity.x, 0, player.air_friction * delta)
	
	player._velocity.y += player.gravity * delta
	player._velocity = player.move_and_slide(player._velocity, Vector2.UP)
	
	if player.is_on_floor():
		if is_zero_approx(player.get_input_direction()):
			state_machine.transition_to("Idle")
		else:
			state_machine.transition_to("Run") 

Player State:

# Boilerplate class to get full autocompletion and type checks for the `player` when coding the player's states.
# Without this, we have to run the game to see typos and other errors the compiler could otherwise catch while scripting.
class_name PlayerState
extends State

# Typed reference to the player node.
var player: Player


func _ready() -> void:
	# The states are children of the `Player` node so their `_ready()` callback will execute first.
	# That's why we wait for the `owner` to be ready first.
	yield(owner, "ready")
	# The `as` keyword casts the `owner` variable to the `Player` type.
	# If the `owner` is not a `Player`, we'll get `null`.
	player = owner as Player
	# This check will tell us if we inadvertently assign a derived state script
	# in a scene other than `Player.tscn`, which would be unintended. This can
	# help prevent some bugs that are difficult to understand.
	assert(player != null)

State:

# Virtual base class for all states.
class_name State
extends Node

# Reference to the state machine, to call its `transition_to()` method directly.
# That's one unorthodox detail of our state implementation, as it adds a dependency between the
# state and the state machine objects, but we found it to be most efficient for our needs.
# The state machine node will set it.
var state_machine = null


# Virtual function. Receives events from the `_unhandled_input()` callback.
func handle_input(_event: InputEvent) -> void:
	pass


# Virtual function. Corresponds to the `_process()` callback.
func update(_delta: float) -> void:
	pass


# Virtual function. Corresponds to the `_physics_process()` callback.
func physics_update(_delta: float) -> void:
	pass


# Virtual function. Called by the state machine upon changing the active state. The `msg` parameter
# is a dictionary with arbitrary data the state can use to initialize itself.
func enter(_msg := {}) -> void:
	pass


# Virtual function. Called by the state machine before changing the active state. Use this function
# to clean up the state.
func exit() -> void:
	pass

State Machine:

# Generic state machine. Initializes states and delegates engine callbacks
# (_physics_process, _unhandled_input) to the active state.
class_name StateMachine
extends Node

# Emitted when transitioning to a new state.
signal transitioned(state_name)

# Path to the initial active state. We export it to be able to pick the initial state in the inspector.
export var initial_state := NodePath()

# The current active state. At the start of the game, we get the `initial_state`.
onready var state: State = get_node(initial_state)


func _ready() -> void:
	yield(owner, "ready")
	# The state machine assigns itself to the State objects' state_machine property.
	for child in get_children():
		child.state_machine = self
	state.enter()


# The state machine subscribes to node callbacks and delegates them to the state objects.
func _unhandled_input(event: InputEvent) -> void:
	state.handle_input(event)


func _process(delta: float) -> void:
	state.update(delta)


func _physics_process(delta: float) -> void:
	state.physics_update(delta)


# This function calls the current state's exit() function, then changes the active state,
# and calls its enter function.
# It optionally takes a `msg` dictionary to pass to the next state's enter() function.
func transition_to(target_state_name: String, msg: Dictionary = {}) -> void:
	# Safety check, you could use an assert() here to report an error if the state name is incorrect.
	# We don't use an assert here to help with code reuse. If you reuse a state in different state machines
	# but you don't want them all, they won't be able to transition to states that aren't in the scene tree.
	if not has_node(target_state_name):
		return

	state.exit()
	state = get_node(target_state_name)
	state.enter(msg)
	emit_signal("transitioned", state.name)
:bust_in_silhouette: Reply From: DaddyMonster

You’ve got a lot of branching here and staying on top of it is going to give you a headache. I think you’re discovering that it’s not very manageable, maintainable or scalable as your project grows.

At the risk of sounding like a presenter on the Shopping Channel, I’d like to introduce the AnimationTree node. [cue “oohs” and “aahs” from audience]. It’s arguably the best feature of Godot and is best in class imho.

Here’s a good tutorial: https://www.youtube.com/watch?v=KAZX4qfD06E

You might need to watch a few tutorials and read the docs as it’s packed full of features. Docs:

Sorry to leave an answer that is effectively “start again”, but with AnimationTree you do this with a fraction of the code and you’ll be in a much stronger position going forward.

Thank you DaddyMonster. Really appreciate your suggestion. I will look at tutorial and document, and will try to implement animation tree to my project. I was hoping to get a solution for jump problem but thanks anyway.

amaou310 | 2022-07-25 14:17

Ok, I’ll take a look this evening when I’m home. Could you include the PlayerState parent class please?

Out of curiousity, what was the tutorial?

DaddyMonster | 2022-07-25 17:20

Just for future questions you ask, you presented a ton of code, that we cant test, and we also dont know how your state machine works (remember that state machine is an aproach to solve a problem, you can build them as you want). So solving your problem is quite a difficult/impossible task from here.

Pomelo | 2022-07-25 17:37

This video was the tutorial that I followed.

amaou310 | 2022-07-26 02:40

Ah, I see. Slick Games took it from GDQuest’s Nathan Lovato. It’s a perfectly serviceable OOP state machine but my goodness there’s a lot of pointless repetition… This could have been done far more elegantly with a fraction of the code. Maybe it was for teaching purposes but needlessly repeating needlessly repeating needlessly repeating. Ugh.

Anyway, I just quickly created the project by copying and pasting your code and it worked fine for me*. I can’t replicate any problems with jumping.

I didn’t get what the point of the PlayerPosition2D was or what PlayerSkinIK was so I skipped them and had AnimationPlayer as a child of player.

The only thing I can think of is if is_on_floor() is flicking state because of your particular terrain / your particular character. Try printing the state every time you jump. Replace the ground with a CollisionShape2D RectangleShape to see if that’s the issue. Just find what state it’s in when you’re getting the issue.

For a double jump, in AirState.gd just test the velocity.y. If it’s low enough and is_on_floor()is true then call the jump method. That’s if you want to only be able to jump at the top of the arc.

*with minor change of export var initial_state := NodePath() to export (NodePath) var initial_state

DaddyMonster | 2022-07-26 12:01

Thank you very much DaddyMonster. I really appreciate your kindness. Well… I tried everything to test my jump state, however nothing did solved the problem. Besides, I realised my state machine is not very manageable, especially when tried to implement fall and jump state instead of air state. Maybe I should look for other tutorials. Do you have any recommendation to follow?

amaou310 | 2022-07-26 14:17

You’re most welcome.

An OOP state machine is potentially very powerful. Like I said, I’m not the biggest fan of Nathan’s code, I’d be too embarrassed to publish that. In any event, even if you tidied up the code, in a little beginner’s project, it’s overkill. It’s more appropriate for a big game / an experienced Godot dev.

First, check out AnimationTree like I said, it has a StateMachine built in which will lerp between animation states according to a vector and it’s all built in for you. It’s a really great tool that you’ll soon become addicted to.

How about you just go for a simple enum state machine? For a small, straightforward project that’s the way to go. Here’s HeartBeast doing a great job of explaining it:

https://www.youtube.com/watch?v=4bdiyOGHLtM

DaddyMonster | 2022-07-26 14:56