initial commit: canny cat basic movement, bouncing, some gridmap tiles for levels
This commit is contained in:
commit
e1b43c8bc5
120 changed files with 5785 additions and 0 deletions
319
addons/godot_state_charts/state_chart_state.gd
Normal file
319
addons/godot_state_charts/state_chart_state.gd
Normal file
|
@ -0,0 +1,319 @@
|
|||
@tool
|
||||
## This class represents a state that can be either active or inactive.
|
||||
class_name StateChartState
|
||||
extends Node
|
||||
|
||||
## Called when the state is entered.
|
||||
signal state_entered()
|
||||
|
||||
## Called when the state is exited.
|
||||
signal state_exited()
|
||||
|
||||
## Called when the state receives an event. Only called if the state is active.
|
||||
signal event_received(event:StringName)
|
||||
|
||||
## Called when the state is processing.
|
||||
signal state_processing(delta:float)
|
||||
|
||||
## Called when the state is physics processing.
|
||||
signal state_physics_processing(delta:float)
|
||||
|
||||
## Called when the state chart step function is called.
|
||||
signal state_stepped()
|
||||
|
||||
## Called when the state is receiving input.
|
||||
signal state_input(event:InputEvent)
|
||||
|
||||
## Called when the state is receiving unhandled input.
|
||||
signal state_unhandled_input(event:InputEvent)
|
||||
|
||||
## Called every frame while a delayed transition is pending for this state.
|
||||
## Returns the initial delay and the remaining delay of the transition.
|
||||
signal transition_pending(initial_delay:float, remaining_delay:float)
|
||||
|
||||
|
||||
## Whether the state is currently active (internal flag, use active).
|
||||
var _state_active:bool = false
|
||||
|
||||
## Whether the current state is active.
|
||||
var active:bool:
|
||||
get: return _state_active
|
||||
|
||||
## The currently active pending transition.
|
||||
var _pending_transition:Transition = null
|
||||
|
||||
## Remaining time in seconds until the pending transition is triggered.
|
||||
var _pending_transition_remaining_delay:float = 0
|
||||
## The initial time of the pending transition.
|
||||
var _pending_transition_initial_delay:float = 0
|
||||
|
||||
## Transitions in this state that react on events.
|
||||
var _transitions:Array[Transition] = []
|
||||
|
||||
|
||||
## The state chart that owns this state.
|
||||
var _chart:StateChart
|
||||
|
||||
func _ready() -> void:
|
||||
# don't run in the editor
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
_chart = _find_chart(get_parent())
|
||||
|
||||
|
||||
## Finds the owning state chart by moving upwards.
|
||||
func _find_chart(parent:Node) -> StateChart:
|
||||
if parent is StateChart:
|
||||
return parent
|
||||
|
||||
return _find_chart(parent.get_parent())
|
||||
|
||||
## Runs a transition either immediately or delayed depending on the
|
||||
## transition settings.
|
||||
func _run_transition(transition:Transition, immediately:bool = false):
|
||||
var initial_delay := transition.evaluate_delay()
|
||||
if not immediately and initial_delay > 0:
|
||||
_queue_transition(transition, initial_delay)
|
||||
else:
|
||||
_chart._run_transition(transition, self)
|
||||
|
||||
|
||||
|
||||
## Called when the state chart is built.
|
||||
func _state_init():
|
||||
# disable state by default
|
||||
process_mode = Node.PROCESS_MODE_DISABLED
|
||||
_state_active = false
|
||||
_toggle_processing(false)
|
||||
|
||||
# load transitions
|
||||
_transitions.clear()
|
||||
for child in get_children():
|
||||
if child is Transition:
|
||||
_transitions.append(child)
|
||||
|
||||
|
||||
## Called when the state is entered. The parameter gives the target of the transition
|
||||
## that caused the entering of the state. This can be used determine whether an
|
||||
## initial child state should be activated. If the state entering was not caused by a transition
|
||||
## this can be null.
|
||||
func _state_enter(_transition_target:StateChartState):
|
||||
# print("state_enter: " + name)
|
||||
_state_active = true
|
||||
|
||||
process_mode = Node.PROCESS_MODE_INHERIT
|
||||
|
||||
# enable processing if someone listens to our signal
|
||||
_toggle_processing(true)
|
||||
|
||||
# emit the signal
|
||||
state_entered.emit()
|
||||
|
||||
# process transitions that are triggered by entering the state
|
||||
_process_transitions(StateChart.TriggerType.STATE_ENTER)
|
||||
|
||||
## Called when the state is exited.
|
||||
func _state_exit():
|
||||
# print("state_exit: " + name)
|
||||
# cancel any pending transitions
|
||||
_pending_transition = null
|
||||
_pending_transition_remaining_delay = 0
|
||||
_pending_transition_initial_delay = 0
|
||||
_state_active = false
|
||||
# stop processing
|
||||
process_mode = Node.PROCESS_MODE_DISABLED
|
||||
_toggle_processing(false)
|
||||
|
||||
# emit the signal
|
||||
state_exited.emit()
|
||||
|
||||
## Called when the state should be saved. The parameter is is the SavedState object
|
||||
## of the parent state. The state is expected to add a child to the SavedState object
|
||||
## under its own name.
|
||||
##
|
||||
## The child_levels parameter indicates how many levels of children should be saved.
|
||||
## If set to -1 (default), all children should be saved. If set to 0, no children should be saved.
|
||||
##
|
||||
## This method will only be called if the state is active and should only be called on
|
||||
## active children if children should be saved.
|
||||
func _state_save(saved_state:SavedState, child_levels:int = -1) -> void:
|
||||
if not active:
|
||||
push_error("_state_save should only be called if the state is active.")
|
||||
return
|
||||
|
||||
# create a new SavedState object for this state
|
||||
var our_saved_state := SavedState.new()
|
||||
our_saved_state.pending_transition_name = _pending_transition.name if _pending_transition != null else ""
|
||||
our_saved_state.pending_transition_remaining_delay = _pending_transition_remaining_delay
|
||||
our_saved_state.pending_transition_initial_delay = _pending_transition_initial_delay
|
||||
# add it to the parent
|
||||
saved_state.add_substate(self, our_saved_state)
|
||||
|
||||
if child_levels == 0:
|
||||
return
|
||||
|
||||
# calculate the child levels for the children, -1 means all children
|
||||
var sub_child_levels:int = -1 if child_levels == -1 else child_levels - 1
|
||||
|
||||
# save all children
|
||||
for child in get_children():
|
||||
if child is StateChartState and child.active:
|
||||
child._state_save(our_saved_state, sub_child_levels)
|
||||
|
||||
|
||||
## Called when the state should be restored. The parameter is the SavedState object
|
||||
## of the parent state. The state is expected to retrieve the SavedState object
|
||||
## for itself from the parent and restore its state from it.
|
||||
##
|
||||
## The child_levels parameter indicates how many levels of children should be restored.
|
||||
## If set to -1 (default), all children should be restored. If set to 0, no children should be restored.
|
||||
##
|
||||
## If the state was not active when it was saved, this method still will be called
|
||||
## but the given SavedState object will not contain any data for this state.
|
||||
func _state_restore(saved_state:SavedState, child_levels:int = -1) -> void:
|
||||
# print("restoring state " + name)
|
||||
var our_saved_state := saved_state.get_substate_or_null(self)
|
||||
if our_saved_state == null:
|
||||
# if we are currently active, deactivate the state
|
||||
if active:
|
||||
_state_exit()
|
||||
# otherwise we are already inactive, so we don't need to do anything
|
||||
return
|
||||
|
||||
# otherwise if we are currently inactive, activate the state
|
||||
if not active:
|
||||
_state_enter(null)
|
||||
# and restore any pending transition
|
||||
_pending_transition = get_node_or_null(our_saved_state.pending_transition_name) as Transition
|
||||
_pending_transition_remaining_delay = our_saved_state.pending_transition_remaining_delay
|
||||
_pending_transition_initial_delay = our_saved_state.pending_transition_initial_delay
|
||||
|
||||
# if _pending_transition != null:
|
||||
# print("restored pending transition " + _pending_transition.name + " with time " + str(_pending_transition_remaining_delay))
|
||||
# else:
|
||||
# print("no pending transition restored")
|
||||
|
||||
if child_levels == 0:
|
||||
return
|
||||
|
||||
# calculate the child levels for the children, -1 means all children
|
||||
var sub_child_levels := -1 if child_levels == -1 else child_levels - 1
|
||||
|
||||
# restore all children
|
||||
for child in get_children():
|
||||
if child is StateChartState:
|
||||
child._state_restore(our_saved_state, sub_child_levels)
|
||||
|
||||
|
||||
## Called while the state is active.
|
||||
func _process(delta:float) -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
# emit the processing signal
|
||||
state_processing.emit(delta)
|
||||
# check if there is a pending transition
|
||||
if _pending_transition != null:
|
||||
_pending_transition_remaining_delay -= delta
|
||||
|
||||
# Notify interested parties that currently a transition is pending.
|
||||
transition_pending.emit(_pending_transition.delay_seconds, max(0, _pending_transition_remaining_delay))
|
||||
|
||||
# if the transition is ready, trigger it
|
||||
# and clear it.
|
||||
if _pending_transition_remaining_delay <= 0:
|
||||
var transition_to_send := _pending_transition
|
||||
_pending_transition = null
|
||||
_pending_transition_remaining_delay = 0
|
||||
# print("requesting transition from " + name + " to " + transition_to_send.to.get_concatenated_names() + " now")
|
||||
_chart._run_transition(transition_to_send, self)
|
||||
|
||||
|
||||
|
||||
func _handle_transition(_transition:Transition, _source:StateChartState):
|
||||
push_error("State " + name + " cannot handle transitions.")
|
||||
|
||||
|
||||
func _physics_process(delta:float) -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
state_physics_processing.emit(delta)
|
||||
|
||||
## Called when the state chart step function is called.
|
||||
func _state_step():
|
||||
state_stepped.emit()
|
||||
|
||||
func _input(event:InputEvent):
|
||||
state_input.emit(event)
|
||||
|
||||
|
||||
func _unhandled_input(event:InputEvent):
|
||||
state_unhandled_input.emit(event)
|
||||
|
||||
## Processes all transitions in this state.
|
||||
func _process_transitions(trigger_type:StateChart.TriggerType, event:StringName = "") -> bool:
|
||||
if not active:
|
||||
return false
|
||||
|
||||
# emit an event received signal if this is an event trigger
|
||||
if trigger_type == StateChart.TriggerType.EVENT:
|
||||
event_received.emit(event)
|
||||
|
||||
# Walk over all transitions
|
||||
for transition in _transitions:
|
||||
# Check if the transition is triggered by the given trigger type
|
||||
if transition.is_triggered_by(trigger_type) \
|
||||
# if the event is given it needs to match the event of the transition
|
||||
and (event == "" or transition.event == event) \
|
||||
# and in every case the guard needs to match
|
||||
and transition.evaluate_guard():
|
||||
# print(name + ": consuming event " + event)
|
||||
# first match wins
|
||||
# if the winning transition is the currently pending transition, we do not replace it
|
||||
if transition != _pending_transition:
|
||||
_run_transition(transition)
|
||||
|
||||
# but in any case we return true, because we consumed the event
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
## Queues the transition to be triggered after the delay.
|
||||
func _queue_transition(transition:Transition, initial_delay:float):
|
||||
# print("transitioning from " + name + " to " + transition.to.get_concatenated_names() + " in " + str(transition.delay_seconds) + " seconds" )
|
||||
# queue the transition for the delay time (0 means next frame)
|
||||
_pending_transition = transition
|
||||
_pending_transition_initial_delay = initial_delay
|
||||
_pending_transition_remaining_delay = initial_delay
|
||||
|
||||
# enable processing when we have a transition
|
||||
set_process(true)
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var result := []
|
||||
# if not at least one of our ancestors is a StateChart add a warning
|
||||
var parent := get_parent()
|
||||
var found := false
|
||||
while is_instance_valid(parent):
|
||||
if parent is StateChart:
|
||||
found = true
|
||||
break
|
||||
parent = parent.get_parent()
|
||||
|
||||
if not found:
|
||||
result.append("State is not a child of a StateChart. This will not work.")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func _toggle_processing(active:bool):
|
||||
set_process(active and _has_connections(state_processing))
|
||||
set_physics_process(active and _has_connections(state_physics_processing))
|
||||
set_process_input(active and _has_connections(state_input))
|
||||
set_process_unhandled_input(active and _has_connections(state_unhandled_input))
|
||||
|
||||
## Checks whether the given signal has connections.
|
||||
func _has_connections(sgnl:Signal) -> bool:
|
||||
return sgnl.get_connections().size() > 0
|
Loading…
Add table
Add a link
Reference in a new issue