Godot Version
4.2.2
Question
Hello everyone a few days back i had a problem with my movement which had a quick solution but after that i enter tweak phase and didn’t feel right so i keep searching for solution. Even though is not what i was searching for i found this tutorial which made me refactor my code. Everything works fine beside jumping, or more specifically landing, where even though the velocity isn’t 0 because i press the button it completely stops moving and i need to repress the button to continue moving( video here). This is my code the only difference is that i have a state machine and i tried to implement the component in it:
Player.gd
extends CharacterBody2D
@onready var state_machine: Node = $State_Machine
func _ready() -> void:
#Initialize the state machine, passing a reference of the player to the states,
#that way they can move and react accordingly
state_machine.init(self)
func _unhandled_input(event: InputEvent) -> void:
state_machine.process_input(event)
func _physics_process(delta: float) -> void:
state_machine.process_physics(delta)
func _process(delta: float) -> void:
state_machine.process_frame(delta)
State_machine.gd
extends Node
@export var initial_state: State
var current_state: State
@export var animations: AnimationPlayer
@export_group("Components")
@export var input_component: InputComponent
@export_subgroup("Physics")
@export var gravity_component: GravityComponent
@export var jump_component: JumpComponent
@export var movement_component: MovementComponent
#Initialize the state machine by giving each child state a reference to the
#parent object it belongs to and enter the default starting_state.
func init(parent: CharacterBody2D) -> void:
for child in get_children():
child.parent = parent
child.animations = animations
child.input_component = input_component
child.gravity_component = gravity_component
child.jump_component = jump_component
child.movement_component = movement_component
#Initialize to the default state
change_state(initial_state)
#Change to the new state by first calling any exit logic on the current state
func change_state(new_state: State) -> void:
if current_state:
current_state.exit()
current_state = new_state
current_state.enter()
#print(current_state)
#Pass through functions for the player to call,
#handling state changes as needed
func process_physics(delta: float) -> void:
var new_state: State = current_state.process_physics(delta)
if new_state:
change_state(new_state)
func process_input(event: InputEvent) -> void:
var new_state: State = current_state.process_input(event)
if new_state:
change_state(new_state)
func process_frame(delta: float) -> void:
var new_state: State = current_state.process_frame(delta)
if new_state:
change_state(new_state)
State.gd
class_name State extends Node
@export var animation_name: String
var parent: CharacterBody2D
var animations: AnimationPlayer
var input_component: InputComponent
var gravity_component: GravityComponent
var jump_component: JumpComponent
var movement_component: MovementComponent
var type: State
func enter() -> void:
type = self
pass
func exit() -> void:
pass
func process_input(_event: InputEvent) -> State:
return null
func process_frame(_delta: float) -> State:
return null
func process_physics(_delta: float) -> State:
return null
Idle.gd
extends State
@export
var fall_state: State
@export
var jump_state: State
@export
var move_state: State
func enter() -> void:
super()
func process_input(event: InputEvent) -> State:
if input_component.get_jump_input():
return jump_state
if input_component.direction:
return move_state
return null
func process_physics(delta: float) -> State:
if !parent.is_on_floor():
return fall_state
return null
move.gd
extends State
@export
var fall_state: State
@export
var idle_state: State
@export
var jump_state: State
func enter() -> void:
super()
func process_input(event: InputEvent) -> State:
if input_component.get_jump_input():
return jump_state
return null
func process_physics(delta: float) -> State:
movement_component.handle_movement(parent, input_component.direction)
if not parent.velocity.x:
return idle_state
if !parent.is_on_floor():
return fall_state
return null
jump.gd
extends State
@export
var fall_state: State
@export
var idle_state: State
@export
var move_state: State
func enter() -> void:
super()
jump_component.handle_jump(parent)
func process_physics(delta: float) -> State:
gravity_component.handle_gravity(parent, delta)
movement_component.handle_movement(parent, input_component.direction)
if parent.velocity.y < 0:
return fall_state
if parent.is_on_floor():
if not input_component.direction:
return move_state
return idle_state
return null
fall.gd
extends State
@export
var idle_state: State
@export
var move_state: State
func enter() -> void:
super()
func process_physics(delta: float) -> State:
gravity_component.handle_gravity(parent, delta)
movement_component.handle_movement(parent, input_component.direction)
if parent.is_on_floor():
if not input_component.direction:
return move_state
return idle_state
return null
Components
class_name MovementComponent extends Node
@export_subgroup("Settings")
@export var speed: float = 100
@export var ground_acceleration: float = 6.0
@export var ground_deceleration: float = 8.0
@export var air_acceleration: float = 10.0
@export var air_deceleration: float = 3.0
func handle_movement(parent: CharacterBody2D, direction: float) -> void:
var velocity_change_speed: float = 0.0
if parent.is_on_floor():
velocity_change_speed = ground_acceleration if direction != 0 else ground_deceleration
else:
velocity_change_speed = air_acceleration if direction != 0 else air_deceleration
parent.velocity.x = move_toward(parent.velocity.x, direction * speed, velocity_change_speed)
parent.move_and_slide()
class_name JumpComponent extends Node
@export_subgroup("Settings")
@export var jump_velocity: float =-350.0
var is_jumping: bool = false
func handle_jump(parent: CharacterBody2D) -> void:
if parent.is_on_floor():
parent.velocity.y = jump_velocity
is_jumping = parent.velocity.y < 0 and not parent.is_on_floor()
class_name GravityComponent extends Node
@export_subgroup("Settings")
@export var gravity: int = 980
var is_falling: bool = false
func handle_gravity(parent: CharacterBody2D, delta: float) -> void:
if not parent.is_on_floor():
parent.velocity.y += gravity * delta
is_falling = parent.velocity.y > 0 and not parent.is_on_floor()
class_name InputComponent extends Node
var direction: float = 0.0
func _unhandled_input(event: InputEvent) -> void:
direction = Input.get_axis("move_left", "move_right")
func get_jump_input() -> bool:
return Input.is_action_just_pressed("jump")