Visualization now works
parent
655e00fb2e
commit
e74ec6f3ec
|
@ -127,37 +127,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"target": "Idle",
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"type": ">=",
|
|
||||||
"left": {
|
|
||||||
"value": 10
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"function": "distance",
|
|
||||||
"args": [
|
|
||||||
{
|
|
||||||
"accessor": [
|
|
||||||
"character",
|
|
||||||
"position"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accessor": [
|
|
||||||
"root_nodes",
|
|
||||||
"StateMachineWorld",
|
|
||||||
"child_nodes",
|
|
||||||
"TrashBin",
|
|
||||||
"position"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,6 @@ window/stretch/mode="viewport"
|
||||||
|
|
||||||
spawn_trash={
|
spawn_trash={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ func check_transitions() -> void:
|
||||||
var condition_met: bool = transition_check_condition(condition)
|
var condition_met: bool = transition_check_condition(condition)
|
||||||
if not condition_met:
|
if not condition_met:
|
||||||
all_conditions_met = false
|
all_conditions_met = false
|
||||||
%StateMachineInfoPanel.values["condition failed for " + transition["target"]] = condition
|
%StateMachineInfoPanel.values["condition failed for " + transition["target"]] = human_readable_condition(condition)
|
||||||
break
|
break
|
||||||
if not all_conditions_met:
|
if not all_conditions_met:
|
||||||
continue
|
continue
|
||||||
|
@ -151,6 +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])
|
||||||
|
%StateMachineInfoPanel.values["distance()"] = result
|
||||||
# print("[resolve_function] distance(", args[0], ", ", args[1], ") = ", result)
|
# print("[resolve_function] distance(", args[0], ", ", args[1], ") = ", result)
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
|
@ -262,3 +263,67 @@ func objects_equal(a: Variant, b: Variant) -> bool:
|
||||||
if typeof(a) != typeof(b):
|
if typeof(a) != typeof(b):
|
||||||
return false
|
return false
|
||||||
return a == b
|
return a == b
|
||||||
|
|
||||||
|
|
||||||
|
func human_readable_transition(condition: Dictionary) -> String:
|
||||||
|
var parts: Array[Variant] = []
|
||||||
|
|
||||||
|
if condition.has("signal"):
|
||||||
|
parts.append(condition["signal"])
|
||||||
|
|
||||||
|
if condition.has("conditions"):
|
||||||
|
parts.append(human_readable_condition(condition))
|
||||||
|
|
||||||
|
return " & ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
func human_readable_condition(condition: Dictionary) -> String:
|
||||||
|
if condition.has("conditions"):
|
||||||
|
var parts: Array[Variant] = []
|
||||||
|
for cond in condition["conditions"]:
|
||||||
|
parts.append(human_readable_condition(cond))
|
||||||
|
return " & ".join(parts)
|
||||||
|
|
||||||
|
if condition.has("left") and condition.has("right") and condition.has("type"):
|
||||||
|
var left: Variant = condition["left"]
|
||||||
|
var right: Variant = condition["right"]
|
||||||
|
var type: String = condition["type"]
|
||||||
|
|
||||||
|
return human_readable_parameter(left) + " " + type + " " + human_readable_parameter(right)
|
||||||
|
|
||||||
|
return "Unknown condition"
|
||||||
|
|
||||||
|
|
||||||
|
func human_readable_parameter(parameter: Dictionary) -> String:
|
||||||
|
if "value" in parameter:
|
||||||
|
return str(parameter["value"])
|
||||||
|
elif "function" in parameter:
|
||||||
|
return human_readable_function(parameter)
|
||||||
|
elif "accessor" in parameter:
|
||||||
|
return human_readable_accessor(parameter["accessor"])
|
||||||
|
else:
|
||||||
|
return "Unknown parameter type"
|
||||||
|
|
||||||
|
|
||||||
|
func human_readable_function(function: Dictionary) -> String:
|
||||||
|
var args: Array = []
|
||||||
|
if "args" in function:
|
||||||
|
for arg in function["args"]:
|
||||||
|
if arg is Dictionary:
|
||||||
|
args.append(human_readable_parameter(arg))
|
||||||
|
else:
|
||||||
|
args.append(str(arg))
|
||||||
|
|
||||||
|
if function["function"] == "distance":
|
||||||
|
return "distance(" + ", ".join(args) + ")"
|
||||||
|
else:
|
||||||
|
return "Unknown function"
|
||||||
|
|
||||||
|
|
||||||
|
func human_readable_accessor(accessor: Array) -> String:
|
||||||
|
var parts: Array[Variant] = []
|
||||||
|
var ignore_parts: Array[Variant] = ["signals", "child_nodes", "root_nodes"]
|
||||||
|
for part in accessor:
|
||||||
|
if not part in ignore_parts:
|
||||||
|
parts.append(str(part))
|
||||||
|
return ".".join(parts)
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
[ext_resource type="Script" path="res://scenes/state/visualization/tree_visualizer.gd" id="1_62kxn"]
|
[ext_resource type="Script" path="res://scenes/state/visualization/tree_visualizer.gd" id="1_62kxn"]
|
||||||
|
|
||||||
[node name="TreeVisualizer" type="Window"]
|
[node name="TreeVisualizer" type="Window"]
|
||||||
position = Vector2i(50, 36)
|
position = Vector2i(20, 331)
|
||||||
size = Vector2i(1055, 560)
|
size = Vector2i(1115, 300)
|
||||||
script = ExtResource("1_62kxn")
|
script = ExtResource("1_62kxn")
|
||||||
|
|
||||||
[node name="GraphEdit" type="GraphEdit" parent="."]
|
[node name="GraphEdit" type="GraphEdit" parent="."]
|
||||||
|
@ -16,6 +16,7 @@ grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
size_flags_vertical = 3
|
size_flags_vertical = 3
|
||||||
|
scroll_offset = Vector2(-35, -30)
|
||||||
show_menu = false
|
show_menu = false
|
||||||
show_zoom_buttons = false
|
show_zoom_buttons = false
|
||||||
show_grid_buttons = false
|
show_grid_buttons = false
|
||||||
|
|
|
@ -3,10 +3,13 @@ extends GraphNode
|
||||||
|
|
||||||
var left_slots: Array[String] = []
|
var left_slots: Array[String] = []
|
||||||
var right_slots: Array[String] = []
|
var right_slots: Array[String] = []
|
||||||
|
var color_highlighted: StyleBoxFlat = StyleBoxFlat.new()
|
||||||
|
var color_normal: StyleBoxFlat = StyleBoxFlat.new()
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
pass
|
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:
|
func add_label(label_text: String, left: bool, right: bool) -> Vector3i:
|
||||||
|
@ -27,4 +30,12 @@ func add_label(label_text: String, left: bool, right: bool) -> Vector3i:
|
||||||
right_slots.append(label_text)
|
right_slots.append(label_text)
|
||||||
|
|
||||||
# the port index is counted separately from the left and right slots.
|
# the port index is counted separately from the left and right slots.
|
||||||
return Vector3i(child_index, left_slots.size() - 1, right_slots.size() - 1)
|
return Vector3i(child_index, left_slots.size() - 1, right_slots.size() - 1)
|
||||||
|
|
||||||
|
|
||||||
|
func set_highlighted(highlight: bool) -> void:
|
||||||
|
# set the background color of the node
|
||||||
|
if highlight:
|
||||||
|
self.add_theme_stylebox_override("panel", color_highlighted)
|
||||||
|
else:
|
||||||
|
self.add_theme_stylebox_override("panel", color_normal)
|
||||||
|
|
|
@ -14,7 +14,5 @@ offset_bottom = 184.0
|
||||||
mouse_filter = 1
|
mouse_filter = 1
|
||||||
theme_override_styles/panel = SubResource("StyleBoxFlat_k54lu")
|
theme_override_styles/panel = SubResource("StyleBoxFlat_k54lu")
|
||||||
theme_override_styles/titlebar = SubResource("StyleBoxFlat_dmqj2")
|
theme_override_styles/titlebar = SubResource("StyleBoxFlat_dmqj2")
|
||||||
draggable = false
|
|
||||||
selectable = false
|
|
||||||
title = "TITEL"
|
title = "TITEL"
|
||||||
script = ExtResource("1_o2ffa")
|
script = ExtResource("1_o2ffa")
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
class_name InfoPanel
|
class_name InfoPanel
|
||||||
extends VBoxContainer
|
extends VBoxContainer
|
||||||
|
|
||||||
var values: Dictionary = {}
|
var values: Dictionary = {}
|
||||||
var last_values: Dictionary = {}
|
var last_values: Dictionary = {}
|
||||||
var value_order: Array = [] # Keep track of the order of values
|
var value_order: Array = [] # Keep track of the order of values
|
||||||
|
|
||||||
|
|
||||||
func to_str(value) -> String:
|
func to_str(value) -> String:
|
||||||
if value is float:
|
if value is float:
|
||||||
|
@ -16,6 +17,7 @@ func to_str(value) -> String:
|
||||||
return "(" + to_str(value.x) + ", " + to_str(value.y) + ", " + to_str(value.z) + ", " + to_str(value.w) + ")"
|
return "(" + to_str(value.x) + ", " + to_str(value.y) + ", " + to_str(value.z) + ", " + to_str(value.w) + ")"
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
for child in self.get_children():
|
for child in self.get_children():
|
||||||
if child is Label:
|
if child is Label:
|
||||||
|
|
|
@ -10,98 +10,112 @@ var all_nodes: Array[DTreeNode] = []
|
||||||
var all_node_names_to_nodes: Dictionary = {}
|
var all_node_names_to_nodes: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
func _ready():
|
func _process(delta: float) -> void:
|
||||||
pass
|
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():
|
func build_tree():
|
||||||
if not state_machine:
|
if not state_machine:
|
||||||
push_error("No state machine set.")
|
push_error("No state machine set.")
|
||||||
return
|
return
|
||||||
|
|
||||||
var node_positions: Array[DTreeNode] = []
|
var node_positions: Array[DTreeNode] = []
|
||||||
var y_spacing: int = 400
|
var y_spacing: int = 300
|
||||||
var x_spacing: int = 400
|
var x_spacing: int = 600
|
||||||
var depths: Dictionary = calculate_node_depths()
|
var depths: Dictionary = calculate_node_depths()
|
||||||
|
|
||||||
var states = state_machine.state_machine_data["states"]
|
var states = state_machine.state_machine_data["states"]
|
||||||
|
|
||||||
# Place nodes
|
# Place nodes
|
||||||
for i in range(states.size()):
|
for i in range(states.size()):
|
||||||
var state_name = states.keys()[i]
|
var state_name = states.keys()[i]
|
||||||
var state_data = states[state_name]
|
var state_data = states[state_name]
|
||||||
|
|
||||||
var graph_node: DTreeNode = D_TREE_NODE.instantiate()
|
var graph_node: DTreeNode = D_TREE_NODE.instantiate()
|
||||||
graph_edit.add_child(graph_node)
|
graph_edit.add_child(graph_node)
|
||||||
graph_node.title = state_name
|
graph_node.title = state_name
|
||||||
|
|
||||||
all_nodes.append(graph_node)
|
all_nodes.append(graph_node)
|
||||||
all_node_names_to_nodes[state_name] = graph_node
|
all_node_names_to_nodes[state_name] = graph_node
|
||||||
|
|
||||||
# Connect nodes
|
# Connect nodes
|
||||||
for i in range(states.size()):
|
for i in range(states.size()):
|
||||||
var state_name = states.keys()[i]
|
var state_name = states.keys()[i]
|
||||||
var state_data = states[state_name]
|
var state_data = states[state_name]
|
||||||
var graph_node: DTreeNode = all_nodes[i]
|
var graph_node: DTreeNode = all_nodes[i]
|
||||||
|
|
||||||
# Set up labels (input and output)
|
# Set up labels (input and output)
|
||||||
populate_node_labels(graph_node, state_name, state_data, all_node_names_to_nodes)
|
populate_node_labels(graph_node, state_name, state_data, all_node_names_to_nodes)
|
||||||
|
|
||||||
# Calculate position based on tree depth
|
# Calculate position based on tree depth
|
||||||
var depth: int = depths[i]
|
var depth: int = depths[i]
|
||||||
var siblings: Array = get_nodes_at_depth(depth, depths)
|
var siblings: Array = get_nodes_at_depth(depth, depths)
|
||||||
@warning_ignore("integer_division")
|
@warning_ignore("integer_division")
|
||||||
var y_pos: int = y_spacing * (siblings.find(i) - (siblings.size() - 1) / 2)
|
var y_pos: int = y_spacing * (siblings.find(i) - (siblings.size() - 1) / 2)
|
||||||
var x_pos: int = x_spacing * depth
|
var x_pos: int = x_spacing * depth
|
||||||
y_pos += randi() % 200 - 100
|
y_pos += randi() % 200 - 100
|
||||||
graph_node.position_offset = Vector2(x_pos, y_pos)
|
graph_node.position_offset = Vector2(x_pos, y_pos)
|
||||||
node_positions.append(graph_node)
|
node_positions.append(graph_node)
|
||||||
|
|
||||||
|
|
||||||
func _on_graph_edit_connection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
|
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])
|
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:
|
# 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:
|
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
|
# 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", [])
|
var output_transitions: Array = state_data.get("transitions", [])
|
||||||
for transition in output_transitions:
|
for transition in output_transitions:
|
||||||
var target_state_name: String = transition['target']
|
var target_state_name: String = transition['target']
|
||||||
var port_out: Vector3i = node.add_label("%s" % [target_state_name], false, true)
|
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]
|
var target_node: DTreeNode = node_names_to_nodes[target_state_name]
|
||||||
if target_node:
|
if target_node:
|
||||||
var port_in: Vector3i = target_node.add_label("%s" % [state_name], true, false)
|
var port_in: Vector3i = target_node.add_label("%s" % [state_name], true, false)
|
||||||
|
|
||||||
# connect the nodes
|
# connect the nodes
|
||||||
graph_edit.connect_node(
|
graph_edit.connect_node(
|
||||||
node.get_name(), port_out.z,
|
node.get_name(), port_out.z,
|
||||||
target_node.get_name(), port_in.y
|
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])
|
print("Connecting %s [%d] to %s [%d]" % [node.get_name(), port_out, target_node.get_name(), port_in])
|
||||||
|
|
||||||
|
|
||||||
func calculate_node_depths() -> Dictionary:
|
func calculate_node_depths() -> Dictionary:
|
||||||
var depths: Dictionary = {}
|
var depths: Dictionary = {}
|
||||||
depths[0] = 0 # Root is at depth 0
|
depths[0] = 0 # Root is at depth 0
|
||||||
var state_names = state_machine.state_machine_data["states"].keys()
|
var state_names = state_machine.state_machine_data["states"].keys()
|
||||||
for i in range(state_names.size()):
|
for i in range(state_names.size()):
|
||||||
var state_name = state_names[i]
|
var state_name = state_names[i]
|
||||||
var state_data = state_machine.state_machine_data["states"][state_name]
|
var state_data = state_machine.state_machine_data["states"][state_name]
|
||||||
if state_data.has("transitions"):
|
if state_data.has("transitions"):
|
||||||
for transition in state_data["transitions"]:
|
for transition in state_data["transitions"]:
|
||||||
if transition.has("target"):
|
if transition.has("target"):
|
||||||
var target_state_name = transition["target"]
|
var target_state_name = transition["target"]
|
||||||
var target_state_index = state_names.find(target_state_name)
|
var target_state_index = state_names.find(target_state_name)
|
||||||
if target_state_index != -1:
|
if target_state_index != -1:
|
||||||
depths[target_state_index] = depths[i] + 1
|
depths[target_state_index] = depths[i] + 1
|
||||||
return depths
|
return depths
|
||||||
|
|
||||||
|
|
||||||
func get_nodes_at_depth(depth: int, depths: Dictionary) -> Array:
|
func get_nodes_at_depth(depth: int, depths: Dictionary) -> Array:
|
||||||
var nodes_at_depth: Array[Variant] = []
|
var nodes_at_depth: Array[Variant] = []
|
||||||
for i in depths.keys():
|
for i in depths.keys():
|
||||||
if depths[i] == depth:
|
if depths[i] == depth:
|
||||||
nodes_at_depth.append(i)
|
nodes_at_depth.append(i)
|
||||||
return nodes_at_depth
|
return nodes_at_depth
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue