前言
在unity開發游戲編輯器擴展中,我們常常會用到節點編輯器,節點編輯器就是用節點和節點連接的圖形話編輯器,比如unity自帶的shadergraph就是一個節點編輯器,用戶通過節點之間的拖拖拉拉,連線就能得到想要的結果,
在一段時間里,打算做一個迷宮的地圖編輯器,每個地圖有上下左右4個門,可以和其他地圖連接,于是打算開發一個編輯器,通過可視化的方式去生成整個迷宮的資料,查了很多的節點插件,xnode作為輕量級的節點編輯器就非常適合用來做二次開發
Xnode(https://github.com/Siccity/xNode)是開源的輕量級的節點編輯器插件,所以理解和開發起來非常方便
最終開發效果如下,可以在視窗內創建節點,設定四個門的跳轉關系,最終點擊匯出保存為一個指定的資料檔案

安裝Xnode
從github下載這個插件,解壓出來放在Assets目錄下即可
不過這里我做了一點點改動,考慮到只想要xnode的編輯器功能,不打算打包的時候把xnode的代碼帶到最終的包里面,所以把runtime的代碼全部放到了Editor目錄里面,這樣就可以保證打包時候,我們游戲的工程是和xnode相互獨立的,
如圖所示,現在工程里面代碼全部在Editor下面

定義圖和節點
xnode是非常輕量級的非常易開發的,它的概念就是圖Graph,節點Node,埠Port,埠我們可以先不關心,先來定義一下圖
[CreateAssetMenu(fileName = "mazelayer", menuName = "UmGame/場景/迷宮編輯器資料")] public class MazeLayerGraph : NodeGraph
{
}
就是這么簡單,繼承一下NodeGraph即可,
這里給MazeLayerGraph加了一個CreateAssetMenu標簽,這樣就可以在Project面板中,通過右鍵選單的方式創建出這個圖檔案,

創建之后,就可以通過雙擊這個檔案或者在Inspector面板中點擊Open或者點擊EditGraph都可以打開

打開的界面什么都沒有,右鍵選單里面也沒什么東西

這是因為還沒有定義節點Node,所以開始定義一下Node
namespace Game { public class MazeLayerRoomNode : Node { public Texture2D Tex { get; set; } public int RoomID; public string TerrianID; public string RoomName; } }
也很簡單,繼承Node即可,這里因為后面打算要畫一個迷宮房間的圖,還要記錄房間的ID,所以定義了一些變數,回到unity,右鍵就可以看到有一個選單了

選擇這個選單就可以創建出一個節點了

但是這些資料不是我想在圖中展示的,所以打算重寫節點的繪制,
Node重繪
xnode中實作Node重繪非常簡單,只要類似unity寫一個CustomEditor就可以,對二次開發非常友好,
namespace Game {
[CustomNodeEditor(typeof(MazeLayerRoomNode))]
public class MazeLayerRoomNodeEditor : NodeEditor
{
public override void OnHeaderGUI()
{
var tar = (MazeLayerRoomNode)target;
var head = tar.RoomID.ToString();
if (!string.IsNullOrEmpty(tar.RoomName))
head += ":" + tar.RoomName;
EditorGUILayout.LabelField(head, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30));
}
public override int GetWidth()
{
return 115;
}
public Rect[] Points = new Rect[4];
public override void OnBodyGUI()
{
var tar = (MazeLayerRoomNode)target;
var width = GetWidth();
if (tar.Tex == null)
tar.Tex = AssetDatabase.LoadAssetAtPath<Texture2D>(
$"Assets/Game/Art/Map/levelmap/{tar.TerrianID}.png");
GUILayout.Label("", GUILayout.Width(width - 20), GUILayout.Height(210 / 2f));
var r = new Rect(20, 35, 150 / 2f, 210 / 2f);
if (tar.Tex != null)
EditorGUI.DrawPreviewTexture(r, tar.Tex);
else
EditorGUI.DrawRect(r, Color.white);
var height = GUILayoutUtility.GetLastRect().yMax;
EditorGUILayout.LabelField("", GUILayout.Height(height));
var rect = new Rect(0, 0, 16, 16);
foreach (var port in target.DynamicPorts)
{
var rport = port;
var strs = rport.fieldName.Split('_');
if (strs.Length != 2)
continue;
var doorid = (EmDoorID)int.Parse(strs[0]);
switch (doorid)
{
case EmDoorID.Bottom:
rect.y = height;
if (rport.IsInput)
rect.x = width / 2f + 8;
else
rect.x = width / 2f - 8 - 16;
break;
case EmDoorID.Left:
rect.x = 0;
if (rport.IsInput)
rect.y = height / 2f - 8 - 16;
else
rect.y = height / 2f + 8;
rect.y += 15;
break;
case EmDoorID.Right:
rect.x = width - 16;
if (rport.IsInput)
rect.y = height / 2f + 8;
else
rect.y = height / 2f - 8 - 16;
rect.y += 15;
break;
case EmDoorID.Top:
rect.y = 0;
if (rport.IsInput)
rect.x = width / 2f - 8 - 16;
else
rect.x = width / 2f + 8;
break;
default:
throw new ArgumentOutOfRangeException();
}
var color = rport.IsInput ? Color.red : Color.green;
NodeEditorGUILayout.DrawPortHandle(rect, Color.black, color);
portPositions[rport] = rect.center;
}
}
public override void AddContextMenuItems(GenericMenu menu)
{
base.AddContextMenuItems(menu);
var tar = (MazeLayerRoomNode)target;
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node)
{
menu.AddItem(new GUIContent("增加門/↑上"), false, () => tar.AddDoor(EmDoorID.Top));
menu.AddItem(new GUIContent("增加門/↓下"), false, () => tar.AddDoor(EmDoorID.Bottom));
menu.AddItem(new GUIContent("增加門/上下"), false, () =>
{
tar.AddDoor(EmDoorID.Bottom);
tar.AddDoor(EmDoorID.Top);
});
menu.AddItem(new GUIContent("增加門/←左"), false, () => tar.AddDoor(EmDoorID.Left));
menu.AddItem(new GUIContent("增加門/→右"), false, () => tar.AddDoor(EmDoorID.Right));
menu.AddItem(new GUIContent("增加門/左右"), false, () =>
{
tar.AddDoor(EmDoorID.Right);
tar.AddDoor(EmDoorID.Left);
});
menu.AddItem(new GUIContent("增加門/上下左右"), false, () =>
{
tar.AddDoor(EmDoorID.Bottom);
tar.AddDoor(EmDoorID.Top);
tar.AddDoor(EmDoorID.Right);
tar.AddDoor(EmDoorID.Left);
});
}
}
}
}
1、繼承NodeEditor,比用CustomNodeEditor和我們自定義的Node相關聯
2、復寫了OnHeaderGUI,讓節點頭部顯示房間的ID和名稱,這樣查看比較方便
3、復寫GetWidth,因為我知道畫的圖的大小,其他不想顯示,所以固定為115
4、復寫OnBodyGUI,這個是重點,因為在節點畫出圖,所以在這個函式里面,先去AssetDataBase里面找到圖,然后使用DrawPreviewTexture畫出來,最后我要畫出門的出入口,所以使用Node的DynamicPorts存盤門的資料,最后把這些port的位置記錄到portPositions陣列中,這樣到時候去就可以去做連線了,
5、復寫AddContextMenuItems,在節點的右鍵選單的時候加上一些動態加門的方法

