323 lines
9.6 KiB
C#
323 lines
9.6 KiB
C#
|
using UnityEditor;
|
|||
|
using UnityEngine;
|
|||
|
using System.Collections.Generic;
|
|||
|
|
|||
|
public class TechTreeEditorWindow : EditorWindow
|
|||
|
{
|
|||
|
private List<TechNode> nodes = new List<TechNode>(); // 所有节点
|
|||
|
private TechNode headnode;
|
|||
|
private Vector2 mousePosition;
|
|||
|
private TechNode selectedNode; // 当前选中的节点
|
|||
|
private TechNode nodeToConnect; // 用于保存待连接的节点
|
|||
|
|
|||
|
// TechTreeData 用于保存和加载的 ScriptableObject
|
|||
|
private TechTreeData techTreeData;
|
|||
|
|
|||
|
[MenuItem("Window/Tech Tree Editor")]
|
|||
|
public static void ShowWindow()
|
|||
|
{
|
|||
|
GetWindow<TechTreeEditorWindow>("Tech Tree Editor");
|
|||
|
}
|
|||
|
|
|||
|
private void OnGUI()
|
|||
|
{
|
|||
|
Event e = Event.current;
|
|||
|
mousePosition = e.mousePosition;
|
|||
|
|
|||
|
// 继续绘制节点和处理事件
|
|||
|
DrawNodes();
|
|||
|
ProcessNodeEvents(e);
|
|||
|
|
|||
|
if (e.button == 1 && e.type == EventType.MouseDown)
|
|||
|
{
|
|||
|
ShowContextMenu();
|
|||
|
}
|
|||
|
|
|||
|
// 检测点击空白区域以取消选中节点
|
|||
|
if (e.button == 0 && e.type == EventType.MouseDown && !IsMouseOverAnyNode(mousePosition))
|
|||
|
{
|
|||
|
// 取消选择
|
|||
|
if (nodeToConnect != null)
|
|||
|
{
|
|||
|
nodeToConnect.title = "节点"; // 恢复节点标题
|
|||
|
nodeToConnect = null;
|
|||
|
selectedNode = null;
|
|||
|
Debug.Log("取消选中");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 刷新窗口
|
|||
|
Repaint();
|
|||
|
}
|
|||
|
|
|||
|
private void OnEnable()
|
|||
|
{
|
|||
|
LoadTechTreeData();
|
|||
|
}
|
|||
|
|
|||
|
private void OnDisable()
|
|||
|
{
|
|||
|
SaveTechTreeData();
|
|||
|
}
|
|||
|
|
|||
|
private void DrawNodes()
|
|||
|
{
|
|||
|
foreach (TechNode node in nodes)
|
|||
|
{
|
|||
|
node.Draw();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 显示右键菜单
|
|||
|
private void ShowContextMenu()
|
|||
|
{
|
|||
|
GenericMenu menu = new GenericMenu();
|
|||
|
|
|||
|
if (selectedNode != null)
|
|||
|
{
|
|||
|
menu.AddItem(new GUIContent("Remove Node"), false, RemoveSelectedNode);
|
|||
|
menu.AddItem(new GUIContent("Remove Connections"), false, RemoveSelectedNodeConnections);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
menu.AddItem(new GUIContent("Add Node"), false, () => AddNode(mousePosition));
|
|||
|
}
|
|||
|
|
|||
|
menu.ShowAsContext();
|
|||
|
}
|
|||
|
|
|||
|
// 添加节点
|
|||
|
private void AddNode(Vector2 position)
|
|||
|
{
|
|||
|
TechNode newNode = new TechNode(position, 100, 50);
|
|||
|
if (nodes.Count == 0)
|
|||
|
{
|
|||
|
headnode = newNode;
|
|||
|
}
|
|||
|
nodes.Add(newNode);
|
|||
|
}
|
|||
|
|
|||
|
// 删除选中的节点
|
|||
|
private void RemoveSelectedNode()
|
|||
|
{
|
|||
|
if (selectedNode != null)
|
|||
|
{
|
|||
|
if (selectedNode == headnode)
|
|||
|
{
|
|||
|
// 不能删除头节点
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// 首先删除与其他节点的连接
|
|||
|
foreach (TechNode node in nodes)
|
|||
|
{
|
|||
|
node.RemoveConnection(selectedNode);
|
|||
|
}
|
|||
|
nodes.Remove(selectedNode);
|
|||
|
selectedNode = null;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 删除选中节点的所有连接
|
|||
|
private void RemoveSelectedNodeConnections()
|
|||
|
{
|
|||
|
if (selectedNode != null)
|
|||
|
{
|
|||
|
selectedNode.RemoveAllConnections();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 检查鼠标是否悬停在某个节点上
|
|||
|
private bool IsMouseOverAnyNode(Vector2 mousePos)
|
|||
|
{
|
|||
|
foreach (TechNode node in nodes)
|
|||
|
{
|
|||
|
if (node.rect.Contains(mousePos))
|
|||
|
{
|
|||
|
return true; // 鼠标在某个节点上
|
|||
|
}
|
|||
|
}
|
|||
|
return false; // 鼠标不在任何节点上
|
|||
|
}
|
|||
|
|
|||
|
// 处理节点事件(拖拽、连接、选择)
|
|||
|
private void ProcessNodeEvents(Event e)
|
|||
|
{
|
|||
|
foreach (TechNode node in nodes)
|
|||
|
{
|
|||
|
bool guiChanged = node.ProcessEvents(e);
|
|||
|
|
|||
|
if (guiChanged)
|
|||
|
{
|
|||
|
GUI.changed = true;
|
|||
|
}
|
|||
|
|
|||
|
// 处理右键选择
|
|||
|
if (e.type == EventType.MouseDown && e.button == 1 && node.rect.Contains(e.mousePosition))
|
|||
|
{
|
|||
|
selectedNode = node; // 选中节点
|
|||
|
}
|
|||
|
|
|||
|
// 处理左键点击的节点选择与连接
|
|||
|
if (e.type == EventType.MouseDown && e.button == 0 && node.rect.Contains(e.mousePosition))
|
|||
|
{
|
|||
|
Debug.LogWarning("鼠标位置" + e.mousePosition + "---" + node.rect);
|
|||
|
// 如果是第一次选择节点,标记为待连接
|
|||
|
if (nodeToConnect == null)
|
|||
|
{
|
|||
|
Debug.Log("已选择");
|
|||
|
nodeToConnect = node;
|
|||
|
node.title = "已选择";
|
|||
|
}
|
|||
|
else if (nodeToConnect != node && node.techItem != null && node.techItem != null)
|
|||
|
{
|
|||
|
// 如果选择了不同的节点,执行连接操作
|
|||
|
nodeToConnect.ConnectTo(node); // 支持多个父节点
|
|||
|
nodeToConnect.title = "节点";
|
|||
|
nodeToConnect = null; // 完成连接后清空选择
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#region 存储
|
|||
|
private void SaveTechTreeData()
|
|||
|
{
|
|||
|
// 确保文件夹存在
|
|||
|
string folderPath = "Assets/科技树/科技树窗口";
|
|||
|
if (!System.IO.Directory.Exists(folderPath))
|
|||
|
{
|
|||
|
System.IO.Directory.CreateDirectory(folderPath);
|
|||
|
}
|
|||
|
|
|||
|
// 检查是否有 techTreeData,如果没有则创建
|
|||
|
if (techTreeData == null)
|
|||
|
{
|
|||
|
techTreeData = CreateInstance<TechTreeData>();
|
|||
|
}
|
|||
|
|
|||
|
// 保存头节点的数据
|
|||
|
if (headnode != null)
|
|||
|
{
|
|||
|
techTreeData.headNode = SaveNodeData(headnode); // 递归保存头节点及其子节点
|
|||
|
}
|
|||
|
|
|||
|
// 保存 TechTreeData 资产文件
|
|||
|
string techTreeDataPath = $"{folderPath}/窗口数据.asset";
|
|||
|
if (!AssetDatabase.Contains(techTreeData))
|
|||
|
{
|
|||
|
AssetDatabase.CreateAsset(techTreeData, techTreeDataPath);
|
|||
|
}
|
|||
|
|
|||
|
AssetDatabase.SaveAssets();
|
|||
|
AssetDatabase.Refresh();
|
|||
|
|
|||
|
Debug.Log("科技树数据已成功保存!");
|
|||
|
}
|
|||
|
|
|||
|
// 递归保存节点及其子节点数据
|
|||
|
private TechNodeData SaveNodeData(TechNode node)
|
|||
|
{
|
|||
|
var nodeData = new TechNodeData
|
|||
|
{
|
|||
|
position = node.rect.position,
|
|||
|
title = node.title,
|
|||
|
techItemPath = AssetDatabase.GetAssetPath(node.techItem) // 保存 TechItem 的路径
|
|||
|
};
|
|||
|
|
|||
|
// 如果 TechItem 是新建的,保存它
|
|||
|
if (string.IsNullOrEmpty(nodeData.techItemPath) && node.techItem != null)
|
|||
|
{
|
|||
|
string techItemPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/科技树/科技树窗口/{node.techItem.itemName}.asset");
|
|||
|
AssetDatabase.CreateAsset(node.techItem, techItemPath);
|
|||
|
nodeData.techItemPath = techItemPath;
|
|||
|
}
|
|||
|
|
|||
|
// 递归保存子节点
|
|||
|
foreach (var childNode in node.children)
|
|||
|
{
|
|||
|
var childNodeData = SaveNodeData(childNode); // 递归调用
|
|||
|
nodeData.children.Add(childNodeData);
|
|||
|
}
|
|||
|
|
|||
|
return nodeData;
|
|||
|
}
|
|||
|
|
|||
|
private void LoadTechTreeData()
|
|||
|
{
|
|||
|
string assetPath = "Assets/科技树/科技树窗口/窗口数据.asset";
|
|||
|
|
|||
|
techTreeData = AssetDatabase.LoadAssetAtPath<TechTreeData>(assetPath);
|
|||
|
if (techTreeData != null && techTreeData.headNode != null)
|
|||
|
{
|
|||
|
// 通过保存的头节点数据加载头节点
|
|||
|
headnode = LoadNodeData(techTreeData.headNode);
|
|||
|
|
|||
|
Debug.Log("科技树数据已加载成功!");
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Debug.LogWarning("TechTreeData 资产不存在!");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 递归加载节点及其子节点
|
|||
|
private TechNode LoadNodeData(TechNodeData nodeData)
|
|||
|
{
|
|||
|
var newNode = new TechNode(nodeData.position, 100, 50)
|
|||
|
{
|
|||
|
title = nodeData.title
|
|||
|
};
|
|||
|
|
|||
|
// 加载对应的 TechItem
|
|||
|
if (!string.IsNullOrEmpty(nodeData.techItemPath))
|
|||
|
{
|
|||
|
newNode.techItem = AssetDatabase.LoadAssetAtPath<TechItem>(nodeData.techItemPath);
|
|||
|
|
|||
|
if (newNode.techItem != null)
|
|||
|
{
|
|||
|
// 清空旧的父子关系
|
|||
|
newNode.techItem.parent = new List<TechItem>();
|
|||
|
newNode.techItem.children.Clear();
|
|||
|
|
|||
|
// 恢复 TechItem 的父对象
|
|||
|
foreach (var parentPath in nodeData.parents)
|
|||
|
{
|
|||
|
TechItem parentTechItem = AssetDatabase.LoadAssetAtPath<TechItem>(parentPath);
|
|||
|
if (parentTechItem != null)
|
|||
|
{
|
|||
|
newNode.techItem.parent.Add(parentTechItem);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 恢复 TechItem 的子对象
|
|||
|
List<TechItem> validChildren = new List<TechItem>();
|
|||
|
foreach (var childTechItem in newNode.techItem.children)
|
|||
|
{
|
|||
|
string childPath = AssetDatabase.GetAssetPath(childTechItem);
|
|||
|
TechItem childItem = AssetDatabase.LoadAssetAtPath<TechItem>(childPath);
|
|||
|
if (childItem != null)
|
|||
|
{
|
|||
|
validChildren.Add(childItem);
|
|||
|
}
|
|||
|
}
|
|||
|
newNode.techItem.children = validChildren; // 更新 TechItem 的子节点列表
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
nodes.Add(newNode);
|
|||
|
|
|||
|
// 递归加载子节点
|
|||
|
foreach (var childNodeData in nodeData.children)
|
|||
|
{
|
|||
|
var childNode = LoadNodeData(childNodeData); // 递归调用
|
|||
|
newNode.ConnectTo(childNode); // 恢复父子关系
|
|||
|
}
|
|||
|
|
|||
|
return newNode;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|