diff --git a/addons/behaviour_toolkit/finite_state_machine/fsm.gd b/addons/behaviour_toolkit/finite_state_machine/fsm.gd index 722adf7..57497e0 100644 --- a/addons/behaviour_toolkit/finite_state_machine/fsm.gd +++ b/addons/behaviour_toolkit/finite_state_machine/fsm.gd @@ -49,13 +49,16 @@ signal state_changed(state: FSMState) ## Whether the FSM should print debug messages. @export var verbose: bool = false - ## The list of states in the FSM. var states: Array[FSMState] ## The current active state. var active_state: FSMState ## The list of current events. var current_events: Array[String] +## The list of nested FSMs in the FSM. +var nested_state_machines: Array[FiniteStateMachine] +## Nesting Depth (0 = not nested) +var nest_level = 0 ## Current BT BTStatus var current_bt_status: BTBehaviour.BTStatus @@ -95,7 +98,8 @@ func start() -> void: for state in get_children(): if state is FSMState: states.append(state) - + + if verbose: BehaviourToolkit.Logger.say("Setting up " + str(states.size()) + " states.", self) active = true @@ -202,5 +206,10 @@ func _get_configuration_warnings() -> PackedStringArray: for child in children: if not child is FSMState: warnings.append("Node '" + child.get_name() + "' is not a FSMState.") - + + if initial_state: + # check if initial_state is a descendant of this FSM + if not is_ancestor_of(initial_state): + warnings.append("Don't select initial state outside of this FSM") + return warnings diff --git a/addons/behaviour_toolkit/finite_state_machine/fsm_state.gd b/addons/behaviour_toolkit/finite_state_machine/fsm_state.gd index cd70fe7..01b28b1 100644 --- a/addons/behaviour_toolkit/finite_state_machine/fsm_state.gd +++ b/addons/behaviour_toolkit/finite_state_machine/fsm_state.gd @@ -44,7 +44,7 @@ func _get_configuration_warnings() -> PackedStringArray: var warnings: Array = [] var parent: Node = get_parent() - if not parent is FiniteStateMachine: + if not (parent is FiniteStateMachine or parent is NestedFSM): warnings.append("FSMState should be a child of a FiniteStateMachine node.") return warnings diff --git a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd index 42c917e..4414b11 100644 --- a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd +++ b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd @@ -54,16 +54,48 @@ func get_next_state() -> FSMState: func _get_configuration_warnings() -> PackedStringArray: - var warnings: Array = [] + var warnings: PackedStringArray = [] var parent: Node = get_parent() if not parent is FSMState: warnings.append("FSMTransition should be a child of FSMState.") - + if not next_state: warnings.append("FSMTransition has no next state.") - + return warnings + if use_event and event == "": warnings.append("FSMTransition has no event set.") + + #HACK: Could benefit from caching fsm + var fsm := _find_fsm() + if fsm: + # Get paths directly + var our_path := fsm.get_path_to(get_parent()) + var their_path := fsm.get_path_to(next_state) + + # Debug prints + print(name, " Our Path: ", our_path.get_name_count()) + print(name, " Next Path: ", their_path.get_name_count()) + + # Compare the number of node names in the node paths + var our_size := our_path.get_name_count() + var their_size := their_path.get_name_count() + + # Check if they have different nesting levels + if our_size != their_size: + warnings.append("FSMTransition should not transition outside of this NestedFSM.") + # Check if they're at the same level but in different branches (different immediate parents) + elif our_size >= 2 and their_size >= 2: + if our_path.get_name(our_size - 2) != their_path.get_name(their_size - 2): + warnings.append("FSMTransition should not transition outside of this NestedFSM.") return warnings + +func _find_fsm() -> FiniteStateMachine: + var current: Node = get_parent() + while current: + if current is FiniteStateMachine: + return current as FiniteStateMachine + current = current.get_parent() + return null diff --git a/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd new file mode 100644 index 0000000..8e0b693 --- /dev/null +++ b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd @@ -0,0 +1,109 @@ +@tool +extends FSMState +class_name NestedFSM + +# TODO: +# Make Template +# Integrate into UI +# Add to the documentation + +## An implementation of a simple finite state machine. +## +## The Nested Finite State Machine is a state that contains a FiniteStateMachine insde +## This Instanced FiniteStateMachine inherits values from the parent FiniteStateMachine. +## On ready, the NestedFSM will reparent each child that is an FSMState or NestedFSM to the new FiniteStateMachine. +## To implement your logic you can override the [code]_on_enter, _on_update and +## _on_exit[/code] methods when extending the node's script. + + +## The signal emitted when the fsm's state changes. +signal nested_state_changed(state: FSMState) + +@export var initial_state : FSMState +@export var verbose : bool + +# Create a child FSM instance to manage the nested states +@onready var fsm = FiniteStateMachine.new() + +func _ready() -> void: + super() + + if Engine.is_editor_hint(): + return + + # Find all direct child nodes that are FSMStates + var nested_states : Array[Node] = get_children().filter(func(n): return n is FSMState) + # Find all direct child nodes that are NestedFSMs (for hierarchical nesting) + var nested_fsms : Array[Node] = get_children().filter(func(n): return n is NestedFSM) + + # Add the FSM container as a child node, and rename it for clarity in scene tree + add_child(fsm) + fsm.name = "NestedFSM" + + # Move all FSMState children to the internal FSM container + for state in nested_states: + state.reparent(fsm) + + # Move all nested FSMs to the internal FSM container + for nested_fsm in nested_fsms: + nested_fsm.reparent(fsm) + + # Configure the internal FSM + fsm.initial_state = initial_state # Set the starting state + fsm.verbose = verbose # Set debug output preference + + # Get reference to the parent state machine + # After reparenting, our parent should always be a FiniteStateMachine + # (either the root FSM or another NestedFSM's internal FSM) + var parent_state_machine : FiniteStateMachine = get_parent() + + # Share resources with the parent FSM + fsm.actor = parent_state_machine.actor # Share the actor reference + fsm.process_type = parent_state_machine.process_type # Share process type (physics/idle) + fsm.blackboard = parent_state_machine.blackboard # Share the blackboard (shared data) + + # Connect the internal FSM's state_changed signal to our nested_state_changed signal + fsm.state_changed.connect(func(state: FSMState): nested_state_changed.emit(state)) + +# Executes after the state is entered. +func _on_enter(_actor: Node, _blackboard: Blackboard) -> void: + fsm.start() + +# Executes before the state is exited. +func _on_exit(_actor: Node, _blackboard: Blackboard) -> void: + fsm.active = false + + +# TODO: Improve configuration warnings for Nested FSM +# Now that NestedFSMs exist, there should be one to account for when the user selects a state from another FSM as an intitial state + + +# Add custom configuration warnings +# Note: Can be deleted if you don't want to define your own warnings. +func _get_configuration_warnings() -> PackedStringArray: + var warnings: Array = [] + warnings.append_array(super._get_configuration_warnings()) + + var parent: Node = get_parent() + if not parent is FiniteStateMachine: + warnings.append("NestedFSM should be a child of a FiniteStateMachine node.") + + if not initial_state: + warnings.append("FSM needs an initial state") + return warnings # Return early if no initial state + + var fsm := _find_fsm() + if fsm and initial_state: + # check if initial_state is a descendant of this FSM + if not is_ancestor_of(initial_state): + warnings.append("Don't select initial state outside of this FSM") + + return warnings + +func _find_fsm() -> FiniteStateMachine: + var current: Node = self + while current: + if current is FiniteStateMachine: + return current as FiniteStateMachine + current = current.get_parent() + return null diff --git a/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd.uid b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd.uid new file mode 100644 index 0000000..fc1feb3 --- /dev/null +++ b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd.uid @@ -0,0 +1 @@ +uid://cffv6g18yifit diff --git a/addons/behaviour_toolkit/ui/toolkit_ui.gd b/addons/behaviour_toolkit/ui/toolkit_ui.gd index f832d42..1eef139 100644 --- a/addons/behaviour_toolkit/ui/toolkit_ui.gd +++ b/addons/behaviour_toolkit/ui/toolkit_ui.gd @@ -37,6 +37,7 @@ func _ready(): # Connect buttons %ButtonAddFSM.connect("pressed", _on_button_pressed.bind(FiniteStateMachine, "FiniteStateMachine")) %ButtonState.connect("pressed", _on_button_pressed.bind(FSMState, "FSMState")) + %ButtonNestedFSM.connect("pressed", _on_button_pressed.bind(NestedFSM, "NestedFSM")) %ButtonTransition.connect("pressed", _on_button_pressed.bind(FSMTransition, "FSMTransition")) %ButtonStateIntegratedBT.connect("pressed", _on_button_pressed.bind(FSMStateIntegratedBT, "FSMStateIntegratedBT")) %ButtonStateIntegrationReturn.connect("pressed", _on_button_pressed.bind(FSMStateIntegrationReturn, "FSMStateIntegrationReturn")) diff --git a/addons/behaviour_toolkit/ui/toolkit_ui.tscn b/addons/behaviour_toolkit/ui/toolkit_ui.tscn index 6ff618d..f391774 100644 --- a/addons/behaviour_toolkit/ui/toolkit_ui.tscn +++ b/addons/behaviour_toolkit/ui/toolkit_ui.tscn @@ -164,6 +164,12 @@ layout_mode = 2 text = "New State" icon = ExtResource("1_hqqj5") +[node name="ButtonNestedFSM" type="Button" parent="ScrollContainer/MarginContainer/VBoxContainer/Panel/ScrollContainer/Toolbox/FiniteStateMachine"] +unique_name_in_owner = true +layout_mode = 2 +text = "New NestedFSM" +icon = ExtResource("1_hqqj5") + [node name="ButtonTransition" type="Button" parent="ScrollContainer/MarginContainer/VBoxContainer/Panel/ScrollContainer/Toolbox/FiniteStateMachine"] unique_name_in_owner = true layout_mode = 2 diff --git a/docs/documentation.md b/docs/documentation.md index 3db1436..08c3ab6 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -23,36 +23,41 @@ The node icons where designed/choosen to give you a quick overview of their purp - [Finite State Machine](#finite-state-machine) - [Usage](#usage) - [Nodes](#nodes) - - [ FiniteStateMachine](#-finitestatemachine) - - [Properties](#properties) - - [Methods](#methods) - - [Signals](#signals) - - [ FSMState](#-fsmstate) - - [Methods](#methods-1) - - [ FSMTransition](#-fsmtransition) - - [Properties](#properties-1) - - [Methods](#methods-2) + - [ FiniteStateMachine](#-finitestatemachine) + - [Properties](#properties) + - [Methods](#methods) + - [Signals](#signals) + - [ FSMState](#-fsmstate) + - [Methods](#methods-1) + - [ NestedFSM](#-nestedfsm) + - [Properties](#properties-2) + - [Methods](#methods-2) + - [Signals](#signals-1) + - [ FSMTransition](#-fsmtransition) + - [Properties](#properties-3) + - [Methods](#methods-3) - [Behaviour Tree](#behaviour-tree) - [Usage](#usage-1) - [Tree Nodes](#tree-nodes) - - [ BTRoot](#-btroot) - - [Properties](#properties-2) - - [ BTComposite](#-btcomposite) - - [Properties](#properties-3) - - [ BTLeaf](#-btleaf) - - [ BTDecorator](#-btdecorator) - - [Status Enum](#status-enum) + - [ BTRoot](#-btroot) + - [Properties](#properties-4) + - [ BTComposite](#-btcomposite) + - [Properties](#properties-5) + - [ BTLeaf](#-btleaf) + - [ BTDecorator](#-btdecorator) + - [Status Enum](#status-enum) - [ Blackboard](#-blackboard) - [Creating a new Blackboard](#creating-a-new-blackboard) - [When to use a Blackboard](#when-to-use-a-blackboard) - - [Adding and retrieving data](#adding-and-retrieving-data) + - [Adding and retrieving data](#adding-and-retrieving-data) - [Nesting Behaviours inside Behaviours](#nesting-behaviours-inside-behaviours) - [State Machine nested in Behaviour Tree](#state-machine-nested-in-behaviour-tree) - [Behaviour Tree nested in State Machine](#behaviour-tree-nested-in-state-machine) - [Using Script Templates](#using-script-templates) - [Examples](#examples) - [Example: Busy villagers drinking, becoming ghosts and moving to random positions](#example-busy-villagers-drinking-becoming-ghosts-and-moving-to-random-positions) - - [What does a villager do?](#what-does-a-villager-do) + - [What does a villager do?](#what-does-a-villager-do) + @@ -73,29 +78,29 @@ This is the root node of the State Machine. All `FSMStates` must be children of #### Properties - bool `autostart` - - If `true` the FSM will start automatically when ready. + - If `true` the FSM will start automatically when ready. - Enum `process_type` - - When set to `Physics` the FSM _on_update() will be run in `_physics_process()` callback. - - When set to `Idle` the FSM _on_update() will be run in `_process()` callback. + - When set to `Physics` the FSM _on_update() will be run in `_physics_process()` callback. + - When set to `Idle` the FSM _on_update() will be run in `_process()` callback. - bool `active` - - When `true` the State Machine will run and update its current state. + - When `true` the State Machine will run and update its current state. - FSMState `initial_state` - - The state that is entereted when the State Machine starts. + - The state that is entereted when the State Machine starts. - Node `actor` - - The actor is the different states. Most of the time you want to use the root node of your current scene. + - The actor is the different states. Most of the time you want to use the root node of your current scene. - Blackboard `blackboard` - - When left empty, a new local blackboard will be created. Otherwise the given blackboard will be used, which can be shared between multiple State Machines and Behaviour Trees. + - When left empty, a new local blackboard will be created. Otherwise the given blackboard will be used, which can be shared between multiple State Machines and Behaviour Trees. #### Methods - void `start()` - - Starts the State Machine. This is called automatically when `autostart` is `true`. + - Starts the State Machine. This is called automatically when `autostart` is `true`. - void `fire_event(event: String)` - - Fires an event. This will trigger any transitions that are listening for this event. + - Fires an event. This will trigger any transitions that are listening for this event. #### Signals - `state_changed(state: FSMState)` - - Emitted when the current state changes. + - Emitted when the current state changes. ### ![FSM State Icon](../addons/behaviour_toolkit/icons/FSMState.svg) FSMState @@ -103,11 +108,33 @@ This is the base class for all states. On ready, all `FSMTransition` child nodes #### Methods - void `_on_enter(actor: Node, blackboard: Blackboard)` - - Called when the state is entered. + - Called when the state is entered. - void `_on_update(_delta: float, actor: Node, blackboard: Blackboard)` - - Called every frame while the state is active. + - Called every frame while the state is active. - void `_on_exit(actor: Node, blackboard: Blackboard)` - - Called when the state is exited. + - Called when the state is exited. + +### ![NestedFSM Icon](../addons/behaviour_toolkit/icons/FSMState.svg) NestedFSM +A `NestedFSM` is a special type of `FSMState` that contains its own `FiniteStateMachine`. +This allows you to build **hierarchical state machines**, useful for organizing complex behaviours. + +#### Properties +- FSMState `initial_state` + The state that will be entered when the NestedFSM starts. Must be a child of this node. +- bool `verbose` + If `true`, prints debug information about state changes to the Output panel. + +#### Methods +- void `_on_enter(actor: Node, blackboard: Blackboard)` + Starts the nested FSM. Override to trigger one-shot actions or events. +- void `_on_update(_delta: float, actor: Node, blackboard: Blackboard)` + Per-frame logic. Override for lightweight checks or firing events. +- void `_on_exit(actor: Node, blackboard: Blackboard)` + Stops the nested FSM. Override to run cleanup or exit actions. + +#### Signals +- `nested_state_changed(state: FSMState)` + Emitted whenever the nested FSM switches its current state. ### ![FSM Transition Icon](../addons/behaviour_toolkit/icons/FSMTransition.svg) FSMTransition @@ -115,18 +142,18 @@ This is the base class for all transitions. To implement your logic you can over #### Properties - FSMState `next_state` - - The state that is entered when the transition is triggered. + - The state that is entered when the transition is triggered. - bool `use_event` - - If `true` the transition will be triggered if the given event is fired. + - If `true` the transition will be triggered if the given event is fired. - String `event` - - The event that triggers the transition. + - The event that triggers the transition. #### Methods - void `_on_transition(actor: Node, blackboard: Blackboard)` - - Called when the transition is triggered. + - Called when the transition is triggered. - bool `is_valid` - - Should return `true` if the conditions for the transition are met. + - Should return `true` if the conditions for the transition are met. # Behaviour Tree @@ -146,18 +173,18 @@ This is the root of your behaviour tree. It is designed to only have one child n #### Properties - bool `autostart` - - If `true` the FSM will start automatically when ready. + - If `true` the FSM will start automatically when ready. - Enum `process_type` - - When set to `Physics` the BTree tick() will be run in `_physics_process()` callback. - - When set to `Idle` the BTree tick() will be run in `_process()` callback. + - When set to `Physics` the BTree tick() will be run in `_physics_process()` callback. + - When set to `Idle` the BTree tick() will be run in `_process()` callback. - bool `active` - - When `true` the State Machine will run and update its current state. + - When `true` the State Machine will run and update its current state. - FSMState `initial_state` - - The state that is entereted when the State Machine starts. + - The state that is entereted when the State Machine starts. - Node `actor` - - The actor is the different states. Most of the time you want to use the root node of your current scene. + - The actor is the different states. Most of the time you want to use the root node of your current scene. - Blackboard `blackboard` - - When left empty, a new local blackboard will be created. Otherwise the given blackboard will be used, which can be shared between multiple State Machines and Behaviour Trees. + - When left empty, a new local blackboard will be created. Otherwise the given blackboard will be used, which can be shared between multiple State Machines and Behaviour Trees. ### ![BTComposite Icon](../addons/behaviour_toolkit/icons/BTComposite.svg) BTComposite @@ -178,7 +205,7 @@ Composites nodes are used to combine multiple leaves into a single node and eval #### Properties - Array[BTLeaf] `leaves` - - The leaves that are the children of this node. + - The leaves that are the children of this node. ### ![BTLeaf Icon](../addons/behaviour_toolkit/icons/BTLeaf.svg) BTLeaf diff --git a/examples/fsm_character_controller/MrBlacksmith.gd b/examples/fsm_character_controller/MrBlacksmith.gd index 5614b99..edb1067 100644 --- a/examples/fsm_character_controller/MrBlacksmith.gd +++ b/examples/fsm_character_controller/MrBlacksmith.gd @@ -6,7 +6,7 @@ const SPRINT_MULTIPLIER := 1.7 var movement_direction := Vector2.ZERO - +var in_action := false @onready var state_machine := $FSMController @onready var sprite := $Sprite2D @@ -20,6 +20,5 @@ func _physics_process(_delta): Input.get_axis("ui_up", "ui_down") ) - func _ready(): - state_machine.start() \ No newline at end of file + state_machine.start() diff --git a/examples/fsm_character_controller/states/IDLE.gd b/examples/fsm_character_controller/states/IDLE.gd index 1febb22..d8c9ebc 100644 --- a/examples/fsm_character_controller/states/IDLE.gd +++ b/examples/fsm_character_controller/states/IDLE.gd @@ -17,4 +17,3 @@ func _on_update(_delta, _actor, _blackboard: Blackboard): # Executes before the state is exited. func _on_exit(_actor, _blackboard: Blackboard): pass - diff --git a/examples/fsm_character_controller/transitions/onStartMoving.gd b/examples/fsm_character_controller/transitions/onStartMoving.gd index 7bb9da4..3ee3c37 100644 --- a/examples/fsm_character_controller/transitions/onStartMoving.gd +++ b/examples/fsm_character_controller/transitions/onStartMoving.gd @@ -1,3 +1,4 @@ +@tool extends FSMTransition # Executed when the transition is taken. diff --git a/examples/fsm_character_controller/transitions/onStopSprinting.gd b/examples/fsm_character_controller/transitions/onStopSprinting.gd index 965e838..38d9f2b 100644 --- a/examples/fsm_character_controller/transitions/onStopSprinting.gd +++ b/examples/fsm_character_controller/transitions/onStopSprinting.gd @@ -11,4 +11,4 @@ func is_valid(_actor, _blackboard: Blackboard): return true else: return false - \ No newline at end of file + diff --git a/examples/nested_fsm_controller/Assets/Sword and shield.png b/examples/nested_fsm_controller/Assets/Sword and shield.png new file mode 100644 index 0000000..c0f385e Binary files /dev/null and b/examples/nested_fsm_controller/Assets/Sword and shield.png differ diff --git a/examples/nested_fsm_controller/Assets/Sword and shield.png.import b/examples/nested_fsm_controller/Assets/Sword and shield.png.import new file mode 100644 index 0000000..62950f1 --- /dev/null +++ b/examples/nested_fsm_controller/Assets/Sword and shield.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dctkyiumq3jky" +path="res://.godot/imported/Sword and shield.png-f61d4ac82ecd8ec8202ed6de14c4aa5e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/nested_fsm_controller/Assets/Sword and shield.png" +dest_files=["res://.godot/imported/Sword and shield.png-f61d4ac82ecd8ec8202ed6de14c4aa5e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/examples/nested_fsm_controller/NestedMrBlacksmith.gd b/examples/nested_fsm_controller/NestedMrBlacksmith.gd new file mode 100644 index 0000000..479a334 --- /dev/null +++ b/examples/nested_fsm_controller/NestedMrBlacksmith.gd @@ -0,0 +1,40 @@ +extends CharacterBody2D + + +const SPEED := 100.0 +const SPRINT_MULTIPLIER := 1.7 + + +var movement_direction := Vector2.ZERO +var in_action := false + +@onready var state_machine := $FSMController +@onready var sprite := $Character +@onready var animation_player := $AnimationPlayer +@onready var particles_walking := $ParticlesWalking + + +func _physics_process(_delta): + movement_direction = Vector2( + Input.get_axis("ui_left", "ui_right"), + Input.get_axis("ui_up", "ui_down") + ) + +func _input(event: InputEvent) -> void: + if event is InputEventMouseButton: + in_action = true if event.button_index == MOUSE_BUTTON_LEFT and event.pressed else false + +func _ready(): + state_machine.start() + +func _on_fsm_controller_state_changed(state: FSMState) -> void: + #print("Base FSM: ", state.name, " ", Time.get_ticks_msec()) + pass + +func _on_movement_fsm_nested_state_changed(state: FSMState) -> void: + #print("Movement FSM: ", state.name, " ", Time.get_ticks_msec()) + pass + +func _on_action_fsm_nested_state_changed(state: FSMState) -> void: + #print("Action FSM: ", state.name, " ", Time.get_ticks_msec()) + pass diff --git a/examples/nested_fsm_controller/NestedMrBlacksmith.gd.uid b/examples/nested_fsm_controller/NestedMrBlacksmith.gd.uid new file mode 100644 index 0000000..3258612 --- /dev/null +++ b/examples/nested_fsm_controller/NestedMrBlacksmith.gd.uid @@ -0,0 +1 @@ +uid://b06wtene10wks diff --git a/examples/nested_fsm_controller/States/ATTACKING.gd b/examples/nested_fsm_controller/States/ATTACKING.gd new file mode 100644 index 0000000..cd8ea6e --- /dev/null +++ b/examples/nested_fsm_controller/States/ATTACKING.gd @@ -0,0 +1,19 @@ +extends FSMState + + +# Executes after the state is entered. +func _on_enter(actor, _blackboard: Blackboard): + # Cast actor + actor = actor as CharacterBody2D + + actor.animation_player.play("attack") + + +# Executes every _process call, if the state is active. +func _on_update(_delta, _actor, _blackboard: Blackboard): + pass + + +# Executes before the state is exited. +func _on_exit(_actor, _blackboard: Blackboard): + pass diff --git a/examples/nested_fsm_controller/States/ATTACKING.gd.uid b/examples/nested_fsm_controller/States/ATTACKING.gd.uid new file mode 100644 index 0000000..fcbc604 --- /dev/null +++ b/examples/nested_fsm_controller/States/ATTACKING.gd.uid @@ -0,0 +1 @@ +uid://b356jcd5ffil7 diff --git a/examples/nested_fsm_controller/States/BLOCKING.gd b/examples/nested_fsm_controller/States/BLOCKING.gd new file mode 100644 index 0000000..aca598b --- /dev/null +++ b/examples/nested_fsm_controller/States/BLOCKING.gd @@ -0,0 +1,19 @@ +extends FSMState + + +# Executes after the state is entered. +func _on_enter(actor, _blackboard: Blackboard): + # Cast actor + actor = actor as CharacterBody2D + + actor.animation_player.play("block") + + +# Executes every _process call, if the state is active. +func _on_update(_delta, _actor, _blackboard: Blackboard): + pass + + +# Executes before the state is exited. +func _on_exit(_actor, _blackboard: Blackboard): + pass diff --git a/examples/nested_fsm_controller/States/BLOCKING.gd.uid b/examples/nested_fsm_controller/States/BLOCKING.gd.uid new file mode 100644 index 0000000..ebade9c --- /dev/null +++ b/examples/nested_fsm_controller/States/BLOCKING.gd.uid @@ -0,0 +1 @@ +uid://d4fr0ufg1dfy0 diff --git a/examples/nested_fsm_controller/Transitions/onActionFinished.gd b/examples/nested_fsm_controller/Transitions/onActionFinished.gd new file mode 100644 index 0000000..cebe50f --- /dev/null +++ b/examples/nested_fsm_controller/Transitions/onActionFinished.gd @@ -0,0 +1,15 @@ +extends FSMTransition + + +# Executed when the transition is taken. +func _on_transition(_delta: float, _actor: Node, _blackboard: Blackboard) -> void: + pass + + +# Evaluates true, if the transition conditions are met. +func is_valid(actor: Node, _blackboard: Blackboard) -> bool: + actor = actor as CharacterBody2D + + if actor.animation_player.animation_finished: + return true + return false diff --git a/examples/nested_fsm_controller/Transitions/onActionFinished.gd.uid b/examples/nested_fsm_controller/Transitions/onActionFinished.gd.uid new file mode 100644 index 0000000..4ad3b87 --- /dev/null +++ b/examples/nested_fsm_controller/Transitions/onActionFinished.gd.uid @@ -0,0 +1 @@ +uid://dtwcjneu7jpl6 diff --git a/examples/nested_fsm_controller/Transitions/onStartAction.gd b/examples/nested_fsm_controller/Transitions/onStartAction.gd new file mode 100644 index 0000000..cb3a20d --- /dev/null +++ b/examples/nested_fsm_controller/Transitions/onStartAction.gd @@ -0,0 +1,14 @@ +extends FSMTransition + + +# Executed when the transition is taken. +func _on_transition(_delta: float, _actor: Node, _blackboard: Blackboard) -> void: + pass + + +# Evaluates true, if the transition conditions are met. +func is_valid(actor: Node, _blackboard: Blackboard) -> bool: + if actor is CharacterBody2D and actor.in_action == true: + return true + + return false diff --git a/examples/nested_fsm_controller/Transitions/onStartAction.gd.uid b/examples/nested_fsm_controller/Transitions/onStartAction.gd.uid new file mode 100644 index 0000000..7f142df --- /dev/null +++ b/examples/nested_fsm_controller/Transitions/onStartAction.gd.uid @@ -0,0 +1 @@ +uid://vhmryxkn37mr diff --git a/examples/nested_fsm_controller/Transitions/onStopAction.gd b/examples/nested_fsm_controller/Transitions/onStopAction.gd new file mode 100644 index 0000000..ea26715 --- /dev/null +++ b/examples/nested_fsm_controller/Transitions/onStopAction.gd @@ -0,0 +1,27 @@ +@tool +extends FSMTransition + + +# Executed when the transition is taken. +func _on_transition(_delta: float, _actor: Node, _blackboard: Blackboard) -> void: + pass + + +# Evaluates true, if the transition conditions are met. +func is_valid(actor: Node, _blackboard: Blackboard) -> bool: + if actor is CharacterBody2D and not actor.animation_player.is_playing(): + return true + + return false + + +# Add custom configuration warnings +# Note: Can be deleted if you don't want to define your own warnings. +func _get_configuration_warnings() -> PackedStringArray: + var warnings: Array = [] + + warnings.append_array(super._get_configuration_warnings()) + + # Add your own warnings to the array here + + return warnings diff --git a/examples/nested_fsm_controller/Transitions/onStopAction.gd.uid b/examples/nested_fsm_controller/Transitions/onStopAction.gd.uid new file mode 100644 index 0000000..376f7ea --- /dev/null +++ b/examples/nested_fsm_controller/Transitions/onStopAction.gd.uid @@ -0,0 +1 @@ +uid://btncwf10wx110 diff --git a/examples/nested_fsm_controller/action_fsm.gd b/examples/nested_fsm_controller/action_fsm.gd new file mode 100644 index 0000000..b3d27ed --- /dev/null +++ b/examples/nested_fsm_controller/action_fsm.gd @@ -0,0 +1,29 @@ +@tool +extends NestedFSM + + +# Executes after the state is entered. +func _on_enter(_actor: Node, _blackboard: Blackboard) -> void: + super(_actor, _blackboard) + + +# Executes every _process call, if the state is active. +func _on_update(_delta: float, _actor: Node, _blackboard: Blackboard) -> void: + super(_delta, _actor, _blackboard) + + +# Executes before the state is exited. +func _on_exit(_actor: Node, _blackboard: Blackboard) -> void: + super(_actor, _blackboard) + + +# Add custom configuration warnings +# Note: Can be deleted if you don't want to define your own warnings. +func _get_configuration_warnings() -> PackedStringArray: + var warnings: Array = [] + + warnings.append_array(super._get_configuration_warnings()) + + # Add your own warnings to the array here + + return warnings diff --git a/examples/nested_fsm_controller/action_fsm.gd.uid b/examples/nested_fsm_controller/action_fsm.gd.uid new file mode 100644 index 0000000..20a0b4e --- /dev/null +++ b/examples/nested_fsm_controller/action_fsm.gd.uid @@ -0,0 +1 @@ +uid://dywybb2vhvccg diff --git a/examples/nested_fsm_controller/nested_fsm_character_controller.tscn b/examples/nested_fsm_controller/nested_fsm_character_controller.tscn new file mode 100644 index 0000000..9ba0a19 --- /dev/null +++ b/examples/nested_fsm_controller/nested_fsm_character_controller.tscn @@ -0,0 +1,42 @@ +[gd_scene load_steps=4 format=4 uid="uid://ccrn4134kb2ue"] + +[ext_resource type="TileSet" uid="uid://dtkgrk566xb21" path="res://examples/assets/tileset.tres" id="1_10fic"] +[ext_resource type="PackedScene" uid="uid://dcgl1ra6220lh" path="res://examples/nested_fsm_controller/nested_mr_blacksmith.tscn" id="2_cfmr3"] + +[sub_resource type="LabelSettings" id="LabelSettings_855cg"] + +[node name="NestedFSMCharacterController" type="Node2D"] +texture_filter = 1 + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer"] +offset_right = 40.0 +offset_bottom = 40.0 +theme_override_constants/margin_left = 7 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 7 +theme_override_constants/margin_bottom = 0 + +[node name="Label" type="Label" parent="CanvasLayer/MarginContainer"] +layout_mode = 2 +text = "Move with [WASD]. +Press [SHIFT] to sprint." +label_settings = SubResource("LabelSettings_855cg") + +[node name="Camera2D" type="Camera2D" parent="."] +zoom = Vector2(3, 3) + +[node name="TileMap" type="Node2D" parent="."] + +[node name="Layer0" type="TileMapLayer" parent="TileMap"] +use_parent_material = true +tile_map_data = PackedByteArray("AADz//j/AAAAAAAAAADz//n/AAAAAAAAAAD0//n/AAAAAAAAAAD1//r/AAAAAAAAAAD2//r/AAAAAAAAAAD2//v/AAAAAAAAAAD3//z/AAACAAAAAAD4//z/AAAAAAAAAAD5//3/AAAAAAAAAAD6//3/AAAAAAAAAAD7//7/AAAAAAAAAAD8////AAABAAEAAAD9////AAABAAEAAAD+/wAAAAABAAMAAAD//wAAAAABAAMAAAAAAAEAAAAAAAAAAAABAAEAAAAAAAAAAAACAAIAAAAAAAAAAAADAAIAAAAAAAAAAAAEAAMAAAAAAAAAAAAFAAMAAAAAAAAAAAAGAAMAAAAAAAAAAADz//r/AAAAAAAAAADz//v/AAAAAAAAAADz//z/AAAAAAAAAADz//3/AAAAAAAAAADz//7/AAAAAAAAAADz////AAAAAAEAAADz/wAAAAAAAAMAAADz/wEAAAAAAAAAAADz/wIAAAAAAAAAAADz/wMAAAAAAAAAAADz/wQAAAABAAAAAADz/wUAAAAAAAAAAADz/wYAAAAAAAAAAADz/wcAAAABAAAAAAD0//j/AAAAAAAAAAD0//r/AAAAAAAAAAD0//v/AAAAAAAAAAD0//z/AAAAAAAAAAD0//3/AAAAAAAAAAD0//7/AAAAAAAAAAD0////AAABAAEAAAD0/wAAAAABAAMAAAD0/wEAAAABAAAAAAD0/wIAAAAAAAAAAAD0/wMAAAAAAAAAAAD0/wQAAAAAAAAAAAD0/wUAAAAAAAAAAAD0/wYAAAAAAAAAAAD0/wcAAAABAAAAAAD1//j/AAAAAAAAAAD1//n/AAAAAAAAAAD1//v/AAAAAAAAAAD1//z/AAAAAAAAAAD1//3/AAACAAAAAAD1//7/AAAAAAAAAAD1////AAABAAEAAAD1/wAAAAABAAMAAAD1/wEAAAAAAAAAAAD1/wIAAAAAAAAAAAD1/wMAAAAAAAAAAAD1/wQAAAAAAAAAAAD1/wUAAAAAAAAAAAD1/wYAAAAAAAAAAAD1/wcAAAAAAAAAAAD2//j/AAAAAAAAAAD2//n/AAAAAAAAAAD2//z/AAAAAAAAAAD2//3/AAAAAAAAAAD2//7/AAAAAAAAAAD2////AAABAAEAAAD2/wAAAAABAAMAAAD2/wEAAAAAAAAAAAD2/wIAAAAAAAAAAAD2/wMAAAAAAAAAAAD2/wQAAAAAAAAAAAD2/wUAAAAAAAAAAAD2/wYAAAAAAAAAAAD2/wcAAAAAAAAAAAD3//j/AAAAAAAAAAD3//n/AAAAAAAAAAD3//r/AAAAAAAAAAD3//v/AAAAAAAAAAD3//3/AAAAAAAAAAD3//7/AAAAAAAAAAD3////AAABAAEAAAD3/wAAAAABAAMAAAD3/wEAAAAAAAAAAAD3/wIAAAAAAAAAAAD3/wMAAAAAAAAAAAD3/wQAAAAAAAAAAAD3/wUAAAAAAAAAAAD3/wYAAAAAAAAAAAD3/wcAAAAAAAAAAAD4//j/AAABAAAAAAD4//n/AAAAAAAAAAD4//r/AAAAAAAAAAD4//v/AAAAAAAAAAD4//3/AAAAAAAAAAD4//7/AAAAAAAAAAD4////AAABAAEAAAD4/wAAAAABAAMAAAD4/wEAAAAAAAAAAAD4/wIAAAAAAAAAAAD4/wMAAAAAAAAAAAD4/wQAAAACAAAAAAD4/wUAAAAAAAAAAAD4/wYAAAACAAAAAAD4/wcAAAACAAAAAAD5//j/AAAAAAAAAAD5//n/AAAAAAAAAAD5//r/AAAAAAAAAAD5//v/AAABAAAAAAD5//z/AAAAAAAAAAD5//7/AAABAAAAAAD5////AAABAAEAAAD5/wAAAAABAAMAAAD5/wEAAAAAAAAAAAD5/wIAAAAAAAAAAAD5/wMAAAAAAAAAAAD5/wQAAAAAAAAAAAD5/wUAAAAAAAAAAAD5/wYAAAAAAAAAAAD5/wcAAAAAAAAAAAD6//j/AAAAAAAAAAD6//n/AAAAAAAAAAD6//r/AAAAAAAAAAD6//v/AAAAAAAAAAD6//z/AAAAAAAAAAD6//7/AAAAAAAAAAD6////AAABAAEAAAD6/wAAAAABAAMAAAD6/wEAAAAAAAAAAAD6/wIAAAAAAAAAAAD6/wMAAAAAAAAAAAD6/wQAAAAAAAAAAAD6/wUAAAAAAAAAAAD6/wYAAAABAAAAAAD6/wcAAAABAAAAAAD7//j/AAAAAAAAAAD7//n/AAACAAAAAAD7//r/AAAAAAAAAAD7//v/AAACAAAAAAD7//z/AAAAAAAAAAD7//3/AAAAAAAAAAD7////AAABAAEAAAD7/wAAAAABAAMAAAD7/wEAAAAAAAAAAAD7/wIAAAAAAAAAAAD7/wMAAAAAAAAAAAD7/wQAAAAAAAAAAAD7/wUAAAAAAAAAAAD7/wYAAAAAAAAAAAD7/wcAAAAAAAAAAAD8//j/AAAAAAAAAAD8//n/AAAAAAAAAAD8//r/AAAAAAAAAAD8//v/AAACAAAAAAD8//z/AAAAAAAAAAD8//3/AAAAAAAAAAD8//7/AAAAAAAAAAD8/wAAAAABAAMAAAD8/wEAAAAAAAAAAAD8/wIAAAAAAAAAAAD8/wMAAAAAAAAAAAD8/wQAAAAAAAAAAAD8/wUAAAAAAAAAAAD8/wYAAAAAAAAAAAD8/wcAAAAAAAAAAAD9//j/AAAAAAAAAAD9//n/AAAAAAAAAAD9//r/AAAAAAAAAAD9//v/AAAAAAAAAAD9//z/AAAAAAAAAAD9//3/AAAAAAAAAAD9//7/AAAAAAAAAAD9/wAAAAABAAMAAAD9/wEAAAAAAAAAAAD9/wIAAAAAAAAAAAD9/wMAAAAAAAAAAAD9/wQAAAAAAAAAAAD9/wUAAAAAAAAAAAD9/wYAAAAAAAAAAAD9/wcAAAAAAAAAAAD+//j/AAAAAAAAAAD+//n/AAAAAAAAAAD+//r/AAAAAAAAAAD+//v/AAAAAAAAAAD+//z/AAAAAAAAAAD+//3/AAAAAAAAAAD+//7/AAAAAAAAAAD+////AAABAAEAAAD+/wEAAAABAAAAAAD+/wIAAAAAAAAAAAD+/wMAAAAAAAAAAAD+/wQAAAAAAAAAAAD+/wUAAAAAAAAAAAD+/wYAAAAAAAAAAAD+/wcAAAAAAAAAAAD///j/AAAAAAAAAAD///n/AAAAAAAAAAD///r/AAABAAAAAAD///v/AAAAAAAAAAD///z/AAAAAAAAAAD///3/AAACAAAAAAD///7/AAACAAAAAAD/////AAABAAEAAAD//wEAAAAAAAAAAAD//wIAAAAAAAAAAAD//wMAAAAAAAAAAAD//wQAAAAAAAAAAAD//wUAAAAAAAAAAAD//wYAAAAAAAAAAAD//wcAAAAAAAAAAAAAAPj/AAAAAAAAAAAAAPn/AAAAAAAAAAAAAPr/AAAAAAAAAAAAAPv/AAAAAAAAAAAAAPz/AAAAAAAAAAAAAP3/AAAAAAAAAAAAAP7/AAAAAAAAAAAAAP//AAABAAEAAAAAAAAAAAABAAMAAAAAAAIAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAcAAAAAAAAAAAABAPj/AAAAAAAAAAABAPn/AAAAAAAAAAABAPr/AAAAAAAAAAABAPv/AAAAAAAAAAABAPz/AAAAAAAAAAABAP3/AAAAAAAAAAABAP7/AAAAAAAAAAABAP//AAABAAEAAAABAAAAAAABAAMAAAABAAIAAAAAAAAAAAABAAMAAAAAAAAAAAABAAQAAAAAAAAAAAABAAUAAAAAAAAAAAABAAYAAAAAAAAAAAABAAcAAAAAAAAAAAACAPj/AAAAAAAAAAACAPn/AAAAAAAAAAACAPr/AAACAAAAAAACAPv/AAACAAAAAAACAPz/AAAAAAAAAAACAP3/AAAAAAAAAAACAP7/AAACAAAAAAACAP//AAABAAEAAAACAAAAAAABAAMAAAACAAEAAAAAAAAAAAACAAMAAAACAAAAAAACAAQAAAAAAAAAAAACAAUAAAABAAAAAAACAAYAAAAAAAAAAAACAAcAAAAAAAAAAAADAPj/AAAAAAAAAAADAPn/AAAAAAAAAAADAPr/AAAAAAAAAAADAPv/AAAAAAAAAAADAPz/AAAAAAAAAAADAP3/AAAAAAAAAAADAP7/AAABAAAAAAADAP//AAABAAEAAAADAAAAAAABAAMAAAADAAEAAAAAAAAAAAADAAMAAAAAAAAAAAADAAQAAAAAAAAAAAADAAUAAAAAAAAAAAADAAYAAAAAAAAAAAADAAcAAAAAAAAAAAAEAPj/AAAAAAAAAAAEAPn/AAAAAAAAAAAEAPr/AAAAAAAAAAAEAPv/AAAAAAAAAAAEAPz/AAAAAAAAAAAEAP3/AAAAAAAAAAAEAP7/AAAAAAAAAAAEAP//AAABAAEAAAAEAAAAAAABAAMAAAAEAAEAAAAAAAAAAAAEAAIAAAABAAAAAAAEAAQAAAAAAAAAAAAEAAUAAAAAAAAAAAAEAAYAAAAAAAAAAAAEAAcAAAAAAAAAAAAFAPj/AAAAAAAAAAAFAPn/AAAAAAAAAAAFAPr/AAAAAAAAAAAFAPv/AAAAAAAAAAAFAPz/AAAAAAAAAAAFAP3/AAAAAAAAAAAFAP7/AAAAAAAAAAAFAP//AAABAAEAAAAFAAAAAAABAAMAAAAFAAEAAAAAAAAAAAAFAAIAAAACAAAAAAAFAAQAAAAAAAAAAAAFAAUAAAAAAAAAAAAFAAYAAAAAAAAAAAAFAAcAAAAAAAAAAAAGAPj/AAAAAAAAAAAGAPn/AAAAAAAAAAAGAPr/AAAAAAAAAAAGAPv/AAAAAAAAAAAGAPz/AAACAAAAAAAGAP3/AAAAAAAAAAAGAP7/AAAAAAAAAAAGAP//AAABAAEAAAAGAAAAAAABAAMAAAAGAAEAAAAAAAAAAAAGAAIAAAAAAAAAAAAGAAQAAAAAAAAAAAAGAAUAAAAAAAAAAAAGAAYAAAAAAAAAAAAGAAcAAAAAAAAAAAAHAPj/AAAAAAAAAAAHAPn/AAAAAAAAAAAHAPr/AAAAAAAAAAAHAPv/AAAAAAAAAAAHAPz/AAAAAAAAAAAHAP3/AAAAAAAAAAAHAP7/AAAAAAAAAAAHAP//AAABAAEAAAAHAAAAAAABAAMAAAAHAAEAAAAAAAAAAAAHAAIAAAAAAAAAAAAHAAMAAAAAAAAAAAAHAAQAAAAAAAAAAAAHAAUAAAAAAAAAAAAHAAYAAAAAAAAAAAAHAAcAAAAAAAAAAAAIAPj/AAAAAAAAAAAIAPn/AAAAAAAAAAAIAPr/AAAAAAAAAAAIAPv/AAAAAAAAAAAIAPz/AAAAAAAAAAAIAP3/AAAAAAAAAAAIAP7/AAAAAAAAAAAIAP//AAABAAEAAAAIAAAAAAABAAMAAAAIAAEAAAAAAAAAAAAIAAIAAAAAAAAAAAAIAAMAAAAAAAAAAAAIAAQAAAAAAAAAAAAIAAUAAAAAAAAAAAAIAAYAAAAAAAAAAAAIAAcAAAAAAAAAAAAJAPj/AAAAAAAAAAAJAPn/AAAAAAAAAAAJAPr/AAAAAAAAAAAJAPv/AAAAAAAAAAAJAPz/AAAAAAAAAAAJAP3/AAAAAAAAAAAJAP7/AAAAAAAAAAAJAP//AAABAAEAAAAJAAAAAAABAAMAAAAJAAEAAAAAAAAAAAAJAAIAAAAAAAAAAAAJAAMAAAACAAAAAAAJAAQAAAAAAAAAAAAJAAUAAAACAAAAAAAJAAYAAAAAAAAAAAAJAAcAAAAAAAAAAAAKAPj/AAAAAAAAAAAKAPn/AAAAAAAAAAAKAPr/AAAAAAAAAAAKAPv/AAAAAAAAAAAKAPz/AAAAAAAAAAAKAP3/AAAAAAAAAAAKAP7/AAAAAAAAAAAKAP//AAABAAEAAAAKAAAAAAABAAMAAAAKAAEAAAAAAAAAAAAKAAIAAAAAAAAAAAAKAAMAAAAAAAAAAAAKAAQAAAAAAAAAAAAKAAUAAAAAAAAAAAAKAAYAAAAAAAAAAAAKAAcAAAAAAAAAAAALAPj/AAAAAAAAAAALAPn/AAAAAAAAAAALAPr/AAAAAAAAAAALAPv/AAAAAAAAAAALAPz/AAAAAAAAAAALAP3/AAAAAAAAAAALAP7/AAAAAAAAAAALAP//AAABAAEAAAALAAAAAAABAAMAAAALAAEAAAABAAAAAAALAAIAAAAAAAAAAAALAAMAAAAAAAAAAAALAAQAAAAAAAAAAAALAAUAAAAAAAAAAAALAAYAAAAAAAAAAAALAAcAAAAAAAAAAAAMAPj/AAACAAAAAAAMAPn/AAAAAAAAAAAMAPr/AAAAAAAAAAAMAPv/AAAAAAAAAAAMAPz/AAAAAAAAAAAMAP3/AAACAAAAAAAMAP7/AAAAAAAAAAAMAP//AAACAAEAAAAMAAAAAAACAAMAAAAMAAEAAAACAAAAAAAMAAIAAAAAAAAAAAAMAAMAAAAAAAAAAAAMAAQAAAAAAAAAAAAMAAUAAAAAAAAAAAAMAAYAAAAAAAAAAAAMAAcAAAAAAAAAAAA=") +tile_set = ExtResource("1_10fic") + +[node name="Layer1" type="TileMapLayer" parent="TileMap"] +use_parent_material = true +tile_map_data = PackedByteArray("AAD9//z/AAAIAAUAAAD9//v/AAAIAAQAAAD9//r/AAALAAQAAAD9//n/AAAKAAQAAAD9//j/AAAIAAMAAAADAPz/AAAKAAUAAAACAPz/AAAJAAMAAAABAPz/AAAJAAMAAAAAAPz/AAAJAAMAAAD///z/AAAJAAMAAAD+//z/AAAJAAMAAAADAPv/AAALAAQAAAADAPr/AAAKAAQAAAADAPn/AAALAAQAAAADAPj/AAAKAAMAAAD+//j/AAAJAAMAAAD///j/AAAJAAMAAAAAAPj/AAAJAAMAAAABAPj/AAAJAAMAAAACAPj/AAAJAAMAAAA=") +tile_set = ExtResource("1_10fic") + +[node name="NESTEDMrBlacksmith" parent="." instance=ExtResource("2_cfmr3")] diff --git a/examples/nested_fsm_controller/nested_mr_blacksmith.tscn b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn new file mode 100644 index 0000000..bf241a7 --- /dev/null +++ b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn @@ -0,0 +1,335 @@ +[gd_scene load_steps=28 format=3 uid="uid://dcgl1ra6220lh"] + +[ext_resource type="Script" uid="uid://b06wtene10wks" path="res://examples/nested_fsm_controller/NestedMrBlacksmith.gd" id="1_aurnf"] +[ext_resource type="Texture2D" uid="uid://cp4n4mks4l6t4" path="res://examples/assets/actors/tiny_blacksmith.png" id="2_fpsu8"] +[ext_resource type="Script" uid="uid://b0bqn8gtyevqu" path="res://addons/behaviour_toolkit/finite_state_machine/fsm.gd" id="3_r6iyk"] +[ext_resource type="Script" uid="uid://b8cyip4l40jgx" path="res://examples/fsm_character_controller/states/IDLE.gd" id="4_7vpcd"] +[ext_resource type="Script" uid="uid://cffv6g18yifit" path="res://addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd" id="4_s70fg"] +[ext_resource type="Script" uid="uid://clqh1515due6x" path="res://examples/fsm_character_controller/transitions/onStartMoving.gd" id="5_vlchv"] +[ext_resource type="Script" uid="uid://cvjqfgfhu2yue" path="res://examples/fsm_character_controller/states/MOVING.gd" id="6_cdhgq"] +[ext_resource type="Script" uid="uid://djobir3d8k83r" path="res://examples/fsm_character_controller/transitions/onStopMoving.gd" id="7_ijy0l"] +[ext_resource type="Script" uid="uid://db4n8gh1ye50c" path="res://examples/fsm_character_controller/transitions/onStartSprinting.gd" id="8_v24wg"] +[ext_resource type="Script" uid="uid://c2yda53e16fwn" path="res://examples/fsm_character_controller/states/SPRINTING.gd" id="9_uambq"] +[ext_resource type="Script" uid="uid://beyib2ryg4dja" path="res://examples/fsm_character_controller/transitions/onStopSprinting.gd" id="10_bw01j"] +[ext_resource type="Script" uid="uid://vhmryxkn37mr" path="res://examples/nested_fsm_controller/Transitions/onStartAction.gd" id="12_cdhgq"] +[ext_resource type="Texture2D" uid="uid://dctkyiumq3jky" path="res://examples/nested_fsm_controller/Assets/Sword and shield.png" id="13_7vpcd"] +[ext_resource type="Script" uid="uid://b356jcd5ffil7" path="res://examples/nested_fsm_controller/States/ATTACKING.gd" id="13_ijy0l"] +[ext_resource type="Script" uid="uid://dywybb2vhvccg" path="res://examples/nested_fsm_controller/action_fsm.gd" id="13_sv7u0"] +[ext_resource type="Script" uid="uid://d4fr0ufg1dfy0" path="res://examples/nested_fsm_controller/States/BLOCKING.gd" id="14_v24wg"] +[ext_resource type="Script" uid="uid://btncwf10wx110" path="res://examples/nested_fsm_controller/Transitions/onStopAction.gd" id="15_ijy0l"] + +[sub_resource type="Animation" id="Animation_c3jgb"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Character:rotation") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [0.0] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("Weapons/Sword:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(20, 5)] +} +tracks/2/type = "value" +tracks/2/imported = false +tracks/2/enabled = true +tracks/2/path = NodePath("Weapons/Sword:rotation") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [0.0] +} +tracks/3/type = "value" +tracks/3/imported = false +tracks/3/enabled = true +tracks/3/path = NodePath("Weapons/Sword:visible") +tracks/3/interp = 1 +tracks/3/loop_wrap = true +tracks/3/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [false] +} +tracks/4/type = "value" +tracks/4/imported = false +tracks/4/enabled = true +tracks/4/path = NodePath("Weapons/Shelid:position") +tracks/4/interp = 1 +tracks/4/loop_wrap = true +tracks/4/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(0, 4)] +} +tracks/5/type = "value" +tracks/5/imported = false +tracks/5/enabled = true +tracks/5/path = NodePath("Weapons/Shelid:visible") +tracks/5/interp = 1 +tracks/5/loop_wrap = true +tracks/5/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [false] +} + +[sub_resource type="Animation" id="Animation_7vpcd"] +resource_name = "attack" +length = 0.3 +step = 0.1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Weapons/Sword:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.3), +"transitions": PackedFloat32Array(1, 1), +"update": 0, +"values": [Vector2(7, 5), Vector2(7, 5)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("Weapons/Sword:rotation") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 0.3), +"transitions": PackedFloat32Array(1, 1), +"update": 0, +"values": [-1.55809, -0.0184624] +} +tracks/2/type = "value" +tracks/2/imported = false +tracks/2/enabled = true +tracks/2/path = NodePath("Weapons/Sword:visible") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/keys = { +"times": PackedFloat32Array(0, 0.3), +"transitions": PackedFloat32Array(1, 1), +"update": 1, +"values": [true, false] +} + +[sub_resource type="Animation" id="Animation_vlchv"] +resource_name = "block" +length = 0.3 +step = 0.1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Weapons/Shelid:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(0, 4)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("Weapons/Shelid:visible") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 0.3), +"transitions": PackedFloat32Array(1, 1), +"update": 1, +"values": [true, false] +} + +[sub_resource type="Animation" id="Animation_n1c40"] +resource_name = "idle" +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Character:rotation") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [0.0] +} + +[sub_resource type="Animation" id="Animation_0xjqu"] +resource_name = "walking" +length = 0.5 +loop_mode = 1 +step = 0.05 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Character:rotation") +tracks/0/interp = 2 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.1, 0.25, 0.35), +"transitions": PackedFloat32Array(1, 1, 1, 1), +"update": 0, +"values": [0.0, -0.174533, 0.0, 0.174533] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_t57sx"] +_data = { +&"RESET": SubResource("Animation_c3jgb"), +&"attack": SubResource("Animation_7vpcd"), +&"block": SubResource("Animation_vlchv"), +&"idle": SubResource("Animation_n1c40"), +&"walking": SubResource("Animation_0xjqu") +} + +[sub_resource type="Curve" id="Curve_fp4cc"] +_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.268657, 1), 1.4, 0.0, 0, 0, Vector2(0.675373, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] +point_count = 4 + +[sub_resource type="CurveTexture" id="CurveTexture_42v6a"] +curve = SubResource("Curve_fp4cc") + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_yrg32"] +particle_flag_disable_z = true +emission_shape_scale = Vector3(2, 2, 2) +emission_shape = 1 +emission_sphere_radius = 1.0 +gravity = Vector3(0, 0, 0) +scale_max = 3.0 +scale_curve = SubResource("CurveTexture_42v6a") +turbulence_enabled = true + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_gq6i5"] +radius = 3.125 +height = 11.13 + +[node name="NESTEDMrBlacksmith" type="CharacterBody2D"] +script = ExtResource("1_aurnf") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +&"": SubResource("AnimationLibrary_t57sx") +} + +[node name="ParticlesWalking" type="GPUParticles2D" parent="."] +position = Vector2(0, 7.07) +emitting = false +lifetime = 0.5 +process_material = SubResource("ParticleProcessMaterial_yrg32") + +[node name="Character" type="Sprite2D" parent="."] +texture_filter = 1 +texture = ExtResource("2_fpsu8") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(0, 4.735) +rotation = -1.5708 +shape = SubResource("CapsuleShape2D_gq6i5") + +[node name="FSMController" type="Node" parent="." node_paths=PackedStringArray("initial_state", "actor")] +script = ExtResource("3_r6iyk") +active = false +initial_state = NodePath("MovementFSM") +actor = NodePath("..") + +[node name="MovementFSM" type="Node" parent="FSMController" node_paths=PackedStringArray("initial_state")] +script = ExtResource("4_s70fg") +initial_state = NodePath("IDLE") +metadata/_custom_type_script = "uid://cffv6g18yifit" + +[node name="IDLE" type="Node" parent="FSMController/MovementFSM"] +script = ExtResource("4_7vpcd") + +[node name="onStartMoving" type="Node" parent="FSMController/MovementFSM/IDLE" node_paths=PackedStringArray("next_state")] +script = ExtResource("5_vlchv") +next_state = NodePath("..") + +[node name="MOVING" type="Node" parent="FSMController/MovementFSM"] +script = ExtResource("6_cdhgq") + +[node name="onStopMoving" type="Node" parent="FSMController/MovementFSM/MOVING" node_paths=PackedStringArray("next_state")] +script = ExtResource("7_ijy0l") +next_state = NodePath("../../IDLE") + +[node name="onStartSprinting" type="Node" parent="FSMController/MovementFSM/MOVING" node_paths=PackedStringArray("next_state")] +script = ExtResource("8_v24wg") +next_state = NodePath("../../SPRINTING") + +[node name="SPRINTING" type="Node" parent="FSMController/MovementFSM"] +script = ExtResource("9_uambq") + +[node name="onStopSprinting" type="Node" parent="FSMController/MovementFSM/SPRINTING" node_paths=PackedStringArray("next_state")] +script = ExtResource("10_bw01j") +next_state = NodePath("../../MOVING") + +[node name="onStartAction" type="Node" parent="FSMController/MovementFSM" node_paths=PackedStringArray("next_state")] +script = ExtResource("12_cdhgq") +next_state = NodePath("../../ActionFSM") +metadata/_custom_type_script = "uid://1h0braq41vwb" + +[node name="ActionFSM" type="Node" parent="FSMController" node_paths=PackedStringArray("initial_state")] +script = ExtResource("13_sv7u0") +initial_state = NodePath("ATTACKING") +metadata/_custom_type_script = "uid://cffv6g18yifit" + +[node name="ATTACKING" type="Node" parent="FSMController/ActionFSM"] +script = ExtResource("13_ijy0l") +metadata/_custom_type_script = "uid://k5p6yghm7nrm" + +[node name="BLOCKING" type="Node" parent="FSMController/ActionFSM"] +script = ExtResource("14_v24wg") +metadata/_custom_type_script = "uid://k5p6yghm7nrm" + +[node name="onStopAction" type="Node" parent="FSMController/ActionFSM" node_paths=PackedStringArray("next_state")] +script = ExtResource("15_ijy0l") +next_state = NodePath("../../MovementFSM") +metadata/_custom_type_script = "uid://1h0braq41vwb" + +[node name="Weapons" type="Node2D" parent="."] + +[node name="Sword" type="Sprite2D" parent="Weapons"] +visible = false +texture_filter = 1 +position = Vector2(20, 5) +scale = Vector2(0.75, 0.75) +texture = ExtResource("13_7vpcd") +offset = Vector2(16, 0) +flip_h = true +region_enabled = true +region_rect = Rect2(0, 0, 32, 8.11737) + +[node name="Shelid" type="Sprite2D" parent="Weapons"] +visible = false +texture_filter = 1 +position = Vector2(0, 4) +texture = ExtResource("13_7vpcd") +flip_h = true +region_enabled = true +region_rect = Rect2(23, 16, 8, 8) + +[connection signal="state_changed" from="FSMController" to="." method="_on_fsm_controller_state_changed"] +[connection signal="nested_state_changed" from="FSMController/MovementFSM" to="." method="_on_movement_fsm_nested_state_changed"] +[connection signal="nested_state_changed" from="FSMController/ActionFSM" to="." method="_on_action_fsm_nested_state_changed"] diff --git a/project.godot b/project.godot index 0ca13ec..5968244 100644 --- a/project.godot +++ b/project.godot @@ -86,4 +86,5 @@ action_sprint={ [rendering] +textures/canvas_textures/default_texture_filter=0 anti_aliasing/quality/msaa_2d=3 diff --git a/script_templates/NestedFSM/new_nested_fsm.gd b/script_templates/NestedFSM/new_nested_fsm.gd new file mode 100644 index 0000000..b3d27ed --- /dev/null +++ b/script_templates/NestedFSM/new_nested_fsm.gd @@ -0,0 +1,29 @@ +@tool +extends NestedFSM + + +# Executes after the state is entered. +func _on_enter(_actor: Node, _blackboard: Blackboard) -> void: + super(_actor, _blackboard) + + +# Executes every _process call, if the state is active. +func _on_update(_delta: float, _actor: Node, _blackboard: Blackboard) -> void: + super(_delta, _actor, _blackboard) + + +# Executes before the state is exited. +func _on_exit(_actor: Node, _blackboard: Blackboard) -> void: + super(_actor, _blackboard) + + +# Add custom configuration warnings +# Note: Can be deleted if you don't want to define your own warnings. +func _get_configuration_warnings() -> PackedStringArray: + var warnings: Array = [] + + warnings.append_array(super._get_configuration_warnings()) + + # Add your own warnings to the array here + + return warnings diff --git a/script_templates/NestedFSM/new_nested_fsm.gd.uid b/script_templates/NestedFSM/new_nested_fsm.gd.uid new file mode 100644 index 0000000..396545e --- /dev/null +++ b/script_templates/NestedFSM/new_nested_fsm.gd.uid @@ -0,0 +1 @@ +uid://bfpw7h23l3vqd