如圖,添加4個門之后,就在節點周圍畫出了4個入口和4個出口
多加幾個節點,然后連接起來

這個曲線不怎么滿意,怎么辦,
Graph重繪
在xnode中,不僅Node可以重繪,Graph也可以
[CustomNodeGraphEditor(typeof(MazeLayerGraph))] public class MazeLayerGraphEditor : NodeGraphEditor {
public override NoodlePath GetNoodlePath(NodePort output, NodePort input)
{
return NoodlePath.Straight;
}
}類似的,我們只要繼承NodeGraphEditor,并用CustomNodeGraphEditor和我們自定義的Graph關聯就可以了,
為了實作曲線變直線,我們只要復寫GetNoodlePath即可,NoodlePath.Straight表示直線

OK,那大部分就完成了,最后就是保存了,我們在MazeLayerGraphEditor中的OnGUI可以寫一個buttom,然后把相關資料寫到自定義的資料就可以了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302930.html
標籤:其他
上一篇:HCNP Routing&Switching之路由控制-策略路由
下一篇:華為云計算IE面試筆記-桌面云中的用戶組、虛擬機模板、模板虛擬機、虛擬機組和桌面組的關系及區別。發放完整復制和鏈接克隆虛擬機時,步驟有什么區別,要怎么選擇桌面組?
