72 lines
2.1 KiB
GDScript
72 lines
2.1 KiB
GDScript
extends RefCounted
|
|
class_name StateMachine
|
|
|
|
const MIN_PRIORITY = -2147483648
|
|
|
|
signal state_changed(f_id: StringName, t_id: StringName)
|
|
|
|
var states: Dictionary[StringName, State] = {}
|
|
var transitions: Array[StateTransition] = []
|
|
var current_state: State
|
|
|
|
var _pending_id: StringName = &""
|
|
var _pending_priority = MIN_PRIORITY
|
|
|
|
func setup(state_list: Array[State], transition_list: Array[StateTransition], initial_id: StringName) -> void:
|
|
states.clear()
|
|
for s in state_list:
|
|
if s != null:
|
|
states[s.id] = s
|
|
|
|
transitions = transition_list
|
|
|
|
current_state = states.get(initial_id)
|
|
if current_state == null and not states.is_empty():
|
|
current_state = states.values()[0]
|
|
push_warning("State machine initial state '%s' not found, falling back to '%s'" % [initial_id, current_state.id])
|
|
|
|
func process(data: Object, delta: float) -> void:
|
|
if current_state == null:
|
|
return
|
|
|
|
current_state.process(data, delta)
|
|
_evaluate_transitions(data)
|
|
|
|
# Escape hatch for an immediate transition
|
|
func request_transition(to_id: StringName, priority: int = 999) -> void:
|
|
if priority >= _pending_priority:
|
|
_pending_id = to_id
|
|
_pending_priority = priority
|
|
|
|
func _evaluate_transitions(data: Object) -> void:
|
|
var best_id: StringName = &""
|
|
var best_priority: int = MIN_PRIORITY
|
|
|
|
# Is there a next state pending that isn't our current state?
|
|
if _pending_id != &"" and _pending_id != current_state.id and states.has(_pending_id):
|
|
best_id = _pending_id
|
|
best_priority = _pending_priority
|
|
|
|
for t in transitions:
|
|
# Skip the current state
|
|
if not t.matches_current(current_state.id) or t.to == current_state.id:
|
|
continue
|
|
if not states.has(t.to):
|
|
push_warning("StateMachine: transition targets unknown state '%s'" % t.to)
|
|
continue
|
|
if t.priority < best_priority:
|
|
continue
|
|
if t.is_satisfied(data):
|
|
best_id = t.to
|
|
best_priority = t.priority
|
|
|
|
_pending_id = &""
|
|
_pending_priority = MIN_PRIORITY
|
|
|
|
if best_id != &"" and best_id != current_state.id:
|
|
var from_id = current_state.id
|
|
current_state.exit(data)
|
|
current_state = states[best_id]
|
|
current_state.enter(data)
|
|
state_changed.emit(from_id, current_state.id)
|