Skip to content

Instantly share code, notes, and snippets.

@PranavSK
Last active April 12, 2021 09:35
Show Gist options
  • Select an option

  • Save PranavSK/b3eff3da7be23998f5be58f5d4b85094 to your computer and use it in GitHub Desktop.

Select an option

Save PranavSK/b3eff3da7be23998f5be58f5d4b85094 to your computer and use it in GitHub Desktop.
class_name StateMachine
extends Node
"""
An automata implemented as a Node which supports a pushdown stack.
The below is a state machine that can be used as a finite state machine or a
pushdown automata by using Object or Object-derived instances as nodes.
Implement one of the following callback functions in the state object to get the
relevant behaviour.
Called by the state machine after the state object is registered.
func on_register(state_machine: StateMachine) -> void: pass
Called by the state machine after the state object is unregistered.
func on_unregister() -> void: pass
Called by the state machine when the state is added to the stack.
func on_enter() -> void: pass
Called by the state machine when the state is removed from the stack.
func on_exit() -> void: pass
Called by the state machine when the state is pushed down the stack.
func on_pause() -> void: pass
Called by the state machine when the stack is popped and this state object is
brought to the top of the stack.
func on_resume() -> void: pass
Called by the state machine on every update.
func on_update(delta) -> void: pass
Check if any transition from this state occurs.
func check_transition(state_machine: StateMachine) -> bool: return false
"""
var _states: = {}
var _state_names_stack = []
var _prev_state_name
func register_state(state_name: String, state: Object) -> void:
"""
Register a state to use with the state machine.
Parameters:
state_name (String): The name of the state to register.
state (Object): The state object to register.
"""
if _states.has(state_name):
push_error("The given state %s is already registered." % state_name)
return
_states[state_name] = state
if state.has_method("on_register"):
state.on_register()
func is_registered(state_name: String) -> bool:
"""
Check if a state is already registered.
Parameters:
state_name (String): The name of the state to check.
Returns:
(bool): True if a state with state_name is registered, else false.
"""
return _states.has(state_name)
func unregister_state(state_name: String):
"""
Unregister a state and remove all its instances from the state stack.
The state machine callbacks are not invoked if an instance of the state was
in the stack.
Parameters:
state_name (String): The name of the state to unregister and remove.
"""
var state_to_remove = _states[state_name]
var inds_to_remove: = []
for ind in _state_names_stack.size():
if _state_names_stack[ind] == state_name:
inds_to_remove.append(ind)
for ind in inds_to_remove:
_state_names_stack.remove(ind)
if state_to_remove.has_method("on_unregister"):
state_to_remove.on_unregister()
_states.erase(state_name)
func get_registered_state(state_name: String) -> Object:
"""
Get the state object registered with the name given.
Parameters:
state_name (String): The name of the state to retrieve.
Returns:
(Object): The state object registered with state_name.
"""
return _states[state_name]
func get_current_state() -> Object:
"""
Get current active state object.
Returns:
(Object): The state object that is currently active
"""
return get_registered_state(_state_names_stack.back())
func get_current_state_name() -> String:
"""
Get current active state name.
Returns:
(String): The name of the state that is currently active.
"""
return _state_names_stack.back()
func get_prev_state_name() -> String:
"""
Get previously active state name.
Return:
(String): The name of the previously active state.
"""
return _prev_state_name
func change_state(new_state_name: String) -> void:
"""
Replaces the active state with another state, without notifying the
underlying state.
Parameters
new_state_name (String): The name of the state to change to.
It needs to be registered.
"""
_prev_state_name = get_current_state_name()
pop_state_silent()
push_state_silent(new_state_name)
func pop_state() -> void:
"""Pops the current state and resumes the underlying state."""
_prev_state_name = get_current_state_name()
pop_state_silent()
var curr_state = get_current_state()
if curr_state and curr_state.has_method("on_resume"):
curr_state.on_resume()
func pop_state_silent() -> void:
"""Pops the current state without notifying the underlying state."""
var old_state_name = _state_names_stack.pop_back()
var old_state = get_registered_state(old_state_name)
if old_state and old_state.has_method("on_exit"):
old_state.on_exit()
func pop_all(excluding_root: = false) -> void:
"""
Pops all states in the stack.
Parameters
excluding_root (bool): Whether to keep the bottom state.
"""
while _state_names_stack.size() > 1 if excluding_root else 0:
pop_state()
func push_state(new_state_name: String) -> void:
"""
Pushes a state to the top of the stack and pauses the underlying state.
Parameters
new_state_name (String): The name of the state to push.
It needs to be registered.
"""
_prev_state_name = get_current_state_name()
var old_state = get_current_state()
if old_state and old_state.has_method("on_pause"):
old_state.on_pause()
push_state_silent(new_state_name)
func push_state_silent(new_state_name: String) -> void:
"""
Pushes a state to the top of the stack without notifying the underlying
state.
Parameters
new_state_name (String): The name of the new state to push.
It needs to be registered.
"""
_state_names_stack.push_back(new_state_name)
var new_state = get_registered_state(new_state_name)
if new_state.has_method("on_enter"):
new_state.on_enter()
func update(delta: float) -> void:
"""
Update the state machine.
Updates the current active state and then checks for any valid transitions.
Parameters:
delta (float): The time delta relevant for the update tick.
"""
if _state_names_stack.empty():
return
if get_current_state().has_method("on_update"):
get_current_state().on_update(delta)
# TODO: Else check the transition table of the state machine.
# if (
# get_current_state().has_method("check_transition") and
# get_current_state().check_transition(self)
# ):
# # A transition has occured from within the state.
# return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment