How to implement player State Machine

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

Hello community. I am very new to godot, and making 2d action platformer. I am trying to implement player state machine by following Finite State Machine in Godot by Nathan Lovato from GDQuest. However I am not sure how exactly I should write my code. I would really appreciate if someone could teach me how implement player state machine.

Current Player script. Player can move right and left, jump, and double jump.

extends KinematicBody2D

const UP_DIRECTION := Vector2.UP

export var can_move = true

export var speed := 200.0
export var jump_strength := 450
export var maximum_jumps := 2
export var double_jump_strength := 400
export var gravity := 1200

var _jumps_made := 0
var _velocity := Vector2.ZERO


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



func _physics_process(delta: float) -> void:
	
	#Left and Right Movement Direction
	var _horizontal_direction = (
		Input.get_action_strength("move_right")
		- Input.get_action_strength("move_left")
	)
	
	#X and Y velocity
	_velocity.x = _horizontal_direction * speed
	_velocity.y += gravity * delta
	
	#Player State
	var is_falling := _velocity.y > 0.0 and not is_on_floor()
	var is_jumping := Input.is_action_just_pressed("jump") and is_on_floor()
	var is_double_jumping := Input.is_action_just_pressed("jump") and is_falling
	var is_jump_cancelled := Input.is_action_just_released("jump") and _velocity.y < 0.0
	var is_idling := is_on_floor() and is_zero_approx(_velocity.x)
	var is_running := is_on_floor() and not is_zero_approx(_velocity.x)
	
	
	#Jump Counter
	if is_jumping:
		_jumps_made += 1
		_velocity.y = -jump_strength
	elif is_double_jumping:
		_jumps_made += 1
		if _jumps_made <= maximum_jumps:
			_velocity.y = -double_jump_strength
	elif is_jump_cancelled:
		_velocity.y = 0.0
	elif is_idling or is_running:
		_jumps_made = 0
	
	if (can_move == true):
		#Velocity Calculation
		_velocity = move_and_slide(_velocity, UP_DIRECTION)		
	
	#Flip Sprite 
	if get_global_mouse_position().x > $Position2D/PlayerSkinIK.global_position.x:
		
		position2D.scale.x=1
	else:
		
		position2D.scale.x=-1
		
	#Flip Sprite for Walking Animation
	if position2D.scale.x==1:
		
		if Input.is_action_pressed("move_right"):
			print("forward1")
			if is_on_floor():
				_animation_player.play("Player-Run IK")
		elif Input.is_action_pressed("move_left"):
			print("backward1")
			if is_on_floor():
				_animation_player.play("Player-Run IK Backward")
			
	elif position2D.scale.x==-1:
		
		if Input.is_action_pressed("move_left"):	
			print("forward2")			
			if is_on_floor():
				_animation_player.play("Player-Run IK")
		elif Input.is_action_pressed("move_right"):
			print("backward2")
			if is_on_floor():
				_animation_player.play("Player-Run IK")
		
	#Animation Control
	if is_jumping or is_double_jumping:
		_animation_player.play("Player-Jump IK")
	elif is_falling:
		_animation_player.play("Player-Fall IK")
	elif is_idling:
		_animation_player.play("Player-Idle IK")

And these are scripts for the state machine:

State:

extends Node

class_name State

var state_machine = null


func handle_input(_event: InputEvent) -> void:
	pass


func update(_delta: float) -> void:
	pass


func physics_update(_delta: float) -> void:
	pass


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


func exit() -> void:
	pass

State Machine:

extends Node

class_name StateMachine

signal transitioned(state_name)

export var initial_state := NodePath()

onready var state: State = get_node(initial_state)

func _ready() -> void:
	yield(owner, "ready")
	
	for child in get_children():
		child.state_machine = self
	state.enter()


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)


func transition_to(target_state_name: String, msg: Dictionary = {}) -> void:
	if not has_node(target_state_name):
		return
	
	state.exit()
	state = get_node(target_state_name)
	state.enter(msg)
	emit_signal("transitioned", state.name)

