forked from 2121578/gai-ca2
Initial version of a tree visualizer
parent
0becddf3d2
commit
d8bc800077
|
|
@ -1,4 +1,4 @@
|
||||||
[gd_scene load_steps=44 format=4 uid="uid://b88asko1ugyd2"]
|
[gd_scene load_steps=45 format=4 uid="uid://b88asko1ugyd2"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/global/GameManager.gd" id="1_eeg2d"]
|
[ext_resource type="Script" path="res://scripts/global/GameManager.gd" id="1_eeg2d"]
|
||||||
[ext_resource type="Script" path="res://scripts/tilemap/World.gd" id="1_k0rw8"]
|
[ext_resource type="Script" path="res://scripts/tilemap/World.gd" id="1_k0rw8"]
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
[ext_resource type="Material" uid="uid://ckg3be082ny3h" path="res://assets/shader/shader_vignette.tres" id="3_7waul"]
|
[ext_resource type="Material" uid="uid://ckg3be082ny3h" path="res://assets/shader/shader_vignette.tres" id="3_7waul"]
|
||||||
[ext_resource type="Script" path="res://scripts/player/PlayerManager.gd" id="4_1xqo1"]
|
[ext_resource type="Script" path="res://scripts/player/PlayerManager.gd" id="4_1xqo1"]
|
||||||
[ext_resource type="Texture2D" uid="uid://1ae5agveqddp" path="res://icon.svg" id="4_o8ona"]
|
[ext_resource type="Texture2D" uid="uid://1ae5agveqddp" path="res://icon.svg" id="4_o8ona"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/visualization/BehaviorTreeVisualizer.gd" id="5_ecfvx"]
|
||||||
[ext_resource type="Script" path="res://scripts/tilemap/StepVisualization.gd" id="5_sr2su"]
|
[ext_resource type="Script" path="res://scripts/tilemap/StepVisualization.gd" id="5_sr2su"]
|
||||||
[ext_resource type="Script" path="res://scripts/player/tree/BehaviorTree.gd" id="6_efs30"]
|
[ext_resource type="Script" path="res://scripts/player/tree/BehaviorTree.gd" id="6_efs30"]
|
||||||
[ext_resource type="Script" path="res://scripts/player/tree/impl/base/TaskSelector.gd" id="7_1jajd"]
|
[ext_resource type="Script" path="res://scripts/player/tree/impl/base/TaskSelector.gd" id="7_1jajd"]
|
||||||
|
|
@ -210,6 +211,28 @@ layout_mode = 2
|
||||||
size_flags_horizontal = 0
|
size_flags_horizontal = 0
|
||||||
size_flags_vertical = 8
|
size_flags_vertical = 8
|
||||||
|
|
||||||
|
[node name="TreeVisualizer" type="Window" parent="Camera2D/CanvasLayer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
position = Vector2i(35, 405)
|
||||||
|
size = Vector2i(1075, 225)
|
||||||
|
script = ExtResource("5_ecfvx")
|
||||||
|
|
||||||
|
[node name="GraphEdit" type="GraphEdit" parent="Camera2D/CanvasLayer/TreeVisualizer"]
|
||||||
|
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
|
||||||
|
scroll_offset = Vector2(-100, -100)
|
||||||
|
show_menu = false
|
||||||
|
show_zoom_buttons = false
|
||||||
|
show_grid_buttons = false
|
||||||
|
show_minimap_button = false
|
||||||
|
show_arrange_button = false
|
||||||
|
|
||||||
[node name="Tileset" type="Node2D" parent="."]
|
[node name="Tileset" type="Node2D" parent="."]
|
||||||
script = ExtResource("1_k0rw8")
|
script = ExtResource("1_k0rw8")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ var tilemap_types: TileMapTileTypes = TileMapTileTypes.new()
|
||||||
|
|
||||||
var tilemap_navigation: TilemapNavigation = TilemapNavigation.new()
|
var tilemap_navigation: TilemapNavigation = TilemapNavigation.new()
|
||||||
|
|
||||||
|
@onready var tree_visualizer: BehaviorTreeVisualizer = %TreeVisualizer
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
tilemap_navigation.world = world
|
tilemap_navigation.world = world
|
||||||
|
|
@ -24,6 +26,11 @@ func _ready() -> void:
|
||||||
world.step_visualizer.game_manager = self
|
world.step_visualizer.game_manager = self
|
||||||
world.step_visualizer.world = world
|
world.step_visualizer.world = world
|
||||||
update_bars()
|
update_bars()
|
||||||
|
call_deferred("defer_ready")
|
||||||
|
|
||||||
|
func defer_ready() -> void:
|
||||||
|
tree_visualizer.behavior_tree = player.behavior_tree
|
||||||
|
tree_visualizer.build_tree()
|
||||||
|
|
||||||
|
|
||||||
# game_ticker.start()
|
# game_ticker.start()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
class_name BehaviorTreeVisualizer
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
func build_tree() -> void:
|
||||||
|
if not behavior_tree:
|
||||||
|
push_error("No behavior tree set.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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] = []
|
||||||
|
|
||||||
|
# Calculate height for each layer based on the number of nodes
|
||||||
|
for layer in layers:
|
||||||
|
layer_heights.append(float(layer.size() - 1) * y_spacing)
|
||||||
|
|
||||||
|
# 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 x: float = float(layer_index) * x_spacing
|
||||||
|
var num_nodes = layer.size()
|
||||||
|
var start_y = -layer_heights[layer_index] / 2.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)
|
||||||
|
|
||||||
|
return positions
|
||||||
|
|
||||||
|
|
||||||
|
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}]
|
||||||
|
|
||||||
|
while not queue.is_empty():
|
||||||
|
var current_item = queue.pop_front()
|
||||||
|
var task = current_item.task
|
||||||
|
var depth = current_item.depth
|
||||||
|
|
||||||
|
if layers.size() <= depth:
|
||||||
|
layers.append([])
|
||||||
|
layers[depth].append(task)
|
||||||
|
|
||||||
|
for child in task.get_children():
|
||||||
|
if child is Task:
|
||||||
|
queue.append({"task": child as Task, "depth": depth + 1})
|
||||||
|
|
||||||
|
return layers
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
class_name DTreeNode
|
||||||
|
extends GraphNode
|
||||||
|
|
||||||
|
var left_slots: Array[String] = []
|
||||||
|
var right_slots: Array[String] = []
|
||||||
|
var color_highlighted: StyleBoxFlat = StyleBoxFlat.new()
|
||||||
|
var color_normal: StyleBoxFlat = StyleBoxFlat.new()
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
color_highlighted.bg_color = Color(0.67058825, 1.0, 0.5411765)
|
||||||
|
color_normal.bg_color = Color(1.0, 1.0, 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
func set_highlighted(highlight: bool) -> void:
|
||||||
|
if highlight:
|
||||||
|
self.add_theme_stylebox_override("panel", color_highlighted)
|
||||||
|
else:
|
||||||
|
self.add_theme_stylebox_override("panel", color_normal)
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
[gd_scene load_steps=4 format=3 uid="uid://bmv75j2e8xc6n"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/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 = 41.0
|
||||||
|
offset_bottom = 23.0
|
||||||
|
mouse_filter = 1
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_k54lu")
|
||||||
|
theme_override_styles/titlebar = SubResource("StyleBoxFlat_dmqj2")
|
||||||
|
title = "TITEL"
|
||||||
|
script = ExtResource("1_o2ffa")
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
class_name InfoPanel
|
||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
var values: Dictionary = {}
|
||||||
|
var last_values: Dictionary = {}
|
||||||
|
var value_order: Array = []
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
hide()
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
if Input.is_action_just_pressed("debug_vis_2"):
|
||||||
|
if is_visible():
|
||||||
|
hide()
|
||||||
|
else:
|
||||||
|
show()
|
||||||
|
|
||||||
|
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:
|
||||||
|
# Move to the end (most recent)
|
||||||
|
value_order.erase(value)
|
||||||
|
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()
|
||||||
Loading…
Reference in New Issue