107 lines
2.9 KiB
GDScript
107 lines
2.9 KiB
GDScript
tool
|
|
class_name CompoundState, "compound_state.svg"
|
|
extends State
|
|
|
|
## initial state to activate when state is activated
|
|
export var initial_state: NodePath setget _set_initial_state
|
|
|
|
var _active_state: State = null
|
|
|
|
onready var _initial_state: State = get_node_or_null(initial_state)
|
|
|
|
func _set_initial_state(value: NodePath) -> void:
|
|
initial_state = value
|
|
update_configuration_warning()
|
|
|
|
func _state_init() -> void:
|
|
._state_init()
|
|
|
|
for child in get_children():
|
|
if child is State:
|
|
child._state_init()
|
|
|
|
func _state_enter() -> void:
|
|
._state_enter()
|
|
|
|
# activate initial state
|
|
if _initial_state != null:
|
|
_active_state = _initial_state
|
|
_active_state._state_enter()
|
|
else:
|
|
push_error("no initial state set for state %s" % name)
|
|
|
|
func _state_exit() -> void:
|
|
# deactivate current state
|
|
if _active_state != null:
|
|
_active_state._state_exit()
|
|
_active_state = null
|
|
._state_exit()
|
|
|
|
func _state_event(event: String) -> bool:
|
|
if not active:
|
|
return false
|
|
|
|
# forward event to active state
|
|
if is_instance_valid(_active_state):
|
|
if _active_state._state_event(event):
|
|
emit_signal("event_received", event)
|
|
return true
|
|
|
|
# if event not handled by active state, handle here
|
|
return ._state_event(event)
|
|
|
|
func _handle_transition(transition: Transition, source: State) -> void:
|
|
var target: State = transition.resolve_target()
|
|
if not target is State:
|
|
push_error("the target state: %s of transition from state: %s is not a state" % [str(transition.to), source.name])
|
|
return
|
|
|
|
if target.active:
|
|
return
|
|
|
|
# if direct child, just switch active state
|
|
if target in get_children():
|
|
# deactivate current state
|
|
if is_instance_valid(_active_state):
|
|
_active_state._state_exit()
|
|
# activate target state
|
|
_active_state = target
|
|
_active_state._state_enter()
|
|
return
|
|
|
|
# if ancestor, activate next state down and let it handle transition further
|
|
if self.is_a_parent_of(target):
|
|
# find which child is also ancestor
|
|
for child in get_children():
|
|
if child.is_a_parent_of(target):
|
|
# change state if necessary
|
|
if _active_state != child:
|
|
if is_instance_valid(_active_state):
|
|
_active_state._state_exit()
|
|
_active_state = child
|
|
_active_state._state_enter()
|
|
child._handle_transition(transition, source)
|
|
return
|
|
return
|
|
|
|
# target is a cousin, defer to mommy
|
|
get_parent()._handle_transition(transition, source)
|
|
|
|
func _get_configuration_warning() -> String:
|
|
var warning := ._get_configuration_warning()
|
|
if not warning.empty():
|
|
return warning
|
|
|
|
if get_child_count() == 0:
|
|
return "compound states must have at least one child state"
|
|
|
|
var child_state = get_node_or_null(initial_state)
|
|
|
|
if not is_instance_valid(child_state):
|
|
return "initial state not found, is the path correct?"
|
|
if child_state.get_parent() != self:
|
|
return "initial state must be a direct child of this compound state"
|
|
if not child_state is State:
|
|
return "initial state must be a State"
|
|
|
|
return ""
|