How to implement player state machine - 2d

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)

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

Related

How to update a counter from an async process in F#

I need to run a process that runs something from a list - it doesn't really matter what order it runs in - but I want it to update a global "counter" when it has completed each task so that I can see the progress somewhere else ( maybe using something like signal R )
I used to do this stuff in an object-oriented way - but trying to be a little more "functional".
let doSomethingElse(value: int) = async{
// Update a global counter incrementing it by 1
return true
}
let doSomething() = async{
let values = [2; 4; 6; 8]
let! newList = values |> List.map(fun value -> doSomethingElse(value)) |> Async.Parallel
return true
}
Following what #JL0PD mentioned you could do something like the following.
[<RequireQualifiedAccess>]
module Async =
let tee (f: _ -> unit) a =
async {
let! r = a
f r
return r
}
module Counter =
let create () =
let mutable counter = 0
let increment =
fun _ ->
counter <- counter + 1
()
(increment, fun () -> counter)
let doSomethingElse(value: int) = async {
return value % 2
}
let doSomething() = async {
let values = [1; 2; 4; 6; 8; 9]
let increment, getCount = Counter.create()
let doSomethingElseWithProgress =
doSomethingElse
>> (Async.tee increment)
let! _ =
values
|> List.map doSomethingElseWithProgress
|> Async.Parallel
return getCount() = (List.length values)
}
doSomething ()
|> Async.RunSynchronously
I do recommend doing something better than a mutable counter, specially since you are dealing with parallel tasks.
In this case I'm composing doSomethingElse and increment using tee, this way doSomethingElse don't have to know (and call) an external function.

KinematicBody2D moves only to left and right

So i'm a complete newbie to coding and so i followed this tutorial:
https://www.youtube.com/watch?v=BeSJgUTLmk0
And the i cant move KinematicBody2D up nor down
Here's the code
extends KinematicBody2D
var MaxSpeed = 500
var Accel = 5000
var Motion = Vector2.ZERO
func _physics_process(delta):
var Axis = GetInputAxis()
if Axis == Vector2.ZERO:
ApplyFriction(Accel * delta)
else:
ApplyMovement(Axis * Accel * delta)
Motion = move_and_slide(Motion)
func GetInputAxis():
var Axis = Vector2.ZERO
Axis.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
Axis.y - Input.get_action_strength("ui_up") - Input.get_action_strength("ui_down")
return Axis.normalized()
func ApplyFriction(amount):
if Motion.length() > amount:
Motion -= Motion.normalized() * amount
else:
Motion = Vector2.ZERO
func ApplyMovement(acceleration):
Motion += acceleration
Motion = Motion.clamped(MaxSpeed)
and yes my keybinds are set properly

Move and rotate object around pivot and resume from where it stopped

