2026-01-18 23:27:59 +01:00
|
|
|
@tool
|
2026-02-17 23:08:36 +01:00
|
|
|
|
2026-01-18 23:27:59 +01:00
|
|
|
extends Control
|
2026-02-17 23:08:36 +01:00
|
|
|
class_name BltBlendTreeEditor
|
2026-01-18 23:27:59 +01:00
|
|
|
|
2026-01-24 15:39:12 +01:00
|
|
|
@onready var blend_tree_graph_edit: GraphEdit = %BlendTreeGraphEdit
|
|
|
|
|
@onready var add_node_popup_menu: PopupMenu = %AddNodePopupMenu
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
signal edit_subgraph(blt_node:BLTAnimationNode)
|
|
|
|
|
signal graph_changed()
|
|
|
|
|
|
|
|
|
|
var blend_tree:BLTAnimationNodeBlendTree
|
|
|
|
|
|
2026-01-24 15:39:12 +01:00
|
|
|
var blend_tree_node_to_graph_node = {}
|
|
|
|
|
var graph_node_to_blend_tree_node = {}
|
2026-02-17 23:08:36 +01:00
|
|
|
|
2026-01-24 15:39:12 +01:00
|
|
|
var selected_nodes = {}
|
2026-02-17 23:08:36 +01:00
|
|
|
var last_selected_graph_node:GraphNode = null
|
|
|
|
|
var new_node_position:Vector2 = Vector2.ZERO
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
var registered_nodes = [
|
|
|
|
|
"BLTAnimationNodeSampler",
|
|
|
|
|
"BLTAnimationNodeBlend2",
|
|
|
|
|
"BLTAnimationNodeBlendTree",
|
|
|
|
|
]
|
2026-01-18 23:27:59 +01:00
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
|
2026-01-18 23:27:59 +01:00
|
|
|
func _ready() -> void:
|
2026-01-28 21:05:16 +01:00
|
|
|
add_node_popup_menu.clear(true)
|
|
|
|
|
|
2026-01-24 15:39:12 +01:00
|
|
|
for node_name in registered_nodes:
|
|
|
|
|
add_node_popup_menu.add_item(node_name)
|
2026-01-18 23:27:59 +01:00
|
|
|
|
|
|
|
|
|
2026-01-24 15:39:12 +01:00
|
|
|
func _reset_editor():
|
|
|
|
|
for child in blend_tree_graph_edit.get_children():
|
|
|
|
|
if child.name == "_connection_layer":
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
child.get_parent().remove_child(child)
|
|
|
|
|
child.queue_free()
|
|
|
|
|
|
|
|
|
|
blend_tree_graph_edit.clear_connections()
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
blend_tree = null
|
2026-01-24 15:39:12 +01:00
|
|
|
blend_tree_node_to_graph_node = {}
|
|
|
|
|
graph_node_to_blend_tree_node = {}
|
|
|
|
|
selected_nodes = {}
|
2026-02-17 23:08:36 +01:00
|
|
|
|
2026-01-24 15:39:12 +01:00
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
func edit_blend_tree(blt_blend_tree:BLTAnimationNodeBlendTree):
|
2026-01-28 21:05:16 +01:00
|
|
|
_reset_editor()
|
2026-02-17 23:08:36 +01:00
|
|
|
blend_tree = blt_blend_tree
|
|
|
|
|
blend_tree_graph_edit.scroll_offset = blend_tree.graph_offset
|
2026-01-28 21:05:16 +01:00
|
|
|
|
|
|
|
|
_update_editor_nodes_from_blend_tree()
|
|
|
|
|
_update_editor_connections_from_blend_tree()
|
|
|
|
|
|
2026-02-02 16:17:33 +01:00
|
|
|
|
2026-01-24 15:39:12 +01:00
|
|
|
func _update_editor_nodes_from_blend_tree():
|
|
|
|
|
for node_name in blend_tree.get_node_names():
|
|
|
|
|
var blend_tree_node:BLTAnimationNode = blend_tree.get_node(node_name)
|
2026-02-17 23:08:36 +01:00
|
|
|
var graph_node:GraphNode = create_graph_node_for_blt_node(blend_tree_node)
|
2026-01-24 15:39:12 +01:00
|
|
|
blend_tree_graph_edit.add_child(graph_node)
|
|
|
|
|
|
|
|
|
|
blend_tree_node_to_graph_node[blend_tree_node] = graph_node
|
|
|
|
|
graph_node_to_blend_tree_node[graph_node] = blend_tree_node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_editor_connections_from_blend_tree():
|
|
|
|
|
var connection_array = blend_tree.get_connections()
|
|
|
|
|
|
|
|
|
|
for i in range(len(connection_array) / 3):
|
|
|
|
|
var source_node:BLTAnimationNode = connection_array[i * 3]
|
|
|
|
|
var target_node:BLTAnimationNode = connection_array[i * 3 + 1]
|
|
|
|
|
var target_port = connection_array[i * 3 + 2]
|
|
|
|
|
|
|
|
|
|
var source_graph_node = blend_tree_node_to_graph_node[source_node]
|
|
|
|
|
|
|
|
|
|
var connect_result = blend_tree_graph_edit.connect_node(source_node.resource_name, 0, target_node.resource_name, target_node.get_input_index(target_port), true)
|
|
|
|
|
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
func create_graph_node_for_blt_node(blt_node: BLTAnimationNode) -> GraphNode:
|
|
|
|
|
var result_graph_node:GraphNode = GraphNode.new()
|
|
|
|
|
result_graph_node.name = blt_node.resource_name
|
|
|
|
|
result_graph_node.title = blt_node.resource_name
|
|
|
|
|
result_graph_node.position_offset = blt_node.position
|
|
|
|
|
|
|
|
|
|
var result_slot_offset = 0
|
|
|
|
|
|
|
|
|
|
if (blt_node.get_class() != "BLTAnimationNodeOutput"):
|
|
|
|
|
result_slot_offset = 1
|
|
|
|
|
var output_slot_label:Label = Label.new()
|
|
|
|
|
output_slot_label.text = "Result"
|
|
|
|
|
result_graph_node.add_child(output_slot_label)
|
|
|
|
|
result_graph_node.set_slot(0, false, 1, Color.WHITE, true, 1, Color.WHITE)
|
|
|
|
|
|
|
|
|
|
if blt_node.get_class() == "BLTAnimationNodeBlendTree":
|
|
|
|
|
result_graph_node.gui_input.connect(_on_node_gui_input.bind(result_graph_node))
|
|
|
|
|
|
|
|
|
|
var inputs = blt_node.get_input_names()
|
|
|
|
|
for i in range(len(inputs)):
|
|
|
|
|
var slot_label:Label = Label.new()
|
|
|
|
|
slot_label.text = inputs[i]
|
|
|
|
|
result_graph_node.add_child(slot_label)
|
|
|
|
|
result_graph_node.set_slot(i + result_slot_offset, true, 1, Color.WHITE, false, 1, Color.BLACK)
|
|
|
|
|
|
|
|
|
|
blt_node.node_changed.connect(_trigger_graph_changed)
|
|
|
|
|
|
|
|
|
|
return result_graph_node
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
func _trigger_graph_changed():
|
|
|
|
|
graph_changed.emit()
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
func _remove_node_connections(graph_node:GraphNode):
|
|
|
|
|
var node_connections:Array = []
|
2026-01-24 15:39:12 +01:00
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
for connection:Dictionary in blend_tree_graph_edit.connections:
|
|
|
|
|
if connection["from_node"] == graph_node.name or connection["to_node"] == graph_node.name:
|
|
|
|
|
node_connections.append(connection)
|
|
|
|
|
|
|
|
|
|
for node_connection:Dictionary in node_connections:
|
|
|
|
|
print("Removing connection %s" % str(node_connection))
|
|
|
|
|
blend_tree_graph_edit.disconnect_node(node_connection["from_node"], node_connection["from_port"], node_connection["to_node"], node_connection["to_port"])
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
#
|
|
|
|
|
# GraphEdit signal handling
|
|
|
|
|
#
|
2026-01-24 15:39:12 +01:00
|
|
|
func _on_blend_tree_graph_edit_connection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
|
|
|
|
|
print("Trying to connect '%s' port %d to node '%s' port %d" % [from_node, from_port, to_node, to_port])
|
|
|
|
|
|
|
|
|
|
var source_node:BLTAnimationNode = blend_tree.get_node(from_node)
|
|
|
|
|
var target_node:BLTAnimationNode = blend_tree.get_node(to_node)
|
|
|
|
|
|
|
|
|
|
if target_node == null:
|
|
|
|
|
push_error("Invalid connection, target node %s not found." % to_node)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var target_node_port_name = target_node.get_input_names()[to_port]
|
|
|
|
|
|
|
|
|
|
var connection_result = blend_tree.is_connection_valid(source_node, target_node, target_node_port_name)
|
|
|
|
|
if connection_result != blend_tree.CONNECTION_OK:
|
|
|
|
|
push_error("Could not add connection (error %d)" % connection_result)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
blend_tree.add_connection(source_node, target_node, target_node_port_name)
|
|
|
|
|
|
|
|
|
|
var connect_result = blend_tree_graph_edit.connect_node(from_node, from_port, to_node, to_port, true)
|
|
|
|
|
print("graph connect result: " + str(connect_result))
|
|
|
|
|
|
|
|
|
|
print("Success!")
|
|
|
|
|
|
|
|
|
|
|
2026-02-20 12:34:49 +01:00
|
|
|
func _on_blend_tree_graph_edit_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
|
|
|
|
|
var blend_tree_source_node = blend_tree.get_node(from_node)
|
|
|
|
|
var blend_tree_target_node = blend_tree.get_node(to_node)
|
|
|
|
|
var target_port_name = blend_tree_target_node.get_input_names()[to_port]
|
|
|
|
|
blend_tree.remove_connection(blend_tree_source_node, blend_tree_target_node, target_port_name)
|
|
|
|
|
|
|
|
|
|
blend_tree_graph_edit.disconnect_node(from_node, from_port, to_node, to_port)
|
|
|
|
|
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
func _on_blend_tree_graph_edit_delete_nodes_request(nodes: Array[StringName]) -> void:
|
|
|
|
|
for node_name:StringName in nodes:
|
|
|
|
|
print("remove node '%s'" % node_name)
|
|
|
|
|
var blend_tree_node:BLTAnimationNode = blend_tree.get_node(node_name)
|
|
|
|
|
|
|
|
|
|
if blend_tree_node == null:
|
|
|
|
|
push_error("Cannot delete node '%s': node not found." % node_name)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if blend_tree_node == blend_tree.get_output_node():
|
|
|
|
|
push_warning("Output node not allowed to be removed.")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
blend_tree_node.node_changed.disconnect(_trigger_graph_changed)
|
|
|
|
|
|
|
|
|
|
var graph_node:GraphNode = blend_tree_node_to_graph_node[blend_tree_node]
|
|
|
|
|
blend_tree.remove_node(blend_tree_node)
|
|
|
|
|
blend_tree_node_to_graph_node.erase(blend_tree_node)
|
|
|
|
|
|
|
|
|
|
_remove_node_connections(graph_node)
|
|
|
|
|
graph_node_to_blend_tree_node.erase(graph_node)
|
|
|
|
|
blend_tree_graph_edit.remove_child(graph_node)
|
|
|
|
|
_on_blend_tree_graph_edit_node_deselected(graph_node)
|
|
|
|
|
|
|
|
|
|
EditorInterface.get_inspector().edit(null)
|
|
|
|
|
|
|
|
|
|
|
2026-01-24 15:39:12 +01:00
|
|
|
func _on_blend_tree_graph_edit_end_node_move() -> void:
|
|
|
|
|
for graph_node:GraphNode in selected_nodes.keys():
|
2026-02-17 23:08:36 +01:00
|
|
|
graph_node_to_blend_tree_node[graph_node].position = graph_node.position_offset
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
func _on_blend_tree_graph_edit_node_deselected(graph_node: Node) -> void:
|
|
|
|
|
if selected_nodes.has(graph_node):
|
|
|
|
|
selected_nodes.erase(graph_node)
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
|
2026-01-28 21:05:16 +01:00
|
|
|
func _on_blend_tree_graph_edit_node_selected(graph_node: Node) -> void:
|
|
|
|
|
selected_nodes[graph_node] = graph_node
|
2026-02-17 23:08:36 +01:00
|
|
|
last_selected_graph_node = graph_node
|
2026-01-28 21:05:16 +01:00
|
|
|
EditorInterface.get_inspector().edit(graph_node_to_blend_tree_node[graph_node])
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
func _on_blend_tree_graph_edit_scroll_offset_changed(offset: Vector2) -> void:
|
2026-02-19 19:03:56 +01:00
|
|
|
if is_instance_valid(blend_tree):
|
|
|
|
|
blend_tree.graph_offset = offset
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
#
|
|
|
|
|
# AddNodePopupMenu
|
|
|
|
|
#
|
2026-01-24 15:39:12 +01:00
|
|
|
func _on_blend_tree_graph_edit_popup_request(at_position: Vector2) -> void:
|
|
|
|
|
add_node_popup_menu.position = get_screen_position() + get_local_mouse_position()
|
|
|
|
|
add_node_popup_menu.reset_size()
|
|
|
|
|
add_node_popup_menu.popup()
|
2026-01-28 21:05:16 +01:00
|
|
|
new_node_position = blend_tree_graph_edit.scroll_offset + at_position
|
2026-01-24 15:39:12 +01:00
|
|
|
|
2026-01-18 23:27:59 +01:00
|
|
|
|
2026-01-24 15:39:12 +01:00
|
|
|
func _on_add_node_popup_menu_index_pressed(index: int) -> void:
|
|
|
|
|
var new_blend_tree_node: BLTAnimationNode = ClassDB.instantiate(registered_nodes[index])
|
2026-02-02 16:17:33 +01:00
|
|
|
blend_tree.add_node(new_blend_tree_node)
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
var graph_node:GraphNode = create_graph_node_for_blt_node(new_blend_tree_node)
|
2026-02-02 16:17:33 +01:00
|
|
|
blend_tree_graph_edit.add_child(graph_node)
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
graph_node_to_blend_tree_node[graph_node] = new_blend_tree_node
|
|
|
|
|
blend_tree_node_to_graph_node[new_blend_tree_node] = graph_node
|
|
|
|
|
|
|
|
|
|
if new_node_position != Vector2.INF:
|
|
|
|
|
graph_node.position_offset = new_node_position
|
2026-02-17 23:08:36 +01:00
|
|
|
new_blend_tree_node.position = new_node_position
|
2026-01-24 15:39:12 +01:00
|
|
|
|
|
|
|
|
new_node_position = Vector2.INF
|
2026-01-28 21:05:16 +01:00
|
|
|
|
|
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
#
|
|
|
|
|
# Handle Node double click
|
|
|
|
|
#
|
|
|
|
|
func _on_node_gui_input(input_event:InputEvent, graph_node:GraphNode):
|
|
|
|
|
# print("Got input event on graph node %s!" % graph_node.name)
|
2026-01-29 23:21:38 +01:00
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
var mouse_button_event:InputEventMouseButton = input_event as InputEventMouseButton
|
|
|
|
|
if mouse_button_event and mouse_button_event.double_click:
|
|
|
|
|
_on_node_double_click(graph_node)
|
2026-01-29 23:21:38 +01:00
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
func _on_node_double_click(graph_node:GraphNode):
|
|
|
|
|
var blend_tree_node:BLTAnimationNode = graph_node_to_blend_tree_node[graph_node]
|
2026-01-28 21:05:16 +01:00
|
|
|
|
2026-02-17 23:08:36 +01:00
|
|
|
if blend_tree_node is BLTAnimationNodeBlendTree:
|
|
|
|
|
edit_subgraph.emit(blend_tree_node)
|