From 2a07dc59300b911f5a19c702a7f8d8067933cc43 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Fri, 22 Aug 2025 13:23:42 -0500 Subject: [PATCH 01/10] prototype complete --- .../finite_state_machine/fsm.gd | 8 +- .../finite_state_machine/nested_fsm.gd | 67 ++++ .../finite_state_machine/nested_fsm.gd.uid | 1 + .../fsm_character_controller/MrBlacksmith.gd | 11 +- .../fsm_character_controller/states/IDLE.gd | 1 - examples/nested_fsm/ACTION_FINISHED.gd | 15 + examples/nested_fsm/ACTION_FINISHED.gd.uid | 1 + .../nested_fsm/Assets/Sword and shield.png | Bin 0 -> 551 bytes .../Assets/Sword and shield.png.import | 34 ++ examples/nested_fsm/States/ATTACKING.gd | 19 ++ examples/nested_fsm/States/ATTACKING.gd.uid | 1 + examples/nested_fsm/States/BLOCKING.gd | 19 ++ examples/nested_fsm/States/BLOCKING.gd.uid | 1 + .../nested_fsm_character_controller.tscn | 42 +++ examples/nested_fsm/nested_mr_blacksmith.tscn | 304 ++++++++++++++++++ project.godot | 1 + 16 files changed, 520 insertions(+), 5 deletions(-) create mode 100644 addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd create mode 100644 addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd.uid create mode 100644 examples/nested_fsm/ACTION_FINISHED.gd create mode 100644 examples/nested_fsm/ACTION_FINISHED.gd.uid create mode 100644 examples/nested_fsm/Assets/Sword and shield.png create mode 100644 examples/nested_fsm/Assets/Sword and shield.png.import create mode 100644 examples/nested_fsm/States/ATTACKING.gd create mode 100644 examples/nested_fsm/States/ATTACKING.gd.uid create mode 100644 examples/nested_fsm/States/BLOCKING.gd create mode 100644 examples/nested_fsm/States/BLOCKING.gd.uid create mode 100644 examples/nested_fsm/nested_fsm_character_controller.tscn create mode 100644 examples/nested_fsm/nested_mr_blacksmith.tscn diff --git a/addons/behaviour_toolkit/finite_state_machine/fsm.gd b/addons/behaviour_toolkit/finite_state_machine/fsm.gd index 722adf7..2942314 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 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..2081ce8 --- /dev/null +++ b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd @@ -0,0 +1,67 @@ +@tool +extends FSMState +class_name NestedFSM + +#TODO: Test NESTED MR BLACKSMITH + +## The signal emitted when the fsm's state changes. +signal nested_state_changed(state: FSMState) + +@export var initial_state : FSMState +@export var verbose : bool + +@onready var fsm = FiniteStateMachine.new() + +func _ready() -> void: + super() + + if Engine.is_editor_hint(): + return + + + var nested_states : Array[Node] = get_children().filter(func(n): return n is FSMState) + var nested_fsms : Array[Node] = get_children().filter(func(n): return n is NestedFSM) + + add_child(fsm) + fsm.name = "NestedFSM" + + for state in nested_states: + state.reparent(fsm) + + for nested_fsm in nested_fsms: + nested_fsm.reparent(fsm) + + fsm.initial_state = initial_state + fsm.verbose = verbose + + var parent_state_machine : FiniteStateMachine = get_parent() + fsm.actor = parent_state_machine.actor + fsm.process_type = parent_state_machine.process_type + fsm.blackboard = parent_state_machine.blackboard + + 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 + + +# 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 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/examples/fsm_character_controller/MrBlacksmith.gd b/examples/fsm_character_controller/MrBlacksmith.gd index 5614b99..733ee71 100644 --- a/examples/fsm_character_controller/MrBlacksmith.gd +++ b/examples/fsm_character_controller/MrBlacksmith.gd @@ -9,7 +9,7 @@ var movement_direction := Vector2.ZERO @onready var state_machine := $FSMController -@onready var sprite := $Sprite2D +@onready var sprite := $Character @onready var animation_player := $AnimationPlayer @onready var particles_walking := $ParticlesWalking @@ -22,4 +22,11 @@ func _physics_process(_delta): func _ready(): - state_machine.start() \ No newline at end of file + state_machine.start() + +func _on_fsm_controller_state_changed(state: FSMState) -> void: + print(state.name) + + +func _on_movement_fsm_nested_state_changed(state: FSMState) -> void: + print(state.name) 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/nested_fsm/ACTION_FINISHED.gd b/examples/nested_fsm/ACTION_FINISHED.gd new file mode 100644 index 0000000..cebe50f --- /dev/null +++ b/examples/nested_fsm/ACTION_FINISHED.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/ACTION_FINISHED.gd.uid b/examples/nested_fsm/ACTION_FINISHED.gd.uid new file mode 100644 index 0000000..4ad3b87 --- /dev/null +++ b/examples/nested_fsm/ACTION_FINISHED.gd.uid @@ -0,0 +1 @@ +uid://dtwcjneu7jpl6 diff --git a/examples/nested_fsm/Assets/Sword and shield.png b/examples/nested_fsm/Assets/Sword and shield.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f385e59226e49c65e69d17e1596271dd562ea4 GIT binary patch literal 551 zcmV+?0@(eDP)Px$;Ymb6R9J=8Rv}CSF%*55TuA0FxDy!8pkQW}1Ty48hQy%e$)*;9fhDLBJcgNB zvQ3U`35o=>O<@j$IJcaeEMohmcWZBLxEte{yY{!g@3;T`7FYvZ$78PJF`3O)8PY_v zv<(2L^}vGAw;}`p;O_B0TL#?*{}lrcLf^8eGfNU%3j(jBS){-7HJk515c*cVQf1HY zFPRYd?5b@x{RE+Jl?%npNC5MQMFgR5+3}8Yp_qD7QWDY{Y5QM^BpLoM{5BHVe47k9 z4o-J+0DumnT9z%O^3g19qt*kXxwc6Sc)PjcP6)E<1|$@>ZX*(i4FG%jeLVtNqmt`N zye9BUsNxcIlWGBNeVoK# zIOZh8#eqc*$k?l{<)^a}0N^m669`NZKXb7iKZbt0^^*glLEK8!wM<7qT8__??LZQY zGhQbNMHNt+c0r6jGst#eBa2Yx(23gUJzxr1%@ASy9xw&fj7f7B*AD@1FWt!3_&s1u pKx2&2d%zU3_k)PF$Z_MP_YLj%9ZszwD%k)4002ovPDHLkV1mO(?_vM| literal 0 HcmV?d00001 diff --git a/examples/nested_fsm/Assets/Sword and shield.png.import b/examples/nested_fsm/Assets/Sword and shield.png.import new file mode 100644 index 0000000..10e41d7 --- /dev/null +++ b/examples/nested_fsm/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-c57e132e6fad8a4a88557cfd4c8a9177.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/nested_fsm/Assets/Sword and shield.png" +dest_files=["res://.godot/imported/Sword and shield.png-c57e132e6fad8a4a88557cfd4c8a9177.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/States/ATTACKING.gd b/examples/nested_fsm/States/ATTACKING.gd new file mode 100644 index 0000000..cd8ea6e --- /dev/null +++ b/examples/nested_fsm/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/States/ATTACKING.gd.uid b/examples/nested_fsm/States/ATTACKING.gd.uid new file mode 100644 index 0000000..fcbc604 --- /dev/null +++ b/examples/nested_fsm/States/ATTACKING.gd.uid @@ -0,0 +1 @@ +uid://b356jcd5ffil7 diff --git a/examples/nested_fsm/States/BLOCKING.gd b/examples/nested_fsm/States/BLOCKING.gd new file mode 100644 index 0000000..aca598b --- /dev/null +++ b/examples/nested_fsm/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/States/BLOCKING.gd.uid b/examples/nested_fsm/States/BLOCKING.gd.uid new file mode 100644 index 0000000..ebade9c --- /dev/null +++ b/examples/nested_fsm/States/BLOCKING.gd.uid @@ -0,0 +1 @@ +uid://d4fr0ufg1dfy0 diff --git a/examples/nested_fsm/nested_fsm_character_controller.tscn b/examples/nested_fsm/nested_fsm_character_controller.tscn new file mode 100644 index 0000000..3f3dac7 --- /dev/null +++ b/examples/nested_fsm/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/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/nested_mr_blacksmith.tscn b/examples/nested_fsm/nested_mr_blacksmith.tscn new file mode 100644 index 0000000..76d5749 --- /dev/null +++ b/examples/nested_fsm/nested_mr_blacksmith.tscn @@ -0,0 +1,304 @@ +[gd_scene load_steps=23 format=3 uid="uid://dcgl1ra6220lh"] + +[ext_resource type="Script" uid="uid://d4g4u8q8b7rsx" path="res://examples/fsm_character_controller/MrBlacksmith.gd" id="1_bflu6"] +[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="Texture2D" uid="uid://dctkyiumq3jky" path="res://examples/nested_fsm/Assets/Sword and shield.png" id="13_7vpcd"] + +[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": [true] +} + +[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_bflu6") + +[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") +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("../../MOVING") + +[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="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"] +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"] 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 From a4a0396b9e14cc413260617d1687652d03f313ea Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Mon, 25 Aug 2025 22:23:26 -0500 Subject: [PATCH 02/10] base implementation done --- .../finite_state_machine/fsm_state.gd | 2 +- .../fsm_character_controller/MrBlacksmith.gd | 12 +----- .../Assets/Sword and shield.png | Bin .../Assets/Sword and shield.png.import | 6 +-- .../NestedMrBlacksmith.gd | 39 ++++++++++++++++++ .../NestedMrBlacksmith.gd.uid | 1 + .../States/ATTACKING.gd | 0 .../States/ATTACKING.gd.uid | 0 .../States/BLOCKING.gd | 0 .../States/BLOCKING.gd.uid | 0 .../Transitions/onActionFinished.gd} | 0 .../Transitions/onActionFinished.gd.uid} | 0 .../Transitions/onStartAction.gd | 14 +++++++ .../Transitions/onStartAction.gd.uid | 1 + .../Transitions/onStopAction.gd | 26 ++++++++++++ .../Transitions/onStopAction.gd.uid | 1 + examples/nested_fsm_controller/action_fsm.gd | 17 ++++++++ .../nested_fsm_controller/action_fsm.gd.uid | 1 + .../nested_fsm_character_controller.tscn | 2 +- .../nested_mr_blacksmith.tscn | 39 +++++++++++++++--- 20 files changed, 141 insertions(+), 20 deletions(-) rename examples/{nested_fsm => nested_fsm_controller}/Assets/Sword and shield.png (100%) rename examples/{nested_fsm => nested_fsm_controller}/Assets/Sword and shield.png.import (67%) create mode 100644 examples/nested_fsm_controller/NestedMrBlacksmith.gd create mode 100644 examples/nested_fsm_controller/NestedMrBlacksmith.gd.uid rename examples/{nested_fsm => nested_fsm_controller}/States/ATTACKING.gd (100%) rename examples/{nested_fsm => nested_fsm_controller}/States/ATTACKING.gd.uid (100%) rename examples/{nested_fsm => nested_fsm_controller}/States/BLOCKING.gd (100%) rename examples/{nested_fsm => nested_fsm_controller}/States/BLOCKING.gd.uid (100%) rename examples/{nested_fsm/ACTION_FINISHED.gd => nested_fsm_controller/Transitions/onActionFinished.gd} (100%) rename examples/{nested_fsm/ACTION_FINISHED.gd.uid => nested_fsm_controller/Transitions/onActionFinished.gd.uid} (100%) create mode 100644 examples/nested_fsm_controller/Transitions/onStartAction.gd create mode 100644 examples/nested_fsm_controller/Transitions/onStartAction.gd.uid create mode 100644 examples/nested_fsm_controller/Transitions/onStopAction.gd create mode 100644 examples/nested_fsm_controller/Transitions/onStopAction.gd.uid create mode 100644 examples/nested_fsm_controller/action_fsm.gd create mode 100644 examples/nested_fsm_controller/action_fsm.gd.uid rename examples/{nested_fsm => nested_fsm_controller}/nested_fsm_character_controller.tscn (99%) rename examples/{nested_fsm => nested_fsm_controller}/nested_mr_blacksmith.tscn (82%) 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/examples/fsm_character_controller/MrBlacksmith.gd b/examples/fsm_character_controller/MrBlacksmith.gd index 733ee71..edb1067 100644 --- a/examples/fsm_character_controller/MrBlacksmith.gd +++ b/examples/fsm_character_controller/MrBlacksmith.gd @@ -6,10 +6,10 @@ 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 sprite := $Sprite2D @onready var animation_player := $AnimationPlayer @onready var particles_walking := $ParticlesWalking @@ -20,13 +20,5 @@ func _physics_process(_delta): Input.get_axis("ui_up", "ui_down") ) - func _ready(): state_machine.start() - -func _on_fsm_controller_state_changed(state: FSMState) -> void: - print(state.name) - - -func _on_movement_fsm_nested_state_changed(state: FSMState) -> void: - print(state.name) diff --git a/examples/nested_fsm/Assets/Sword and shield.png b/examples/nested_fsm_controller/Assets/Sword and shield.png similarity index 100% rename from examples/nested_fsm/Assets/Sword and shield.png rename to examples/nested_fsm_controller/Assets/Sword and shield.png diff --git a/examples/nested_fsm/Assets/Sword and shield.png.import b/examples/nested_fsm_controller/Assets/Sword and shield.png.import similarity index 67% rename from examples/nested_fsm/Assets/Sword and shield.png.import rename to examples/nested_fsm_controller/Assets/Sword and shield.png.import index 10e41d7..62950f1 100644 --- a/examples/nested_fsm/Assets/Sword and shield.png.import +++ b/examples/nested_fsm_controller/Assets/Sword and shield.png.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://dctkyiumq3jky" -path="res://.godot/imported/Sword and shield.png-c57e132e6fad8a4a88557cfd4c8a9177.ctex" +path="res://.godot/imported/Sword and shield.png-f61d4ac82ecd8ec8202ed6de14c4aa5e.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://examples/nested_fsm/Assets/Sword and shield.png" -dest_files=["res://.godot/imported/Sword and shield.png-c57e132e6fad8a4a88557cfd4c8a9177.ctex"] +source_file="res://examples/nested_fsm_controller/Assets/Sword and shield.png" +dest_files=["res://.godot/imported/Sword and shield.png-f61d4ac82ecd8ec8202ed6de14c4aa5e.ctex"] [params] diff --git a/examples/nested_fsm_controller/NestedMrBlacksmith.gd b/examples/nested_fsm_controller/NestedMrBlacksmith.gd new file mode 100644 index 0000000..73449f4 --- /dev/null +++ b/examples/nested_fsm_controller/NestedMrBlacksmith.gd @@ -0,0 +1,39 @@ +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()) + + +func _on_movement_fsm_nested_state_changed(state: FSMState) -> void: + print("Movement FSM: ", state.name, " ", Time.get_ticks_msec()) + + +func _on_action_fsm_nested_state_changed(state: FSMState) -> void: + print("Action FSM: ", state.name, " ", Time.get_ticks_msec()) 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/States/ATTACKING.gd b/examples/nested_fsm_controller/States/ATTACKING.gd similarity index 100% rename from examples/nested_fsm/States/ATTACKING.gd rename to examples/nested_fsm_controller/States/ATTACKING.gd diff --git a/examples/nested_fsm/States/ATTACKING.gd.uid b/examples/nested_fsm_controller/States/ATTACKING.gd.uid similarity index 100% rename from examples/nested_fsm/States/ATTACKING.gd.uid rename to examples/nested_fsm_controller/States/ATTACKING.gd.uid diff --git a/examples/nested_fsm/States/BLOCKING.gd b/examples/nested_fsm_controller/States/BLOCKING.gd similarity index 100% rename from examples/nested_fsm/States/BLOCKING.gd rename to examples/nested_fsm_controller/States/BLOCKING.gd diff --git a/examples/nested_fsm/States/BLOCKING.gd.uid b/examples/nested_fsm_controller/States/BLOCKING.gd.uid similarity index 100% rename from examples/nested_fsm/States/BLOCKING.gd.uid rename to examples/nested_fsm_controller/States/BLOCKING.gd.uid diff --git a/examples/nested_fsm/ACTION_FINISHED.gd b/examples/nested_fsm_controller/Transitions/onActionFinished.gd similarity index 100% rename from examples/nested_fsm/ACTION_FINISHED.gd rename to examples/nested_fsm_controller/Transitions/onActionFinished.gd diff --git a/examples/nested_fsm/ACTION_FINISHED.gd.uid b/examples/nested_fsm_controller/Transitions/onActionFinished.gd.uid similarity index 100% rename from examples/nested_fsm/ACTION_FINISHED.gd.uid rename to examples/nested_fsm_controller/Transitions/onActionFinished.gd.uid 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..3875bed --- /dev/null +++ b/examples/nested_fsm_controller/Transitions/onStopAction.gd @@ -0,0 +1,26 @@ +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..65d86d3 --- /dev/null +++ b/examples/nested_fsm_controller/action_fsm.gd @@ -0,0 +1,17 @@ +@tool +extends FSMState + + +## Executes after the state is entered. +#func _on_enter(_actor: Node, _blackboard: Blackboard) -> void: + #pass +# +# +## Executes every _process call, if the state is active. +#func _on_update(_delta: float, _actor: Node, _blackboard: Blackboard) -> void: + #pass +# +# +## Executes before the state is exited. +#func _on_exit(_actor: Node, _blackboard: Blackboard) -> void: + #pass 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..236f14e --- /dev/null +++ b/examples/nested_fsm_controller/action_fsm.gd.uid @@ -0,0 +1 @@ +uid://c81gffq30ywkx diff --git a/examples/nested_fsm/nested_fsm_character_controller.tscn b/examples/nested_fsm_controller/nested_fsm_character_controller.tscn similarity index 99% rename from examples/nested_fsm/nested_fsm_character_controller.tscn rename to examples/nested_fsm_controller/nested_fsm_character_controller.tscn index 3f3dac7..9ba0a19 100644 --- a/examples/nested_fsm/nested_fsm_character_controller.tscn +++ b/examples/nested_fsm_controller/nested_fsm_character_controller.tscn @@ -1,7 +1,7 @@ [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/nested_mr_blacksmith.tscn" id="2_cfmr3"] +[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"] diff --git a/examples/nested_fsm/nested_mr_blacksmith.tscn b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn similarity index 82% rename from examples/nested_fsm/nested_mr_blacksmith.tscn rename to examples/nested_fsm_controller/nested_mr_blacksmith.tscn index 76d5749..a65cf00 100644 --- a/examples/nested_fsm/nested_mr_blacksmith.tscn +++ b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn @@ -1,6 +1,6 @@ -[gd_scene load_steps=23 format=3 uid="uid://dcgl1ra6220lh"] +[gd_scene load_steps=27 format=3 uid="uid://dcgl1ra6220lh"] -[ext_resource type="Script" uid="uid://d4g4u8q8b7rsx" path="res://examples/fsm_character_controller/MrBlacksmith.gd" id="1_bflu6"] +[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"] @@ -11,7 +11,11 @@ [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="Texture2D" uid="uid://dctkyiumq3jky" path="res://examples/nested_fsm/Assets/Sword and shield.png" id="13_7vpcd"] +[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://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 @@ -85,7 +89,7 @@ tracks/5/keys = { "times": PackedFloat32Array(0), "transitions": PackedFloat32Array(1), "update": 1, -"values": [true] +"values": [false] } [sub_resource type="Animation" id="Animation_7vpcd"] @@ -222,7 +226,7 @@ radius = 3.125 height = 11.13 [node name="NESTEDMrBlacksmith" type="CharacterBody2D"] -script = ExtResource("1_bflu6") +script = ExtResource("1_aurnf") [node name="AnimationPlayer" type="AnimationPlayer" parent="."] libraries = { @@ -279,6 +283,29 @@ script = ExtResource("9_uambq") 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("4_s70fg") +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"] @@ -293,6 +320,7 @@ 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") @@ -302,3 +330,4 @@ 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"] From 22139a3e389038427ef8ca236e73a7a8dc2d5cf8 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Thu, 4 Sep 2025 23:19:00 -0500 Subject: [PATCH 03/10] transition warnings implemented --- .../finite_state_machine/fsm_transition.gd | 33 ++++++++++++++++--- .../finite_state_machine/nested_fsm.gd | 1 - .../transitions/onStartMoving.gd | 1 + .../nested_mr_blacksmith.tscn | 4 +-- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd index 42c917e..de53dc6 100644 --- a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd +++ b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd @@ -54,16 +54,41 @@ 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.") - + + var fsm := _find_fsm() + if fsm: + # Get path from FSM root to each node + var our_path := fsm.get_path_to(get_parent()).get_concatenated_names().split("/") + var their_path := fsm.get_path_to(next_state).get_concatenated_names().split("/") + + var our_state := get_node_or_null(our_path[our_path.size() - 2]) + var their_state := get_node_or_null(their_path[their_path.size() - 2]) + + print("Our Path: ", our_path) + print("Next Path: ", their_path) + + # Compare depth by counting path segments + if our_path.size() != their_path.size() or our_state != their_state: + warnings.append("FSMTransition should not transition outside of this NestedFSM.") + 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 b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd index 2081ce8..46d05d8 100644 --- a/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd +++ b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd @@ -2,7 +2,6 @@ extends FSMState class_name NestedFSM -#TODO: Test NESTED MR BLACKSMITH ## The signal emitted when the fsm's state changes. signal nested_state_changed(state: FSMState) 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/nested_fsm_controller/nested_mr_blacksmith.tscn b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn index a65cf00..40042bb 100644 --- a/examples/nested_fsm_controller/nested_mr_blacksmith.tscn +++ b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn @@ -263,7 +263,7 @@ 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("../../MOVING") +next_state = NodePath("..") [node name="MOVING" type="Node" parent="FSMController/MovementFSM"] script = ExtResource("6_cdhgq") @@ -281,7 +281,7 @@ 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") +next_state = NodePath("../../../ActionFSM") [node name="onStartAction" type="Node" parent="FSMController/MovementFSM" node_paths=PackedStringArray("next_state")] script = ExtResource("12_cdhgq") From 8d4082fbdfd781ae20a60e8f66ceb61c0c12c0d6 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Thu, 4 Sep 2025 23:47:07 -0500 Subject: [PATCH 04/10] improved transition warnings + added comments to transition warnings --- .../finite_state_machine/fsm_transition.gd | 19 ++++++++++--------- .../Transitions/onStopAction.gd | 1 + .../nested_mr_blacksmith.tscn | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd index de53dc6..99d0cfb 100644 --- a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd +++ b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd @@ -69,20 +69,21 @@ func _get_configuration_warnings() -> PackedStringArray: var fsm := _find_fsm() if fsm: - # Get path from FSM root to each node - var our_path := fsm.get_path_to(get_parent()).get_concatenated_names().split("/") - var their_path := fsm.get_path_to(next_state).get_concatenated_names().split("/") - - var our_state := get_node_or_null(our_path[our_path.size() - 2]) - var their_state := get_node_or_null(their_path[their_path.size() - 2]) + # Generate arrays of node names to compare. + var our_path := fsm.get_path_to(get_parent()).get_concatenated_names().split("/") # Node names from FSM to parent state + var their_path := fsm.get_path_to(next_state).get_concatenated_names().split("/") # Node names from FSM to next_state print("Our Path: ", our_path) print("Next Path: ", their_path) - # Compare depth by counting path segments - if our_path.size() != their_path.size() or our_state != their_state: + # Handle mismatch case. Where one is >=2, the other is <2 + # This is to avoid accessesing elements that don't exist + if (our_path.size() >= 2) != (their_path.size() >= 2): warnings.append("FSMTransition should not transition outside of this NestedFSM.") - + # Compare their sizes to check for nesting, then compare their parent states to check if they're the same using length - 2 + elif our_path.size() != their_path.size() or our_path[-2] != their_path[-2]: + warnings.append("FSMTransition should not transition outside of this NestedFSM.") + return warnings func _find_fsm() -> FiniteStateMachine: diff --git a/examples/nested_fsm_controller/Transitions/onStopAction.gd b/examples/nested_fsm_controller/Transitions/onStopAction.gd index 3875bed..ea26715 100644 --- a/examples/nested_fsm_controller/Transitions/onStopAction.gd +++ b/examples/nested_fsm_controller/Transitions/onStopAction.gd @@ -1,3 +1,4 @@ +@tool extends FSMTransition diff --git a/examples/nested_fsm_controller/nested_mr_blacksmith.tscn b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn index 40042bb..0838f2f 100644 --- a/examples/nested_fsm_controller/nested_mr_blacksmith.tscn +++ b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn @@ -303,7 +303,7 @@ 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") +next_state = NodePath("..") metadata/_custom_type_script = "uid://1h0braq41vwb" [node name="Weapons" type="Node2D" parent="."] From 05aa27ca857a5ecd29e8df2eddedae45e6566f64 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Fri, 5 Sep 2025 00:03:13 -0500 Subject: [PATCH 05/10] added comments to NestedFSM, next is adjusting configuration warnings in states --- .../finite_state_machine/nested_fsm.gd | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd index 46d05d8..95237b7 100644 --- a/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd +++ b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd @@ -2,6 +2,14 @@ extends FSMState class_name NestedFSM +## 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) @@ -9,6 +17,7 @@ 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: @@ -17,27 +26,38 @@ func _ready() -> void: 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) - fsm.initial_state = initial_state - fsm.verbose = verbose + # 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() - fsm.actor = parent_state_machine.actor - fsm.process_type = parent_state_machine.process_type - fsm.blackboard = parent_state_machine.blackboard + + # 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. @@ -49,6 +69,10 @@ 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 + + # Add custom configuration warnings # Note: Can be deleted if you don't want to define your own warnings. func _get_configuration_warnings() -> PackedStringArray: From e801b8064a5bad69f3b363f7b13a28ba0341b23d Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Fri, 5 Sep 2025 12:12:59 -0500 Subject: [PATCH 06/10] added configuration warnings to NestedFSM and FSM The new configuration warnings check if the initial state exists within that FSM, preventing human error. --- .../finite_state_machine/fsm.gd | 7 +++++- .../finite_state_machine/fsm_transition.gd | 4 +-- .../finite_state_machine/nested_fsm.gd | 25 ++++++++++++++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/addons/behaviour_toolkit/finite_state_machine/fsm.gd b/addons/behaviour_toolkit/finite_state_machine/fsm.gd index 2942314..57497e0 100644 --- a/addons/behaviour_toolkit/finite_state_machine/fsm.gd +++ b/addons/behaviour_toolkit/finite_state_machine/fsm.gd @@ -206,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_transition.gd b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd index 99d0cfb..6798ae9 100644 --- a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd +++ b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd @@ -73,8 +73,8 @@ func _get_configuration_warnings() -> PackedStringArray: var our_path := fsm.get_path_to(get_parent()).get_concatenated_names().split("/") # Node names from FSM to parent state var their_path := fsm.get_path_to(next_state).get_concatenated_names().split("/") # Node names from FSM to next_state - print("Our Path: ", our_path) - print("Next Path: ", their_path) + print(name, " Our Path: ", our_path) + print(name, " Next Path: ", their_path) # Handle mismatch case. Where one is >=2, the other is <2 # This is to avoid accessesing elements that don't exist diff --git a/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd index 95237b7..8e0b693 100644 --- a/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd +++ b/addons/behaviour_toolkit/finite_state_machine/nested_fsm.gd @@ -2,6 +2,11 @@ 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 @@ -70,21 +75,35 @@ func _on_exit(_actor: Node, _blackboard: Blackboard) -> void: # 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 +# 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 From a22c5a73995d2de6e3aaec637afcca2416d50ad0 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Sat, 6 Sep 2025 23:45:34 -0500 Subject: [PATCH 07/10] added nestedFSM to UI + created template + added to docs --- addons/behaviour_toolkit/ui/toolkit_ui.gd | 1 + addons/behaviour_toolkit/ui/toolkit_ui.tscn | 6 + docs/documentation.md | 110 +++++++++++------- .../transitions/onStopSprinting.gd | 2 +- .../NestedMrBlacksmith.gd | 11 +- examples/nested_fsm_controller/action_fsm.gd | 44 ++++--- .../nested_fsm_controller/action_fsm.gd.uid | 2 +- .../nested_mr_blacksmith.tscn | 12 +- script_templates/NestedFSM/new_nested_fsm.gd | 29 +++++ .../NestedFSM/new_nested_fsm.gd.uid | 1 + 10 files changed, 146 insertions(+), 72 deletions(-) create mode 100644 script_templates/NestedFSM/new_nested_fsm.gd create mode 100644 script_templates/NestedFSM/new_nested_fsm.gd.uid 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..375e313 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -23,36 +23,36 @@ 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) + - [ FSMTransition](#-fsmtransition) + - [Properties](#properties-1) + - [Methods](#methods-2) - [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-2) + - [ BTComposite](#-btcomposite) + - [Properties](#properties-3) + - [ 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 +73,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 +103,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 +137,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 +168,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 +200,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/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/NestedMrBlacksmith.gd b/examples/nested_fsm_controller/NestedMrBlacksmith.gd index 73449f4..479a334 100644 --- a/examples/nested_fsm_controller/NestedMrBlacksmith.gd +++ b/examples/nested_fsm_controller/NestedMrBlacksmith.gd @@ -28,12 +28,13 @@ func _ready(): state_machine.start() func _on_fsm_controller_state_changed(state: FSMState) -> void: - print("Base FSM: ", state.name, " ", Time.get_ticks_msec()) - + #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()) - + #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()) + #print("Action FSM: ", state.name, " ", Time.get_ticks_msec()) + pass diff --git a/examples/nested_fsm_controller/action_fsm.gd b/examples/nested_fsm_controller/action_fsm.gd index 65d86d3..b3d27ed 100644 --- a/examples/nested_fsm_controller/action_fsm.gd +++ b/examples/nested_fsm_controller/action_fsm.gd @@ -1,17 +1,29 @@ @tool -extends FSMState - - -## Executes after the state is entered. -#func _on_enter(_actor: Node, _blackboard: Blackboard) -> void: - #pass -# -# -## Executes every _process call, if the state is active. -#func _on_update(_delta: float, _actor: Node, _blackboard: Blackboard) -> void: - #pass -# -# -## Executes before the state is exited. -#func _on_exit(_actor: Node, _blackboard: Blackboard) -> void: - #pass +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 index 236f14e..20a0b4e 100644 --- a/examples/nested_fsm_controller/action_fsm.gd.uid +++ b/examples/nested_fsm_controller/action_fsm.gd.uid @@ -1 +1 @@ -uid://c81gffq30ywkx +uid://dywybb2vhvccg diff --git a/examples/nested_fsm_controller/nested_mr_blacksmith.tscn b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn index 0838f2f..a671599 100644 --- a/examples/nested_fsm_controller/nested_mr_blacksmith.tscn +++ b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=27 format=3 uid="uid://dcgl1ra6220lh"] +[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"] @@ -14,6 +14,7 @@ [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"] @@ -250,6 +251,7 @@ 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("..") @@ -263,7 +265,7 @@ 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("..") +next_state = NodePath("../../MOVING") [node name="MOVING" type="Node" parent="FSMController/MovementFSM"] script = ExtResource("6_cdhgq") @@ -281,7 +283,7 @@ 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("../../../ActionFSM") +next_state = NodePath("../../MOVING") [node name="onStartAction" type="Node" parent="FSMController/MovementFSM" node_paths=PackedStringArray("next_state")] script = ExtResource("12_cdhgq") @@ -289,7 +291,7 @@ next_state = NodePath("../../ActionFSM") metadata/_custom_type_script = "uid://1h0braq41vwb" [node name="ActionFSM" type="Node" parent="FSMController" node_paths=PackedStringArray("initial_state")] -script = ExtResource("4_s70fg") +script = ExtResource("13_sv7u0") initial_state = NodePath("ATTACKING") metadata/_custom_type_script = "uid://cffv6g18yifit" @@ -303,7 +305,7 @@ 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("..") +next_state = NodePath("../../MovementFSM") metadata/_custom_type_script = "uid://1h0braq41vwb" [node name="Weapons" type="Node2D" parent="."] 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 From de85b67b64d5b0723b3e85d3336e957b077e7505 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Sat, 6 Sep 2025 23:49:44 -0500 Subject: [PATCH 08/10] Update documentation.md --- docs/documentation.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/documentation.md b/docs/documentation.md index 375e313..08c3ab6 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -29,16 +29,20 @@ The node icons where designed/choosen to give you a quick overview of their purp - [Signals](#signals) - [ FSMState](#-fsmstate) - [Methods](#methods-1) - - [ FSMTransition](#-fsmtransition) - - [Properties](#properties-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) + - [Properties](#properties-4) - [ BTComposite](#-btcomposite) - - [Properties](#properties-3) + - [Properties](#properties-5) - [ BTLeaf](#-btleaf) - [ BTDecorator](#-btdecorator) - [Status Enum](#status-enum) @@ -56,6 +60,7 @@ The node icons where designed/choosen to give you a quick overview of their purp + # Finite State Machine A finite-state machine (FSM) [...] is an abstract machine that can be in exactly one of a finite number of states at any given time. The FSM can change from one state to another in response to some inputs; the change from one state to another is called a transition. ([Wikipedia](https://en.wikipedia.org/wiki/Finite-state_machine)) @@ -121,11 +126,11 @@ This allows you to build **hierarchical state machines**, useful for organizing #### Methods - void `_on_enter(actor: Node, blackboard: Blackboard)` - Starts the nested FSM. **Override** to trigger one-shot actions or events. + 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. + 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. + Stops the nested FSM. Override to run cleanup or exit actions. #### Signals - `nested_state_changed(state: FSMState)` From 2cc41caefa9edff9d61ac491058ed03a392df29c Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Sun, 7 Sep 2025 00:29:52 -0500 Subject: [PATCH 09/10] improved transition configuration warnings. They are more performant now --- .../finite_state_machine/fsm_transition.gd | 33 +++++++++++-------- .../nested_mr_blacksmith.tscn | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd index 6798ae9..91f291b 100644 --- a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd +++ b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd @@ -69,25 +69,30 @@ func _get_configuration_warnings() -> PackedStringArray: var fsm := _find_fsm() if fsm: - # Generate arrays of node names to compare. - var our_path := fsm.get_path_to(get_parent()).get_concatenated_names().split("/") # Node names from FSM to parent state - var their_path := fsm.get_path_to(next_state).get_concatenated_names().split("/") # Node names from FSM to next_state - - print(name, " Our Path: ", our_path) - print(name, " Next Path: ", their_path) - - # Handle mismatch case. Where one is >=2, the other is <2 - # This is to avoid accessesing elements that don't exist - if (our_path.size() >= 2) != (their_path.size() >= 2): - warnings.append("FSMTransition should not transition outside of this NestedFSM.") - # Compare their sizes to check for nesting, then compare their parent states to check if they're the same using length - 2 - elif our_path.size() != their_path.size() or our_path[-2] != their_path[-2]: + # 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 = self + var current: Node = get_parent() while current: if current is FiniteStateMachine: return current as FiniteStateMachine diff --git a/examples/nested_fsm_controller/nested_mr_blacksmith.tscn b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn index a671599..bf241a7 100644 --- a/examples/nested_fsm_controller/nested_mr_blacksmith.tscn +++ b/examples/nested_fsm_controller/nested_mr_blacksmith.tscn @@ -265,7 +265,7 @@ 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("../../MOVING") +next_state = NodePath("..") [node name="MOVING" type="Node" parent="FSMController/MovementFSM"] script = ExtResource("6_cdhgq") From 300a994fbbfc1b04fbdf702154b274298de4cdbd Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Sun, 7 Sep 2025 00:32:47 -0500 Subject: [PATCH 10/10] minor change --- addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd index 91f291b..4414b11 100644 --- a/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd +++ b/addons/behaviour_toolkit/finite_state_machine/fsm_transition.gd @@ -67,6 +67,7 @@ func _get_configuration_warnings() -> PackedStringArray: 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