I'd like to move an object around another - just as if the one object was a child of the other. This is GDscript - Godot Engine 3.2, but the logic should be very similar to other game engines.
Whenever I hold spacebar the green cube follows the blue cubes rotation.
First GIF demonstrates the green cube starting at position Vector3(0, 4, 0) without any rotation applied. This works perfectly fine.
In the second GIF I'm holding and releasing spacebar repeatedly. I would expect the green cube to continue from where it left, but instead it "jumps" to a new position and continues from there.
Code below does not include the actual rotation of the blue cube (pivot point), but only the calculations needed to move/rotate the green cube. Rotation of the blue cube is not an issue. Also, rotation is just for the sake of demonstration - in real scenario the blue cube would be moving around as well.
Rotations are calculated using quaternion, but this is not a requirement.
extends Node
var _parent
var _child
var _subject
var _positionOffset: Vector3
var _rotationOffset: Quat
func _ready():
_parent = get_parent()
_child = $"/root/Main/Child"
func _input(event):
if event is InputEventKey:
if event.scancode == KEY_SPACE:
if event.is_action_pressed("ui_accept") and _child != null:
_subject = _child
_set_subject_offset(_parent.transform, _child.transform)
elif event.is_action_released("ui_accept"):
_subject = null
func _set_subject_offset(pivot: Transform, subject: Transform):
_positionOffset = (pivot.origin - subject.origin)
_rotationOffset = pivot.basis.get_rotation_quat().inverse() * subject.basis.get_rotation_quat()
func _rotate_around_pivot(subject_position: Vector3, pivot: Vector3, subject_rotation: Quat):
return pivot + (subject_rotation * (subject_position - pivot))
func _physics_process(_delta):
if _subject == null: return
var target_position = _parent.transform.origin - _positionOffset
var target_rotation = _parent.transform.basis.get_rotation_quat() * _rotationOffset
_subject.transform.origin = _rotate_around_pivot(target_position, _parent.transform.origin, target_rotation)
_subject.set_rotation(target_rotation.get_euler())
I feel like I'm missing something obvious.
You can easily achieve this by temporarily parenting the subject to the pivot point while preserving the global transform:
extends Spatial
onready var obj1: Spatial = $Object1
onready var obj2: Spatial = $Object2
func reparent(obj: Spatial, new_parent: Spatial):
# Preserve the global transform while reparenting
var old_trans := obj.global_transform
obj.get_parent().remove_child(obj)
new_parent.add_child(obj)
obj.global_transform = old_trans
func _physics_process(delta: float):
obj1.rotate_z(delta)
func _input(event: InputEvent):
if event.is_action_pressed("ui_accept"):
reparent(obj2, obj1)
elif event.is_action_released("ui_accept"):
reparent(obj2, self)
If reparenting isn't feasible, you could instead parent a RemoteTransform to the pivot, and have that push its transform to the object you want to rotate:
extends Spatial
onready var remote_trans: RemoteTransform = $RemoteTransform
func _process(delta):
rotate_z(delta)
func attach(n: Node):
# move the remote so the target maintains its transform
remote_trans.global_transform = n.global_transform
remote_trans.remote_path = n.get_path()
func detach():
remote_trans.remote_path = ""
repost, Thank You path9263, answered Jun 4, 2019 by path9263 (134 points)
func rotate_around(obj:Spatial, point:Vector3, axis:Vector3, phi:float):
# https://godotengine.org/qa/45609/how-do-you-rotate-spatial-node-around-axis-given-point-space?show=45970#a45970
obj.global_translate(-point)
obj.transform = obj.transform.rotated(axis, phi)
obj.global_translate(point)
the below functions do not work
func rotate_around_point(transform_me:Spatial, pivot_point:Vector3, axis:Vector3, phi:float):
# #4: busted, no rotation
# https://stackoverflow.com/a/59939086/1695680
# Preserve the global transform while reparenting
var pivot_spatial = Spatial.new()
pivot_spatial.translation = pivot_point
var parent_orig = transform_me.get_parent()
var transform_orig = transform_me.global_transform
parent_orig.remove_child(transform_me)
pivot_spatial.add_child(transform_me)
transform_me.global_transform = transform_orig
pivot_spatial.rotate(axis, phi)
var transform_rotated = transform_me.global_transform
pivot_spatial.remove_child(transform_me)
parent_orig.add_child(transform_me)
transform_me.global_transform = transform_orig
func rotate_around_point(transform_me:Spatial, pivot_point:Vector3, axis:Vector3, phi:float):
# busted, no rotate
# https://stackoverflow.com/a/59939086/1695680
var remote_trans : RemoteTransform = RemoteTransform.new()
remote_trans.global_transform = transform_me.global_transform
remote_trans.remote_path = transform_me.get_path()
remote_trans.rotate(axis, phi)
remote_trans.remote_path = ""
func rotate_around_point(transform_me:Spatial, pivot_point:Vector3, axis:Vector3, phi:float):
# busted, local rotate
# https://godotforums.org/discussion/comment/43335/#Comment_43335
var gto_orig = transform_me.global_transform.origin
transform_me.global_transform.origin = pivot_point
transform_me.rotate(axis, phi)
transform_me.global_transform.origin = gto_orig
func rotate_around_point(transform_me:Spatial, pivot_point:Vector3, axis:Vector3, phi:float):
# busted, no rotate
# https://godotengine.org/qa/34248/rotate-around-a-fixed-point-in-3d-space?show=38928#a38928
var start_position : Vector3 = transform_me.translation
var pivot_radius : Vector3 = start_position - pivot_point
var pivot_transform : Transform = Transform(transform.basis, pivot_point)
var transform = pivot_transform.rotated(axis, phi).translated(pivot_radius)
transform.xform(transform_me)

Why isn't my code working for basic Movement in Godot?

