关键词:UnityEditor、可视化节点编辑、Unity编辑器自定义窗口工具
使用Newtonsoft.Json、UnityEditor相关接口实现
主要代码:
Handles.DrawBezier(起点,终点,起点切线向量,终点切线向量,颜色,null, 线粗度) 绘制贝塞尔曲线
Handles.DrawAAPolyLine(线粗度,顶点1, 顶点2, ...) 根据线段点绘制不规则线段
GUI.Window(id, rect, DrawNodeWindow, 窗口标题);
void DrawNodeWindow(int id) 传入的id即GUI.Window第一个参数,一般传节点唯一标识ID。
LinkObj类是节点类,里面有一个位置pos数据是存储该节点窗口位于编辑器的位置SerializableVector2类型是一个可被Json序列化的Vector2,不然无法被序列化。
cs
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
public class LinkObj
{
public int id;
public List<int> preList;
public List<int> nextList;
public SerializableVector2 pos;//位于编辑窗口的位置
public LinkObj(int _id, SerializableVector2 _pos)
{
id = _id;
pos = _pos;
preList = new List<int>();
nextList = new List<int>();
}
}
public static class PathConfig
{
public static string SaveJsonPath = Application.dataPath + "/MyGraphicsEditorDemo/Editor/LinkList.json";
}
public class MyGraphicsEditorWindow : EditorWindow
{
private Dictionary<int, LinkObj> linkObjDict = new Dictionary<int, LinkObj>();
private int tempAddId;
private Vector2 scrollViewPos;
private Dictionary<int, Rect> linkObjRectDict = new Dictionary<int, Rect>();
private LinkObj currentSelectLinkObj;
private Color defaultColor;
private Vector2 detailScrollViewPos;
private bool isLoaded;
[MenuItem("Tools/可视链结构编辑器")]
private static void ShowWindow()
{
Debug.Log("打开可视链结构编辑器");
var window = EditorWindow.GetWindow(typeof(MyGraphicsEditorWindow)) as MyGraphicsEditorWindow;
window.minSize = new Vector2(1280, 500);
window.Show(true);
window.isLoaded = false;
}
MyGraphicsEditorWindow()
{
this.titleContent = new GUIContent("可视链结构编辑器");
}
private void OnGUI()
{
defaultColor = GUI.color;
EditorGUILayout.BeginHorizontal(GUILayout.Width(position.width), GUILayout.Height(position.height));
{
//左面板(操作)
EditorGUILayout.BeginVertical(GUILayout.Width(80));
{
EditorGUILayout.BeginHorizontal();
{
if (GUILayout.Button("加载"))
{
//如果本地没有对应的json 文件,重新创建
if (File.Exists(PathConfig.SaveJsonPath))
{
string json = File.ReadAllText(PathConfig.SaveJsonPath);
linkObjDict = JsonConvert.DeserializeObject<Dictionary<int, LinkObj>>(json);
if (linkObjDict == null)
{
linkObjDict = new Dictionary<int, LinkObj>();
}
isLoaded = true;
}
else
{
isLoaded = false;
Debug.LogError("加载失败,尚未存在json文件:" + PathConfig.SaveJsonPath);
}
}
bool isExistFile = File.Exists(PathConfig.SaveJsonPath);
if ((isLoaded || !isExistFile) && GUILayout.Button("保存"))
{
//如果本地没有对应的json 文件,重新创建
if (!isExistFile)
{
File.Create(PathConfig.SaveJsonPath);
}
AssetDatabase.Refresh();
string json = JsonConvert.SerializeObject(linkObjDict);
File.WriteAllText(PathConfig.SaveJsonPath, json);
Debug.Log("保存成功:" + json);
AssetDatabase.SaveAssets();
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginVertical();
{
if (GUILayout.Button("添加节点"))
{
LinkObj obj = new LinkObj(tempAddId, new SerializableVector2(scrollViewPos));
if (!linkObjDict.ContainsKey(tempAddId))
{
linkObjDict.Add(tempAddId, obj);
}
else
{
Debug.LogError("节点ID已存在:" + tempAddId);
}
}
tempAddId = int.Parse(EditorGUILayout.TextField("节点ID", tempAddId.ToString()));
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndVertical();
//中间面板(可视节点)
EditorGUILayout.BeginVertical(GUILayout.Width(position.width - 500));
{
EditorGUILayout.LabelField(string.Format("所有链节点"), EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal("box", GUILayout.Height(position.height));
{
scrollViewPos = EditorGUILayout.BeginScrollView(scrollViewPos, GUILayout.Height(position.height));
{
BeginWindows();
if (linkObjDict != null && linkObjDict.Count > 0)
{
foreach (var item in linkObjDict)
{
int id = item.Key;
var linkObj = item.Value;
Rect oRect;
if (!linkObjRectDict.TryGetValue(id, out oRect))
{
Rect windowRect = new Rect(180, 50, 180, 100);
windowRect.x = linkObj.pos.x;
windowRect.y = linkObj.pos.y;
linkObjRectDict.Add(id, windowRect);
}
string str = string.Format("{0}-[节点]", id);
if (currentSelectLinkObj != null && currentSelectLinkObj.id == id)
GUI.color = Color.yellow;
else if (currentSelectLinkObj != null && currentSelectLinkObj.preList.Exists((int x) => x == id))
GUI.color = Color.blue;
else if (currentSelectLinkObj != null && currentSelectLinkObj.nextList.Exists((int x) => x == id))
GUI.color = Color.green;
//绘画窗口
linkObjRectDict[id] = GUI.Window(id, linkObjRectDict[id], DrawNodeWindow, str);
GUI.color = defaultColor;
foreach (int nextId in linkObj.nextList)
{
Rect nextRect;
if (linkObjRectDict.TryGetValue(nextId, out nextRect))
{
DrawNodeCurve(linkObjRectDict[id], nextRect, Color.red);
}
}
}
}
EndWindows();
}
EditorGUILayout.EndScrollView();
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
//右面板(编辑选中节点)
EditorGUILayout.BeginVertical(GUILayout.Width(250));
{
EditorGUILayout.LabelField("节点属性", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal("box", GUILayout.Height(position.height));
{
EditorGUILayout.BeginVertical();
{
detailScrollViewPos = EditorGUILayout.BeginScrollView(detailScrollViewPos, GUILayout.Height(position.height));
{
if (currentSelectLinkObj != null)
{
DrawCurrentLinkObj();
}
}
EditorGUILayout.EndScrollView();
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndHorizontal();
}
//绘画窗口函数
private void DrawNodeWindow(int id)
{
EditorGUILayout.LabelField(string.Format("节点ID:{0}", linkObjDict[id].id), EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
{
//创建一个GUI Button
if (GUILayout.Button("选择"))
{
currentSelectLinkObj = linkObjDict[id];
}
GUI.color = Color.red;
if (GUILayout.Button("删除"))
{
if (EditorUtility.DisplayDialog("询问", "确认删除?", "确认", "取消"))
{
linkObjDict.Remove(id);
linkObjRectDict.Remove(id);
return;
}
}
GUI.color = defaultColor;
}
EditorGUILayout.EndHorizontal();
//设置改窗口可以拖动
GUI.DragWindow();
var oItem = linkObjDict[id];
Rect oRect;
if (oItem != null && linkObjRectDict.TryGetValue(id, out oRect))
{
oItem.pos = new SerializableVector2(linkObjRectDict[id].position);
}
}
//***描绘连线
private void DrawNodeCurve(Rect start, Rect end, Color color, float fValue = 4)
{
//根据不同相对位置决定线条的起点和终点 (看似复杂,实际简单,可优化写法)
float startX, startY, endX, endY;
//start左 end右时, 起点是start右侧中点, 终点是end左侧中点
if (start.x < end.x && Mathf.Abs(start.x + start.width / 2 - end.x - end.width / 2) > 50)
{ startX = start.x + start.width; endX = end.x; startY = start.y + start.height / 2; endY = end.y + end.height / 2; }
//start右 end左时, 起点是start左侧中点, 终点是end右侧中点
else if (start.x >= end.x && Mathf.Abs(start.x + start.width / 2 - end.x - end.width / 2) > 50)
{ startX = start.x; endX = end.x + end.width; startY = start.y + start.height / 2; endY = end.y + end.height / 2; }
else
{
//start上 end下时, 起点是start下侧中点, 终点是end上侧中点
if (start.y > end.y)
{ startX = start.x + start.width / 2; startY = start.y; endX = end.x + end.width / 2; endY = end.y + end.height; }
//start下 end上时, 起点是start上侧中点, 终点是end下侧中点
else
{ startX = start.x + start.width / 2; startY = start.y + start.height; endX = end.x + end.width / 2; endY = end.y; }
}
Vector3 startPos = new Vector3(startX, startY, 0);
Vector3 endPos = new Vector3(endX, endY, 0);
//根据起点和终点偏向给出不同朝向的Tan切线
Vector3 startTan, endTan;
if (start.x < end.x)
{
startTan = startPos + Vector3.right * 50;
endTan = endPos + Vector3.left * 50;
}
else
{
startTan = startPos + Vector3.left * 50;
endTan = endPos + Vector3.right * 50;
}
//绘制线条 color颜色 fValue控制粗细
Handles.DrawBezier(startPos, endPos, startTan, endTan, color, null, fValue);
//绘制线条终点的2条斜线 形成箭头
Handles.color = color;
Vector2 to = endPos;
Vector2 v1, v2;
//与上方大同小异,根据相对位置得出不同的箭头线段点
if (start.x < end.x && Mathf.Abs(start.x + start.width / 2 - end.x - end.width / 2) > 50)
{
v1 = new Vector2(-8f, 8f);
v2 = new Vector2(-8f, -8f);
}
else if (start.x >= end.x && Mathf.Abs(start.x + start.width / 2 - end.x - end.width / 2) > 50)
{
v1 = new Vector2(8f, 8f);
v2 = new Vector2(8f, -8f);
}
else
{
if (start.y > end.y)
{
v1 = new Vector2(-8f, 8f);
v2 = new Vector2(8f, 8f);
}
else
{
v1 = new Vector2(-8f, -8f);
v2 = new Vector2(8f, -8f);
}
}
//fValue粗细绘制由3个点构成的线段形成箭头
Handles.DrawAAPolyLine(fValue, to + v1, to, to + v2);
}
// 当前选中节点详情编辑页面
private void DrawCurrentLinkObj()
{
EditorGUILayout.LabelField(string.Format("节点ID:{0}", currentSelectLinkObj.id), EditorStyles.boldLabel);
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("下一个节点");
DrawListMember(currentSelectLinkObj.nextList);
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("上一个节点");
DrawListMember(currentSelectLinkObj.preList);
}
//列表显示
private void DrawListMember(List<int> lst, bool isOnlyRead = false)
{
EditorGUILayout.BeginVertical();
{
if (lst.Count != 0)
{
for (int i = 0; i < lst.Count; i++)
{
EditorGUILayout.BeginHorizontal();
{
GUILayout.Label((i + 1).ToString(), GUILayout.Width(25));
lst[i] = EditorGUILayout.IntField(lst[i]);
GUI.color = Color.red;
if (GUILayout.Button("-", GUILayout.Width(30)))
{
lst.RemoveAt(i);
}
GUI.color = defaultColor;
}
EditorGUILayout.EndHorizontal();
}
}
if (GUILayout.Button("+"))
{
lst.Add(lst.Count);
}
EditorGUILayout.EndVertical();
}
}
}
cs
using Newtonsoft.Json;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class SerializableVector2
{
public float x;
public float y;
[JsonIgnore]
public Vector2 UnityVector
{
get
{
return new Vector2(x, y);
}
}
public SerializableVector2(Vector2 v)
{
x = v.x;
y = v.y;
}
public static List<SerializableVector2> GetSerializableList(List<Vector2> vList)
{
List<SerializableVector2> list = new List<SerializableVector2>(vList.Count);
for (int i = 0; i < vList.Count; i++)
{
list.Add(new SerializableVector2(vList[i]));
}
return list;
}
public static List<Vector2> GetSerializableList(List<SerializableVector2> vList)
{
List<Vector2> list = new List<Vector2>(vList.Count);
for (int i = 0; i < vList.Count; i++)
{
list.Add(vList[i].UnityVector);
}
return list;
}
}