Reputation: 75
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
#Play WalkForward animation when moving towards mouse, play WalkBackward animation when moving away from mouse
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")
Player Script:
extends KinematicBody2D
class_name Player
enum States {ON_GROUND, IN_AIR, GLIDING}
var _state : int = States.ON_GROUND
export var speed := 200.0
var _velocity := Vector2.ZERO
var glide_gravity := 1000.0
var base_gravity := 4000.0
var glide_acceleration := 1000.0
var glide_max_speed := 1000.0
var glide_jump_impulse := 500.0
var jump_impulse := 1200.0
func _physics_process(delta: float) -> void:
var input_direction_x: float = (
Input.get_action_strength("move_right")
- Input.get_action_strength("move_left")
)
var is_jumping: bool = _state == States.ON_GROUND and Input.is_action_pressed("jump")
if Input.is_action_just_pressed("glide") and _state == States.IN_AIR:
_state = States.GLIDING
# Canceling gliding.
if _state == States.GLIDING and Input.is_action_just_pressed("move_up"):
_state = States.IN_AIR
# Calculating horizontal velocity.
if _state == States.GLIDING:
_velocity.x += input_direction_x * glide_acceleration * delta
_velocity.x = min(_velocity.x, glide_max_speed)
else:
_velocity.x = input_direction_x * speed
# Calculating vertical velocity.
var gravity := glide_gravity if _state == States.GLIDING else base_gravity
_velocity.y += gravity * delta
if is_jumping:
var impulse = glide_jump_impulse if _state == States.GLIDING else jump_impulse
_velocity.y = -jump_impulse
_state = States.IN_AIR
# Moving the character.
_velocity = move_and_slide(_velocity, Vector2.UP)
# If we're gliding and we collide with something, we turn gliding off and the character falls.
if _state == States.GLIDING and get_slide_count() > 0:
_state = States.IN_AIR
if is_on_floor():
_state = States.ON_GROUND
func change_state(new_state: int) -> void:
var previous_state := _state
_state = new_state
Player Idle:
extends PlayerState
func enter(_msg := {}) -> void:
player.velocity = Vector2.ZERO
func physics_update(_delta: float) -> void:
if not player.is_on_floor():
state_machine.transition_to("Air")
return
if Input.is_action_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.transition_to("Move")
Player Move:
extends PlayerState
func physics_update(delta: float) -> void:
if not player.is_on_floor():
state_machine.transition_to("Air")
return
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.graivty * delta
player.velocity = player.move_and_slide(player.velocity, Vector2.UP)
if Input.is_action_pressed("jump"):
state_machine.transition_to("Air", {do_jump = true})
elif is_equal_approx(input_direction_x, 0.0):
state_machine.transition_to("Idle")
Player State:
extends State
class_name PlayerState
var player: Player
func _ready() -> void:
yield(owner, "ready")
player = owner as Player
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)
Upvotes: 1
Views: 1039
Reputation: 49
Most of the time I start like this. I hope you will find it helpful.
enum States {IDLE, MOVE, JUMP}
var current_state = States.IDLE
func _state_logic():
match current_state:
States.IDLE:
# STATE TRANSITION: IDLE -- MOVE
if Input.is_aciton_pressed("move_player"):
current_state = State.MOVE
# STATE TRANSITION: IDLE -- JUMP
if Input.is_aciton_pressed("move_player"):
current_state = State.JUMP
States.MOVE:
movement_direction = input_direction
# STATE TRANSITION: IDLE -- JUMP
if Input.is_aciton_pressed("move_player"):
current_state = State.JUMP
# STATE TRANSITION: MOVE -- IDLE
if Input.is_action_released("move_player"):
current_state = State.IDLE
States.JUMP:
handle_player_jump()
# OTHER STATE TRANSITION CODE
Upvotes: -1