diff --git a/project/main-scenes/island.tscn b/project/main-scenes/island.tscn index e7cf058..bae31a2 100644 --- a/project/main-scenes/island.tscn +++ b/project/main-scenes/island.tscn @@ -218,7 +218,6 @@ size_flags_vertical = 8 unique_name_in_owner = true position = Vector2i(0, 36) size = Vector2i(1075, 225) -visible = false script = ExtResource("5_ecfvx") [node name="GraphEdit" type="GraphEdit" parent="Camera2D/CanvasLayer/TreeVisualizer"] diff --git a/project/scripts/visualization/BehaviorTreeVisualizer.gd b/project/scripts/visualization/BehaviorTreeVisualizer.gd index c7bd750..2e53466 100644 --- a/project/scripts/visualization/BehaviorTreeVisualizer.gd +++ b/project/scripts/visualization/BehaviorTreeVisualizer.gd @@ -4,13 +4,15 @@ extends Window const D_TREE_NODE: PackedScene = preload("res://scripts/visualization/d_tree_node.tscn") @onready var graph_edit: GraphEdit = %GraphEdit -# Input data +# var behavior_tree: BehaviorTree -var all_nodes: Array[DTreeNode] = [] -var all_node_names_to_nodes: Dictionary = {} -# Configuration -var x_spacing: int = 300 # Base horizontal distance between parent and child nodes -var y_spacing: int = 200 # Vertical distance between sibling nodes +# +var x_spacing: int = 400 +var y_spacing: int = 100 +# +var all_nodes: Array[DTreeNode] = [] +# +var current_lowest_node_pos: Vector2 = Vector2(0, 0) func build_tree() -> void: @@ -18,127 +20,88 @@ func build_tree() -> void: push_error("No behavior tree set.") return - # Reset current visualization + # reset current visualization graph_edit.clear_connections() for node in all_nodes: node.queue_free() all_nodes.clear() - all_node_names_to_nodes.clear() - # Build tree starting from the root task - var root_node: Task = behavior_tree.behavior_tree - if not root_node: + var root_task: Task = behavior_tree.behavior_tree + if not root_task: push_error("Root behavior tree node is null.") return - # Calculate positions for all nodes - var positions: Dictionary = calculate_tree_layout(root_node) - - # Create the nodes and connections in the GraphEdit - for task_name in positions.keys(): - var position = positions[task_name] - var task: Task = find_task_by_name(root_node, task_name) - if task: - create_node(task, position) - - # Connect the nodes - for node in all_nodes: - var task_name: String = node.title - var task: Task = find_task_by_name(root_node, task_name) - if task: - for child in task.get_children(): - if child is Task: - connect_nodes(task.name, child.name) + current_lowest_node_pos = Vector2(0, 0) + build_tree_from_task(root_task, 0) -func calculate_tree_layout(root_task: Task) -> Dictionary: - """ - Calculates the positions of all nodes in the tree, ensuring no overlaps. - """ - var positions: Dictionary = {} - var layers = get_tree_layers(root_task) - var layer_count = layers.size() - var layer_heights: Array[float] = [] +func build_tree_from_task(task: Task, depth: int) -> DTreeNode: + var child_nodes: Array[DTreeNode] = [] + var child_node_positions: Array[Vector2] = [] - # Calculate height for each layer based on the number of nodes - for layer in layers: - layer_heights.append(float(layer.size() - 1) * y_spacing) + for child in task.get_children(): + var child_node: DTreeNode = build_tree_from_task(child, depth + 1) + child_nodes.append(child_node) + child_node_positions.append(child_node.position_offset) - # Position nodes starting from the deepest layer and working up - for layer_index in range(layer_count - 1, -1, -1): - var layer = layers[layer_index] - var layer_y_offset: float = 0.0 - if layer_index < layer_count - 1: - layer_y_offset = layer_heights[layer_index + 1] / 2.0 + var current_node: DTreeNode = D_TREE_NODE.instantiate() + graph_edit.add_child(current_node) + current_node.name = task.get_name() + str(randf()) + current_node.title = transform_string(task.get_name()) + current_node.add_label("status", true, true) + all_nodes.append(current_node) - var x: float = float(layer_index) * x_spacing - var num_nodes = layer.size() - var start_y = -layer_heights[layer_index] / 2.0 + for child in child_nodes: + graph_edit.connect_node(current_node.name, 0, child.name, 0) - for node_index in range(num_nodes): - var task = layer[node_index] - var y = start_y + float(node_index) * y_spacing + layer_y_offset - positions[task.name] = Vector2(x, y) + if child_node_positions.size() > 0: + var average_position: Vector2 = Vector2(0, 0) + for pos in child_node_positions: + average_position += pos + average_position /= child_node_positions.size() - return positions + current_node.position_offset = average_position - Vector2(x_spacing, 0) + # print("as parent: ", current_node.name, " ", current_node.position_offset, " ", depth, " ", child_node_positions) + else: + current_node.position_offset = Vector2(current_lowest_node_pos) + current_node.position_offset.x += depth * x_spacing + current_lowest_node_pos.y += y_spacing + # print("as leaf: ", current_node.name, " ", current_node.position_offset, " ", depth) + pass -func get_tree_layers(root_task: Task): - """ - Returns a list of layers, where each layer is a list of tasks at the same depth. - """ - var layers = [] - var queue: Array[Dictionary] = [{"task": root_task, "depth": 0}] + return current_node - while not queue.is_empty(): - var current_item = queue.pop_front() - var task = current_item.task - var depth = current_item.depth +func transform_string(input: String) -> String: + var prefixes: Dictionary = {"sl_": "Selector: ", "sq_": "Sequence: ", "Task": ""} + var selected_prefix: String = "" - if layers.size() <= depth: - layers.append([]) - layers[depth].append(task) + for prefix in prefixes.keys(): + if input.begins_with(prefix): + selected_prefix = prefix + input = input.substr(prefix.length()) + break - for child in task.get_children(): - if child is Task: - queue.append({"task": child as Task, "depth": depth + 1}) + var words: Array[Variant] = [] + var current_word: String = "" - return layers + for i in range(input.length()): + var character = input[i] + if character.to_upper() == character and current_word.length() > 0: + words.append(current_word) + current_word = "" + character.to_lower() + elif character == "_": + if current_word.length() > 0: + words.append(current_word) + current_word = "" + else: + current_word += character.to_lower() + if current_word.length() > 0: + words.append(current_word) -func create_node(task: Task, position: Vector2) -> void: - """ - Creates a visual node for the given task at the specified position. - """ - var graph_node: DTreeNode = D_TREE_NODE.instantiate() - graph_node.title = task.name - graph_node.position_offset = position - graph_edit.add_child(graph_node) + var result: String = " ".join(words) + if selected_prefix in prefixes and prefixes[selected_prefix] != "": + result = prefixes[selected_prefix] + result - all_nodes.append(graph_node) - all_node_names_to_nodes[task.name] = graph_node - - -func connect_nodes(parent_name: String, child_name: String) -> void: - """ - Connects two nodes in the GraphEdit. - """ - if all_node_names_to_nodes.has(parent_name) and all_node_names_to_nodes.has(child_name): - graph_edit.connect_node( - all_node_names_to_nodes[parent_name].get_name(), 0, # Parent's output port - all_node_names_to_nodes[child_name].get_name(), 0 # Child's input port - ) - - -func find_task_by_name(root: Task, name: String) -> Task: - """ - Finds a task by name within the behavior tree. - """ - if root.name == name: - return root - for child in root.get_children(): - if child is Task: - var result = find_task_by_name(child as Task, name) - if result: - return result - return null \ No newline at end of file + return result