Skip to content

Instantly share code, notes, and snippets.

@nonunknown
Created September 18, 2021 19:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nonunknown/0cd9321d4ddb98afaf139288b0279568 to your computer and use it in GitHub Desktop.
Save nonunknown/0cd9321d4ddb98afaf139288b0279568 to your computer and use it in GitHub Desktop.
[Godot 4] State machine
extends RefCounted
class_name StateMachine
## This is a state machine implementation, very optimize w/ code organization on your side
var _states_enter:Array[StringName]
var _states_update:Array[StringName]
var _states_exit:Array[StringName]
var _current_state:int = 0
var _machine_owner:Node = null
## Register all states that the machine contains
## for each state the user should create the following functions: _enter_name, _update_name, _exit_name
func register_states(target:Node, states:Dictionary,initial_state:int = 0) -> void:
#Register the functions to be called in arrays of stringname (which is faster)
for st in states:
_states_enter.append(StringName("_enter_%s" % st))
_states_update.append(StringName("_update_%s" % st))
_states_exit.append(StringName("_exit_%s" % st))
print("Registering State: ", st)
_current_state = initial_state
_machine_owner = target
#Call the initial state
target.call(_states_enter[_current_state])
pass
## change the state to the target one
func change_state(to_state:int) -> void:
#call the old state's exit
_machine_owner.call(_states_exit[_current_state])
#update the current state to the targeted one
_current_state = to_state
#call the new state's enter method
_machine_owner.call(_states_enter[to_state])
pass
## update the machine, must be called in _process or _physics_process
func update(delta:float) -> void:
_machine_owner.call(_states_update[_current_state],delta)
pass
## return the integer of current state according to states dictionary
func get_current_state() -> int: return _current_state
## return the bolean to check the current state
func current_state_is(state:int) -> bool: return state == _current_state
@nonunknown
Copy link
Author

nonunknown commented Sep 18, 2021

Usage example:

extends CharacterBody3D
class_name Player

enum STATES {idle,walk,run,jump,crouch} # The function name should follow enum names e.g _enter_idle()
const __st_names:Array[String] = ["IDLE","WALK","RUN","JUMP","CROUCH"] #used just for debugging purposes

var state_machine:StateMachine


func _ready() -> void:
	state_machine = StateMachine.new()
	state_machine.register_states(self,STATES)

func _physics_process(delta):
	state_machine.update(delta)

#STATE TRANSITIONS CONDITIONS - CT = Condition of Transition

func ct_walk() -> void:
	if speed_length > .1:
		state_machine.change_state(STATES.walk)
	pass

func ct_idle() -> void:
	if speed_length < .1 :
		state_machine.change_state(STATES.idle)
	pass
	
func ct_crouch() -> void:
	if Input.is_action_just_pressed("cmd_crouch"):
		state_machine.change_state(STATES.crouch)
	pass

func ct_run() -> void:
	if Input.is_action_just_pressed("cmd_run"):
		state_machine.change_state(STATES.run)
	pass

#END

func _enter_idle() -> void:
	weapon_animation.play("idle",.3)
	pass

func _update_idle(delta:float) -> void:
	
	ct_walk()
	ct_crouch()
	check_shoot()
	check_sight()
	pass

func _exit_idle() -> void:
	
	pass

func _enter_run() -> void:
	weapon_animation.play("run",.1)
	max_speed = max_run_speed
	$FootSteps.set_target(1) #make run sounds
	pass

func _update_run(delta:float) -> void:
	var running:bool = Input.is_action_pressed("cmd_run")
	
	if !running:
		ct_walk()
		ct_idle()
	pass

func _exit_run() -> void:
	max_speed = max_walk_speed
	pass

func _enter_walk() -> void:
	$FootSteps.set_target(0) #make walk sounds
	max_speed = max_walk_speed
	weapon_animation.play("walk",.15)
	pass

func _update_walk(delta:float) -> void:
	check_shoot()
	weapon_animation.playback_speed = speed_length / (max_speed * .7)
	ct_run()
	ct_idle()
	pass

func _exit_walk() -> void:
	weapon_animation.playback_speed = 1
	pass

func _enter_crouch() -> void:
	
	pass

func _update_crouch(delta:float) -> void:
	ct_idle()
	pass

func _exit_crouch() -> void:
	
	pass

#func _enter_() -> void:
#
#	pass
#
#func _update_(delta:float) -> void:
#
#	pass
#
#func _exit_() -> void:
#
#	pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment