import tkinter as tk
class CanvasOperations:
def on_canvas_click(self, event):
item = self.canvas.find_withtag("current")
if item:
node = next((n for n in self.nodes if n["id"] == item[0] or n["text_id"] == item[0]), None)
if node:
if self.connecting:
self.finish_connecting(node)
else:
self.drag_data["item"] = item
self.drag_data["x"] = event.x
self.drag_data["y"] = event.y
def on_drag(self, event):
if self.drag_data["item"]:
dx = event.x - self.drag_data["x"]
dy = event.y - self.drag_data["y"]
self.canvas.move("current", dx, dy)
self.drag_data["x"] = event.x
self.drag_data["y"] = event.y
self.update_node_position(self.drag_data["item"][0], dx, dy)
self.redraw_edges()
def update_node_position(self, item_id, dx, dy):
for node in self.nodes:
if node["id"] == item_id or node["text_id"] == item_id:
node["x"] += dx
node["y"] += dy
self.canvas.coords(node["id"],
node["x"]-50*self.scale, node["y"]-20*self.scale,
node["x"]+50*self.scale, node["y"]+20*self.scale)
self.canvas.coords(node["text_id"], node["x"], node["y"])
break
def redraw_edges(self):
self.canvas.delete("edge")
for node in self.nodes:
for child in node["children"]:
self.draw_edge(node, child)
def on_release(self, event):
self.drag_data["item"] = None
def on_right_click(self, event):
item = self.canvas.find_withtag("current")
if item:
node = next((n for n in self.nodes if n["id"] == item[0] or n["text_id"] == item[0]), None)
if node:
self.show_context_menu(event, node)
def show_context_menu(self, event, node):
context_menu = tk.Menu(self.root, tearoff=0)
context_menu.add_command(label="Connect", command=lambda: self.start_connecting(node))
context_menu.add_command(label="Rename", command=lambda: self.rename_node(node))
context_menu.add_command(label="Edit Properties", command=lambda: self.edit_node_properties(node))
context_menu.add_command(label="Delete", command=lambda: self.delete_node(node))
context_menu.post(event.x_root, event.y_root)
def on_mousewheel(self, event):
# Zoom in/out with mouse wheel
if event.delta > 0:
self.scale *= 1.1
else:
self.scale /= 1.1
self.scale = max(0.1, min(self.scale, 5.0)) # Limit scale between 0.1 and 5.0
self.redraw_all()
5. connection_operations.py
功能: 包含与节点连接相关的函数,如开始连接、完成连接、绘制连接线等。
内容:
import tkinter as tk
from tkinter import messagebox
class ConnectionOperations:
def start_connecting(self, node):
self.connecting = True
self.connection_start = node
self.root.config(cursor="cross")
def finish_connecting(self, target_node):
if self.connection_start and self.connection_start != target_node:
if self.is_valid_connection(self.connection_start, target_node):
if target_node not in self.connection_start["children"]:
self.connection_start["children"].append(target_node)
self.redraw_edges()
else:
messagebox.showerror("Invalid Connection", "Cannot create a cycle in the tree structure.")
self.root.config(cursor="")
self.connecting = False
self.connection_start = None
def is_valid_connection(self, parent, child):
if child in parent["children"]:
return False
if self.is_ancestor(child, parent):
return False
return True
def is_ancestor(self, node, potential_descendant):
if not node["children"]:
return False
if potential_descendant in node["children"]:
return True
return any(self.is_ancestor(child, potential_descendant) for child in node["children"])
def draw_edge(self, parent, child):
start_x, start_y = parent["x"], parent["y"] + 20 * self.scale
end_x, end_y = child["x"], child["y"] - 20 * self.scale
mid_y = (start_y + end_y) / 2
self.canvas.create_line(start_x, start_y, start_x, mid_y, end_x, mid_y, end_x, end_y, smooth=True, arrow=tk.LAST, tags="edge", width=self.scale)
6. layout_operations.py
功能: 包含与自动布局相关的函数,如自动布局、子树布局等。
内容:
class LayoutOperations:
def auto_layout(self):
if not self.nodes:
return
root_nodes = [node for node in self.nodes if not any(node in parent["children"] for parent in self.nodes)]
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
x_spacing = canvas_width / (len(root_nodes) + 1)
y_spacing = 100 * self.scale
for i, root_node in enumerate(root_nodes):
self.layout_subtree(root_node, (i + 1) * x_spacing, 50, x_spacing, y_spacing)
self.redraw_all()
def layout_subtree(self, node, x, y, x_spacing, y_spacing):
node["x"], node["y"] = x, y
if not node["children"]:
return x
total_width = (len(node["children"]) - 1) * x_spacing
start_x = x - total_width / 2
for child in node["children"]:
child_x = self.layout_subtree(child, start_x, y + y_spacing, x_spacing / 2, y_spacing)
start_x += x_spacing
return x
def redraw_all(self):
self.canvas.delete("all")
for node in self.nodes:
self.draw_node(node)
self.redraw_edges()
7. xml_operations.py
功能: 包含与XML导入导出相关的函数,如导出XML、导入XML、解析XML节点等。
内容:
import xml.etree.ElementTree as ET
import xml.dom.minidom
from tkinter import filedialog, messagebox
class XmlOperations:
def export_xml(self):
root = ET.Element("BehaviorTree")
for node in self.nodes:
if not any(child for n in self.nodes for child in n["children"] if child == node):
self.add_xml_node(root, node)
tree = ET.ElementTree(root)
file_path = filedialog.asksaveasfilename(defaultextension=".xml")
if file_path:
xml_string = ET.tostring(root, encoding="unicode")
xml_string = xml_string.replace('<BehaviorTree>', '').replace('</BehaviorTree>', '')
dom = xml.dom.minidom.parseString(xml_string)
pretty_xml = dom.toprettyxml(indent=" ")
pretty_xml = pretty_xml.replace('<?xml version="1.0" ?>', '')
pretty_xml = pretty_xml.strip() # Remove leading and trailing whitespace
with open(file_path, 'w', encoding='utf-8') as f:
f.write(pretty_xml)
messagebox.showinfo("Export", f"Behavior Tree exported to {file_path}")
def add_xml_node(self, parent, node):
xml_node = ET.SubElement(parent, node["type"])
xml_node.set("class", node["class"])
xml_node.set("instance_name", node["instance_name"])
xml_node.set("input", node["input"])
xml_node.set("output", node["output"])
for child in node["children"]:
self.add_xml_node(xml_node, child)
def import_xml(self):
file_path = filedialog.askopenfilename(filetypes=[("XML files", "*.xml")])
if file_path:
with open(file_path, 'r', encoding='utf-8') as f:
xml_content = f"<BehaviorTree>{f.read()}</BehaviorTree>"
root = ET.fromstring(xml_content)
self.canvas.delete("all")
self.nodes = []
self.parse_xml_node(root, None, 50, 50)
self.auto_layout()
def parse_xml_node(self, xml_node, parent, x, y):
node = {
"type": xml_node.tag,
"name": xml_node.get("instance_name", xml_node.tag),
"class": xml_node.get("class", f"{xml_node.tag}Node"),
"instance_name": xml_node.get("instance_name", xml_node.tag),
"input": xml_node.get("input", " "),
"output": xml_node.get("output", " "),
"x": x,
"y": y,
"children": []
}
self.nodes.append(node)
self.draw_node(node)
if parent:
parent["children"].append(node)
self.draw_edge(parent, node)
for child_xml in xml_node:
self.parse_xml_node(child_xml, node, x, y)
return y
1、行为树可视化编辑器 2、行为树驱动的pacman游戏 3、pacman行为树xml
1.
main.py
功能: 主程序入口,负责初始化应用程序并启动主窗口。
内容:
2.
behavior_tree_editor.py
功能: 包含
BehaviorTreeEditor
类的定义,负责应用程序的整体结构和初始化。内容:
3.
node_operations.py
功能: 包含与节点操作相关的函数,如添加、删除、重命名、编辑节点属性等。
内容:
4.
canvas_operations.py
功能: 包含与画布操作相关的函数,如点击、拖动、释放、右键点击、缩放等。
内容:
5.
connection_operations.py
功能: 包含与节点连接相关的函数,如开始连接、完成连接、绘制连接线等。
内容:
6.
layout_operations.py
功能: 包含与自动布局相关的函数,如自动布局、子树布局等。
内容:
7.
xml_operations.py
功能: 包含与XML导入导出相关的函数,如导出XML、导入XML、解析XML节点等。
内容: