godot-parkour/addons/godot_state_charts/parallel_state.gd

117 lines
3.3 KiB
GDScript

@tool
@icon("parallel_state.svg")
## A parallel state is a state which can have sub-states, all of which are active
## when the parallel state is active.
class_name ParallelState
extends State
# all children of the state
var _sub_states:Array[State] = []
func _state_init():
super._state_init()
# find all children of this state which are states
for child in get_children():
if child is State:
_sub_states.append(child)
child._state_init()
# since there is no state transitions between parallel states, we don't need to
# subscribe to events from our children
func _handle_transition(transition:Transition, source:State):
# resolve the target state
var target = transition.resolve_target()
if not target is State:
push_error("The target state '" + str(transition.to) + "' of the transition from '" + source.name + "' is not a state.")
return
# the target state can be
# 0. this state. in this case just activate the state and all its children.
# this can happen when a child state transfers back to its parent state.
# 1. a direct child of this state. this is the easy case in which
# we will do nothing, because our direct children are always active.
# 2. a descendant of this state. in this case we find the direct child which
# is the ancestor of the target state and then ask it to perform
# the transition.
# 3. no descendant of this state. in this case, we ask our parent state to
# perform the transition
if target == self:
# exit this state
_state_exit()
# then re-enter it
_state_enter(false)
return
if target in get_children():
# all good, nothing to do.
return
if self.is_ancestor_of(target):
# find the child which is the ancestor of the new target.
for child in get_children():
if child is State and child.is_ancestor_of(target):
# ask child to handle the transition
child._handle_transition(transition, source)
return
return
# ask the parent
get_parent()._handle_transition(transition, source)
func _state_enter(expect_transition:bool = false):
super._state_enter()
# enter all children
for child in _sub_states:
child._state_enter()
func _state_exit():
# exit all children
for child in _sub_states:
child._state_exit()
super._state_exit()
func _state_step():
super._state_step()
for child in _sub_states:
child._state_step()
func _process_transitions(event:StringName, property_change:bool = false) -> bool:
if not active:
return false
# forward to all children
var handled := false
for child in _sub_states:
var child_handled_it = child._process_transitions(event, property_change)
handled = handled or child_handled_it
# if any child handled this, we don't touch it anymore
if handled:
# emit the event_received signal for completeness
# unless it was a property change
if not property_change:
self.event_received.emit(event)
return true
# otherwise handle it ourselves
# defer to the base class
return super._process_transitions(event, property_change)
func _get_configuration_warnings() -> PackedStringArray:
var warnings = super._get_configuration_warnings()
var child_count = 0
for child in get_children():
if child is State:
child_count += 1
if child_count < 2:
warnings.append("Parallel states should have at least two child states.")
return warnings