PlayerState:

extends "res://Scripts/State Machine Attempt 2/States.gd"

class_name PlayerState


var player: Player


func _ready() -> void:
	yield(owner, "ready")
	
	player = owner as Player
	
	assert(player != null)

Player:

extends KinematicBody2D

class_name Player

enum States {ON_GROUND, IN_AIR}

var _state : int = States.ON_GROUND


const UP_DIRECTION := Vector2.UP

export var speed := 200.0
export var jump_strength := 450
export var maximum_jumps := 2
export var double_jump_strength := 400
export var gravity := 1200

var _jumps_made := 0
var _velocity := Vector2.ZERO



func _physics_process(delta: float) -> void:
	var is_jumping: bool = _state == States.ON_GROUND and Input.is_action_just_pressed("jump")
	var is_falling : bool = _state == States.IN_AIR and _velocity.y > 0.0
	var is_double_jumping : bool = _state == States.IN_AIR and Input.is_action_just_pressed("jump") and is_falling
	var is_jump_cancelled : bool = _state == States.ON_GROUND and States.IN_AIR and Input.is_action_just_released("jump") and _velocity.y < 0.0
	
	if is_jumping:
		_jumps_made += 1
		_velocity.y = -jump_strength
	elif is_double_jumping:
		_jumps_made += 1
		if _jumps_made <= maximum_jumps:
			_velocity.y = -double_jump_strength
	elif is_jump_cancelled:
		_velocity.y = 0.0
	elif is_idling or is_running:
		_jumps_made = 0
	

	# Moving the character.
	_velocity = move_and_slide(_velocity, UP_DIRECTION)	


	if is_on_floor():
		_state = States.ON_GROUND
		



func change_state(new_state: int) -> void:
	var previous_state := _state
	_state = new_state

	# Initialize the new state.
	match _state:
		States.IN_AIR:
			_speed = air_speed
		States.ON_GROUND:
			_speed = ground_speed

Air:

extends "res://Scripts/State Machine Attempt 2/PlayerState.gd"


func enter(msg := {}) -> void:
	if msg.has("do_jump"):
		player.velocity.y = -player.jump_impulse

func physics_update(delta: float) -> void:

	var input_direction_x: float = (
		Input.get_action_strength("move_right")
		- Input.get_action_strength("move_left")
	)
	player.velocity.x = player.speed * input_direction_x

	player.velocity.y += player.gravity * delta
	player.velocity = player.move_and_slide(player.velocity, Vector2.UP)


	if player.is_on_floor():
		if is_equal_approx(player.velocity.x, 0.0):
			state_machine.transition_to("Idle")
		else:
			state_machine.transition_to("Run")

Idle:

extends "res://Scripts/State Machine Attempt 2/PlayerState.gd"

func enter(_msg := {}) -> void:
	owner.velocity = Vector2.ZERO

func update(delta: float) -> void:
	if not player.is_on_floor():
		state_machine.transition_to("Air")
		return
	
	if Input.is_action_just_pressed("jump"):
		state_machine.transition_to("Air",{do_jump = true})
	elif Input.is_action_pressed("move_left") or Input.is_action_pressed("move_right"):
		state_machine.transiton_to("Run")

Move:

extends PlayerState

class_name RunState


func _physics_update(delta: float) -> void:
	
	if not player.is_on_floor():
		state_machine.transition_to("Air")
		return
	
	var _horizontal_direction = (
		Input.get_action_strength("move_right") 
		- Input.get_action_strength("move_left")
	)
	
	_velocity.x = _horizontal_direction * speed
	_velocity.y += gravity * delta
	
	_velocity = player.move_and_slide(_velocity, UP_DIRECTION)	
	
	
	if Input.is_action_just_pressed("jump"):
		state_machine.transition_to("Air", {do_jump = true})
	elif is_equal_approx(input_direction_x, 0.0):
		state_machine.transition_to("Idle")

I have that same problem.

if you want take a look at this

Godot Behaviour Tree

ramazan | 2022-07-11 09:12