Added statistics tracker, started with visualization of states in node graph
parent
0930edc477
commit
655e00fb2e
|
@ -127,6 +127,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "Idle",
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"type": ">=",
|
||||||
|
"left": {
|
||||||
|
"value": 10
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"function": "distance",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"accessor": [
|
||||||
|
"character",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessor": [
|
||||||
|
"root_nodes",
|
||||||
|
"StateMachineWorld",
|
||||||
|
"child_nodes",
|
||||||
|
"TrashBin",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
class_name StateMachine
|
class_name StateMachine
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
var character: CharacterBody2D
|
@onready var tree_visualizer: Window = %TreeVisualizer
|
||||||
|
@onready var character: CharacterBody2D
|
||||||
|
|
||||||
# json read from file, contains all states and transitions
|
# json read from file, contains all states and transitions
|
||||||
var state_machine_data: Dictionary = {}
|
var state_machine_data: Dictionary = {}
|
||||||
# records all signals that were fired during the last frame, dictionary contains signal name and arguments
|
# records all signals that were fired during the last frame, dictionary contains signal name and arguments
|
||||||
|
@ -44,6 +46,9 @@ func _ready() -> void:
|
||||||
|
|
||||||
func defer_ready():
|
func defer_ready():
|
||||||
character.waste_detected.connect(Callable(self, "_on_waste_detected"))
|
character.waste_detected.connect(Callable(self, "_on_waste_detected"))
|
||||||
|
# tree_visualizer.show()
|
||||||
|
tree_visualizer.state_machine = self
|
||||||
|
tree_visualizer.build_tree()
|
||||||
|
|
||||||
|
|
||||||
# STATE MACHINE
|
# STATE MACHINE
|
||||||
|
@ -51,6 +56,16 @@ func _process(delta: float) -> void:
|
||||||
if current_state:
|
if current_state:
|
||||||
current_state.state_process(delta)
|
current_state.state_process(delta)
|
||||||
check_transitions()
|
check_transitions()
|
||||||
|
|
||||||
|
%StateMachineInfoPanel.values["State"] = current_state.get_name()
|
||||||
|
%StateMachineInfoPanel.values["Position"] = character.position
|
||||||
|
for sgl in received_signals:
|
||||||
|
%StateMachineInfoPanel.values["signal_" + sgl["signal"]] = sgl["args"]
|
||||||
|
var transition_names: Array = []
|
||||||
|
for transition in state_machine_data["states"][current_state.get_name()]["transitions"]:
|
||||||
|
transition_names.append(transition["target"])
|
||||||
|
%StateMachineInfoPanel.values["Transitions"] = transition_names
|
||||||
|
|
||||||
received_signals = []
|
received_signals = []
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,17 +79,22 @@ func check_transitions() -> void:
|
||||||
if "conditions" in transition and transition["conditions"]:
|
if "conditions" in transition and transition["conditions"]:
|
||||||
var all_conditions_met: bool = true
|
var all_conditions_met: bool = true
|
||||||
for condition in transition["conditions"]:
|
for condition in transition["conditions"]:
|
||||||
if not transition_check_condition(condition):
|
var condition_met: bool = transition_check_condition(condition)
|
||||||
|
if not condition_met:
|
||||||
all_conditions_met = false
|
all_conditions_met = false
|
||||||
|
%StateMachineInfoPanel.values["condition failed for " + transition["target"]] = condition
|
||||||
break
|
break
|
||||||
if not all_conditions_met:
|
if not all_conditions_met:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
%StateMachineInfoPanel.values["transitioned to"] = transition["target"]
|
||||||
|
|
||||||
# evaluate transfer parameters if any
|
# evaluate transfer parameters if any
|
||||||
state_transfer_variables = {}
|
state_transfer_variables = {}
|
||||||
if "transfer" in transition and transition["transfer"]:
|
if "transfer" in transition and transition["transfer"]:
|
||||||
for key in transition["transfer"]:
|
for key in transition["transfer"]:
|
||||||
state_transfer_variables[key] = transition_resolve_parameter(transition["transfer"][key])
|
state_transfer_variables[key] = transition_resolve_parameter(transition["transfer"][key])
|
||||||
|
%StateMachineInfoPanel.values["transfer [" + key + "]"] = state_transfer_variables[key]
|
||||||
|
|
||||||
# allow current state to contribute to transfer variables
|
# allow current state to contribute to transfer variables
|
||||||
current_state.contribute_transfer_variables(state_transfer_variables)
|
current_state.contribute_transfer_variables(state_transfer_variables)
|
||||||
|
@ -104,7 +124,7 @@ func transition_check_condition(condition: Dictionary) -> bool:
|
||||||
else:
|
else:
|
||||||
print("Unknown condition type [", type, "]")
|
print("Unknown condition type [", type, "]")
|
||||||
|
|
||||||
print("[check_condition] ", left, " ", type, " ", right, " = ", result)
|
# print("[check_condition] ", left, " ", type, " ", right, " = ", result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,7 +151,7 @@ func transition_resolve_function(function: Dictionary) -> Variant:
|
||||||
|
|
||||||
if function["function"] == "distance":
|
if function["function"] == "distance":
|
||||||
var result = args[0].distance_to(args[1])
|
var result = args[0].distance_to(args[1])
|
||||||
print("[resolve_function] distance(", args[0], ", ", args[1], ") = ", result)
|
# print("[resolve_function] distance(", args[0], ", ", args[1], ") = ", result)
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
print("Unknown function [", function, "]")
|
print("Unknown function [", function, "]")
|
||||||
|
@ -142,10 +162,10 @@ func transition_resolve_accessor(accessor: Array) -> Variant:
|
||||||
var current_item: Variant = self
|
var current_item: Variant = self
|
||||||
for next_item in accessor:
|
for next_item in accessor:
|
||||||
|
|
||||||
print("[resolve_accessor] current_item: ", current_item, ' next_item: ', next_item)
|
# print("[resolve_accessor] current_item: ", current_item, ' next_item: ', next_item)
|
||||||
|
|
||||||
if current_item == null:
|
if current_item == null:
|
||||||
print("[resolve_accessor] Null value in ", accessor)
|
# print("[resolve_accessor] Null value in ", accessor)
|
||||||
break
|
break
|
||||||
|
|
||||||
if objects_equal(current_item, self):
|
if objects_equal(current_item, self):
|
||||||
|
@ -175,7 +195,7 @@ func transition_resolve_accessor(accessor: Array) -> Variant:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if current_item is Dictionary:
|
if current_item is Dictionary:
|
||||||
print("[resolve_accessor] Dictionary accessor, keys: ", current_item.keys())
|
# print("[resolve_accessor] Dictionary accessor, keys: ", current_item.keys())
|
||||||
if next_item in current_item:
|
if next_item in current_item:
|
||||||
current_item = current_item[next_item]
|
current_item = current_item[next_item]
|
||||||
continue
|
continue
|
||||||
|
@ -185,9 +205,10 @@ func transition_resolve_accessor(accessor: Array) -> Variant:
|
||||||
current_item = current_item.get(next_item)
|
current_item = current_item.get(next_item)
|
||||||
|
|
||||||
if current_item == null:
|
if current_item == null:
|
||||||
print("[resolve_accessor] Failed to resolve accessor ", accessor)
|
pass
|
||||||
|
# print("[resolve_accessor] Failed to resolve accessor ", accessor)
|
||||||
|
|
||||||
print("[resolve_accessor] resolved to ", current_item)
|
# print("[resolve_accessor] resolved to ", current_item)
|
||||||
return current_item
|
return current_item
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
[gd_scene load_steps=12 format=3 uid="uid://cgdvptekjbpgq"]
|
[gd_scene load_steps=14 format=3 uid="uid://cgdvptekjbpgq"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scenes/state/SMCharacter.gd" id="1_84313"]
|
[ext_resource type="Script" path="res://scenes/state/SMCharacter.gd" id="1_84313"]
|
||||||
[ext_resource type="Script" path="res://scenes/state/SceneManagement.gd" id="1_s1suw"]
|
[ext_resource type="Script" path="res://scenes/state/SceneManagement.gd" id="1_s1suw"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bvdodsxnqjk6x" path="res://scenes/state/visualization/TreeVisualizer.tscn" id="2_bgwhe"]
|
||||||
[ext_resource type="Texture2D" uid="uid://cx04xknqfdscp" path="res://assets/cleaning_robot.png" id="2_cwwab"]
|
[ext_resource type="Texture2D" uid="uid://cx04xknqfdscp" path="res://assets/cleaning_robot.png" id="2_cwwab"]
|
||||||
[ext_resource type="Script" path="res://scenes/state/StateMachine.gd" id="2_d1xqo"]
|
[ext_resource type="Script" path="res://scenes/state/StateMachine.gd" id="2_d1xqo"]
|
||||||
[ext_resource type="Script" path="res://scenes/state/state_idle.gd" id="3_r1btx"]
|
[ext_resource type="Script" path="res://scenes/state/state_idle.gd" id="3_r1btx"]
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
[ext_resource type="Script" path="res://scenes/state/state_PickupTrash.gd" id="7_1rfah"]
|
[ext_resource type="Script" path="res://scenes/state/state_PickupTrash.gd" id="7_1rfah"]
|
||||||
[ext_resource type="Script" path="res://scenes/state/state_ThrowTrashAway.gd" id="8_677fi"]
|
[ext_resource type="Script" path="res://scenes/state/state_ThrowTrashAway.gd" id="8_677fi"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dng6a8i8kp4kw" path="res://scenes/state/TrashBin.tscn" id="9_sd6r3"]
|
[ext_resource type="PackedScene" uid="uid://dng6a8i8kp4kw" path="res://scenes/state/TrashBin.tscn" id="9_sd6r3"]
|
||||||
|
[ext_resource type="Script" path="res://scenes/state/visualization/state_machine_info_panel.gd" id="11_70vf4"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_tr1gq"]
|
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_tr1gq"]
|
||||||
radius = 60.0
|
radius = 60.0
|
||||||
|
@ -20,8 +22,12 @@ size = Vector2(601.536, 254.293)
|
||||||
[node name="StateMachineWorld" type="Node2D"]
|
[node name="StateMachineWorld" type="Node2D"]
|
||||||
script = ExtResource("1_s1suw")
|
script = ExtResource("1_s1suw")
|
||||||
|
|
||||||
|
[node name="TreeVisualizer" parent="." instance=ExtResource("2_bgwhe")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
|
||||||
[node name="Floor" type="Sprite2D" parent="."]
|
[node name="Floor" type="Sprite2D" parent="."]
|
||||||
position = Vector2(573, 329)
|
position = Vector2(573, 323)
|
||||||
scale = Vector2(0.635347, 0.635347)
|
scale = Vector2(0.635347, 0.635347)
|
||||||
texture = ExtResource("6_15pjb")
|
texture = ExtResource("6_15pjb")
|
||||||
|
|
||||||
|
@ -61,3 +67,11 @@ scale = Vector2(1, 0.48)
|
||||||
shape = SubResource("RectangleShape2D_ji64e")
|
shape = SubResource("RectangleShape2D_ji64e")
|
||||||
|
|
||||||
[node name="Node2D" type="Node2D" parent="CleaningRobotCB2D"]
|
[node name="Node2D" type="Node2D" parent="CleaningRobotCB2D"]
|
||||||
|
|
||||||
|
[node name="StateMachineInfoPanel" type="VBoxContainer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
offset_left = 6.0
|
||||||
|
offset_top = 391.0
|
||||||
|
offset_right = 205.0
|
||||||
|
offset_bottom = 645.0
|
||||||
|
script = ExtResource("11_70vf4")
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bvdodsxnqjk6x"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/state/visualization/tree_visualizer.gd" id="1_62kxn"]
|
||||||
|
|
||||||
|
[node name="TreeVisualizer" type="Window"]
|
||||||
|
position = Vector2i(50, 36)
|
||||||
|
size = Vector2i(1055, 560)
|
||||||
|
script = ExtResource("1_62kxn")
|
||||||
|
|
||||||
|
[node name="GraphEdit" type="GraphEdit" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
show_menu = false
|
||||||
|
show_zoom_buttons = false
|
||||||
|
show_grid_buttons = false
|
||||||
|
show_minimap_button = false
|
||||||
|
show_arrange_button = false
|
||||||
|
|
||||||
|
[connection signal="connection_request" from="GraphEdit" to="." method="_on_graph_edit_connection_request"]
|
|
@ -0,0 +1,30 @@
|
||||||
|
class_name DTreeNode
|
||||||
|
extends GraphNode
|
||||||
|
|
||||||
|
var left_slots: Array[String] = []
|
||||||
|
var right_slots: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func add_label(label_text: String, left: bool, right: bool) -> Vector3i:
|
||||||
|
var new_label: Label = Label.new()
|
||||||
|
new_label.text = label_text
|
||||||
|
new_label.add_theme_color_override("font_color", Color(0, 0, 0, 1))
|
||||||
|
|
||||||
|
self.add_child(new_label)
|
||||||
|
|
||||||
|
var child_index: int = self.get_child_count() - 1
|
||||||
|
if left:
|
||||||
|
self.set_slot_enabled_left(child_index, true)
|
||||||
|
self.set_slot_color_left(child_index, Color(0.9, 0.9, 0.9, 1))
|
||||||
|
left_slots.append(label_text)
|
||||||
|
if right:
|
||||||
|
self.set_slot_enabled_right(child_index, true)
|
||||||
|
self.set_slot_color_right(child_index, Color(0.9, 0.9, 0.9, 1))
|
||||||
|
right_slots.append(label_text)
|
||||||
|
|
||||||
|
# the port index is counted separately from the left and right slots.
|
||||||
|
return Vector3i(child_index, left_slots.size() - 1, right_slots.size() - 1)
|
|
@ -0,0 +1,20 @@
|
||||||
|
[gd_scene load_steps=4 format=3 uid="uid://bmv75j2e8xc6n"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/state/visualization/d_tree_node.gd" id="1_o2ffa"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k54lu"]
|
||||||
|
bg_color = Color(0.95158, 0.95158, 0.95158, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dmqj2"]
|
||||||
|
bg_color = Color(0.6, 0.39, 0.39, 1)
|
||||||
|
|
||||||
|
[node name="DTreeNode" type="GraphNode"]
|
||||||
|
offset_right = 128.0
|
||||||
|
offset_bottom = 184.0
|
||||||
|
mouse_filter = 1
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_k54lu")
|
||||||
|
theme_override_styles/titlebar = SubResource("StyleBoxFlat_dmqj2")
|
||||||
|
draggable = false
|
||||||
|
selectable = false
|
||||||
|
title = "TITEL"
|
||||||
|
script = ExtResource("1_o2ffa")
|
|
@ -0,0 +1,47 @@
|
||||||
|
class_name InfoPanel
|
||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
var values: Dictionary = {}
|
||||||
|
var last_values: Dictionary = {}
|
||||||
|
var value_order: Array = [] # Keep track of the order of values
|
||||||
|
|
||||||
|
func to_str(value) -> String:
|
||||||
|
if value is float:
|
||||||
|
return str(round(value * 100) / 100)
|
||||||
|
elif value is Vector2:
|
||||||
|
return "(" + to_str(value.x) + ", " + to_str(value.y) + ")"
|
||||||
|
elif value is Vector3:
|
||||||
|
return "(" + to_str(value.x) + ", " + to_str(value.y) + ", " + to_str(value.z) + ")"
|
||||||
|
elif value is Vector4:
|
||||||
|
return "(" + to_str(value.x) + ", " + to_str(value.y) + ", " + to_str(value.z) + ", " + to_str(value.w) + ")"
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
for child in self.get_children():
|
||||||
|
if child is Label:
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
# 1. Update and Track Order of Values
|
||||||
|
for value in values.keys():
|
||||||
|
if value in value_order:
|
||||||
|
value_order.erase(value) # Move to the end (most recent)
|
||||||
|
value_order.append(value)
|
||||||
|
|
||||||
|
# 2. Display Current Values (Most Recent First)
|
||||||
|
for value in value_order:
|
||||||
|
if values.has(value):
|
||||||
|
var new_label: Label = Label.new()
|
||||||
|
new_label.text = value + ": " + to_str(values[value])
|
||||||
|
new_label.add_theme_color_override("font_color", Color(0, 0, 0, 1))
|
||||||
|
self.add_child(new_label)
|
||||||
|
last_values[value] = values[value]
|
||||||
|
|
||||||
|
# 3. Display Old Values
|
||||||
|
for value in last_values.keys():
|
||||||
|
if not values.has(value):
|
||||||
|
var new_label: Label = Label.new()
|
||||||
|
new_label.text = value + ": " + to_str(last_values[value])
|
||||||
|
new_label.add_theme_color_override("font_color", Color(0.5, 0.5, 0.5, 1))
|
||||||
|
self.add_child(new_label)
|
||||||
|
|
||||||
|
values.clear()
|
|
@ -0,0 +1,107 @@
|
||||||
|
extends Window
|
||||||
|
|
||||||
|
const D_TREE_NODE: PackedScene = preload("res://scenes/state/visualization/d_tree_node.tscn")
|
||||||
|
@onready var graph_edit: GraphEdit = %GraphEdit
|
||||||
|
|
||||||
|
# input data
|
||||||
|
var state_machine: StateMachine
|
||||||
|
# nodes
|
||||||
|
var all_nodes: Array[DTreeNode] = []
|
||||||
|
var all_node_names_to_nodes: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func build_tree():
|
||||||
|
if not state_machine:
|
||||||
|
push_error("No state machine set.")
|
||||||
|
return
|
||||||
|
|
||||||
|
var node_positions: Array[DTreeNode] = []
|
||||||
|
var y_spacing: int = 400
|
||||||
|
var x_spacing: int = 400
|
||||||
|
var depths: Dictionary = calculate_node_depths()
|
||||||
|
|
||||||
|
var states = state_machine.state_machine_data["states"]
|
||||||
|
|
||||||
|
# Place nodes
|
||||||
|
for i in range(states.size()):
|
||||||
|
var state_name = states.keys()[i]
|
||||||
|
var state_data = states[state_name]
|
||||||
|
|
||||||
|
var graph_node: DTreeNode = D_TREE_NODE.instantiate()
|
||||||
|
graph_edit.add_child(graph_node)
|
||||||
|
graph_node.title = state_name
|
||||||
|
|
||||||
|
all_nodes.append(graph_node)
|
||||||
|
all_node_names_to_nodes[state_name] = graph_node
|
||||||
|
|
||||||
|
# Connect nodes
|
||||||
|
for i in range(states.size()):
|
||||||
|
var state_name = states.keys()[i]
|
||||||
|
var state_data = states[state_name]
|
||||||
|
var graph_node: DTreeNode = all_nodes[i]
|
||||||
|
|
||||||
|
# Set up labels (input and output)
|
||||||
|
populate_node_labels(graph_node, state_name, state_data, all_node_names_to_nodes)
|
||||||
|
|
||||||
|
# Calculate position based on tree depth
|
||||||
|
var depth: int = depths[i]
|
||||||
|
var siblings: Array = get_nodes_at_depth(depth, depths)
|
||||||
|
@warning_ignore("integer_division")
|
||||||
|
var y_pos: int = y_spacing * (siblings.find(i) - (siblings.size() - 1) / 2)
|
||||||
|
var x_pos: int = x_spacing * depth
|
||||||
|
y_pos += randi() % 200 - 100
|
||||||
|
graph_node.position_offset = Vector2(x_pos, y_pos)
|
||||||
|
node_positions.append(graph_node)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_graph_edit_connection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
|
||||||
|
print("Connection request from %s [%d] to %s [%d]" % [from_node, from_port, to_node, to_port])
|
||||||
|
|
||||||
|
|
||||||
|
# using connect_node(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> int:
|
||||||
|
func populate_node_labels(node: DTreeNode, state_name: String, state_data: Dictionary, node_names_to_nodes: Dictionary) -> void:
|
||||||
|
# start from the node passed in and only use the output connections to determine the input connections on the other nodes
|
||||||
|
var output_transitions: Array = state_data.get("transitions", [])
|
||||||
|
for transition in output_transitions:
|
||||||
|
var target_state_name: String = transition['target']
|
||||||
|
var port_out: Vector3i = node.add_label("%s" % [target_state_name], false, true)
|
||||||
|
|
||||||
|
var target_node: DTreeNode = node_names_to_nodes[target_state_name]
|
||||||
|
if target_node:
|
||||||
|
var port_in: Vector3i = target_node.add_label("%s" % [state_name], true, false)
|
||||||
|
|
||||||
|
# connect the nodes
|
||||||
|
graph_edit.connect_node(
|
||||||
|
node.get_name(), port_out.z,
|
||||||
|
target_node.get_name(), port_in.y
|
||||||
|
)
|
||||||
|
print("Connecting %s [%d] to %s [%d]" % [node.get_name(), port_out, target_node.get_name(), port_in])
|
||||||
|
|
||||||
|
|
||||||
|
func calculate_node_depths() -> Dictionary:
|
||||||
|
var depths: Dictionary = {}
|
||||||
|
depths[0] = 0 # Root is at depth 0
|
||||||
|
var state_names = state_machine.state_machine_data["states"].keys()
|
||||||
|
for i in range(state_names.size()):
|
||||||
|
var state_name = state_names[i]
|
||||||
|
var state_data = state_machine.state_machine_data["states"][state_name]
|
||||||
|
if state_data.has("transitions"):
|
||||||
|
for transition in state_data["transitions"]:
|
||||||
|
if transition.has("target"):
|
||||||
|
var target_state_name = transition["target"]
|
||||||
|
var target_state_index = state_names.find(target_state_name)
|
||||||
|
if target_state_index != -1:
|
||||||
|
depths[target_state_index] = depths[i] + 1
|
||||||
|
return depths
|
||||||
|
|
||||||
|
|
||||||
|
func get_nodes_at_depth(depth: int, depths: Dictionary) -> Array:
|
||||||
|
var nodes_at_depth: Array[Variant] = []
|
||||||
|
for i in depths.keys():
|
||||||
|
if depths[i] == depth:
|
||||||
|
nodes_at_depth.append(i)
|
||||||
|
return nodes_at_depth
|
Loading…
Reference in New Issue