@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