Attention | Topic was automatically imported from the old Question2Answer platform. | |
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)