gai-godot-games/state/scenes/state/visualization/tree_visualizer.gd

122 lines
4.7 KiB
GDScript

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 _process(delta: float) -> void:
if Input.is_action_just_pressed("ui_accept"):
if is_visible():
hide()
else:
show()
# highlight the current state
for node in all_nodes:
node.set_highlighted(false)
if state_machine and state_machine.current_state:
var current_state_name: StringName = state_machine.current_state.get_name()
if all_node_names_to_nodes.has(current_state_name):
all_node_names_to_nodes[current_state_name].set_highlighted(true)
func build_tree():
if not state_machine:
push_error("No state machine set.")
return
var node_positions: Array[DTreeNode] = []
var y_spacing: int = 300
var x_spacing: int = 600
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(state_machine.human_readable_transition(transition), 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