I just started a new project in godot. So far I have one png which is the main player. I have added a sprite and CollisionShape2D to the player. When i run the game the player does not move (using keys). Does anyone know what is going on here? Heres my code:
extends KinematicBody2D
export var speed = 10.0
export var tileSize = 32.0
var initpos = Vector2()
var dir = Vector2()
var facing = 'down'
var counter = 0.0
var moving = false
func _ready():
initpos = position
func _process(delta):
if not moving:
set_dir()
elif dir != Vector2():
move(delta)
else:
moving = false
func set_dir(): #set moving
dir = get_dir()
if dir.x != 0 or dir.y != 0:
moving = true
initpos = position
func get_dir(): #user input
var x = 0
var y = 0
if dir.y == 0:
x = int(Input.is_action_pressed("ui_right")) - int(Input.is_action_pressed("ui_left"))
if dir.x == 0:
y = int(Input.is_action_pressed("ui_down")) - int(Input.is_action_pressed("ui_up"))
return Vector2(x, y)
func move(delta): #move player linearly
counter += delta + speed
if counter >= 1.0:
position = initpos + dir * tileSize
counter = 0.0
moving = false
else:
position = initpos + dir * tileSize * counter
EDIT:
I have also tried copying and pasting the code directly from Godots documentation and the player still does not move:
extends KinematicBody2D
export (int) var speed = 200
var velocity = Vector2()
func get_input():
velocity = Vector2()
if Input.is_action_pressed('right'):
velocity.x += 1
if Input.is_action_pressed('left'):
velocity.x -= 1
if Input.is_action_pressed('down'):
velocity.y += 1
if Input.is_action_pressed('up'):
velocity.y -= 1
velocity = velocity.normalized() * speed
func _physics_process(delta):
get_input()
velocity = move_and_slide(velocity)
I figured it out. When adding a new script I did not add it to the Player but to the scene.

panic: assignment to entry in nil map on single simple map

I was under the impression that the assignment to entry in nil map error would only happen if we would want to assign to a double map, that is, when a map on a deeper level is trying to be assigned while the higher one doesn't exist, e.g.:
var mm map[int]map[int]int
mm[1][2] = 3
But it also happens for a simple map (though with struct as a key):
package main
import "fmt"
type COO struct {
x int
y int
}
var neighbours map[COO][]COO
func main() {
for i := 0; i < 30; i++ {
for j := 0; j < 20; j++ {
var buds []COO
if i < 29 {
buds = append(buds, COO{x: i + 1, y: j})
}
if i > 0 {
buds = append(buds, COO{x: i - 1, y: j})
}
if j < 19 {
buds = append(buds, COO{x: i, y: j + 1})
}
if j > 0 {
buds = append(buds, COO{x: i, y: j - 1})
}
neighbours[COO{x: i, y: j}] = buds // <--- yields error
}
}
fmt.Println(neighbours)
}
What could be wrong?
You need to initialize neighbours: var neighbours = make(map[COO][]COO)
See the second section in: https://blog.golang.org/go-maps-in-action
You'll get a panic whenever you try to insert a value into a map that hasn't been initialized.
In Golang, everything is initialized to a zero value, it's the default value for uninitialized variables.
So, as it has been conceived, a map's zero value is nil. When trying to use an non-initialized map, it panics. (Kind of a null pointer exception)
Sometimes it can be useful, because if you know the zero value of something you don't have to initialize it explicitly:
var str string
str += "42"
fmt.Println(str)
// 42 ; A string zero value is ""
var i int
i++
fmt.Println(i)
// 1 ; An int zero value is 0
var b bool
b = !b
fmt.Println(b)
// true ; A bool zero value is false
If you have a Java background, that's the same thing: primitive types have a default value and objects are initialized to null;
Now, for more complex types like chan and map, the zero value is nil, that's why you have to use make to instantiate them. Pointers also have a nil zero value. The case of arrays and slice is a bit more tricky:
var a [2]int
fmt.Println(a)
// [0 0]
var b []int
fmt.Println(b)
// [] ; initialized to an empty slice
The compiler knows the length of the array (it cannot be changed) and its type, so it can already instantiate the right amount of memory. All of the values are initialized to their zero value (unlike C where you can have anything inside your array). For the slice, it is initialized to the empty slice [], so you can use append normally.
Now, for structs, it is the same as for arrays. Go creates a struct with all its fields initialized to zero values. It makes a deep initialization, example here:
type Point struct {
x int
y int
}
type Line struct {
a Point
b Point
}
func main() {
var line Line
// the %#v format prints Golang's deep representation of a value
fmt.Printf("%#v\n", line)
}
// main.Line{a:main.Point{x:0, y:0}, b:main.Point{x:0, y:0}}
Finally, the interface and func types are also initialized to nil.
That's really all there is to it. When working with complex types, you just have to remember to initialize them. The only exception is for arrays because you can't do make([2]int).
In your case, you have map of slice, so you need at least two steps to put something inside: Initialize the nested slice, and initialize the first map:
var buds []COO
neighbours := make(map[COO][]COO)
neighbours[COO{}] = buds
// alternative (shorter)
neighbours := make(map[COO][]COO)
// You have to use equal here because the type of neighbours[0] is known
neighbours[COO{}] = make([]COO, 0)

Resources