Unity3D 基于GraphView實(shí)現(xiàn)的節(jié)點(diǎn)編輯器框架詳解

前言

在Unity3D游戲開發(fā)中,節(jié)點(diǎn)編輯器是一種強(qiáng)大的工具,它允許開發(fā)者以可視化的方式創(chuàng)建和編輯復(fù)雜的邏輯和流程。Unity提供了一個(gè)強(qiáng)大的UI工具包——GraphView,它使得創(chuàng)建自定義節(jié)點(diǎn)編輯器變得相對(duì)簡單。本文將詳細(xì)介紹如何使用GraphView實(shí)現(xiàn)一個(gè)節(jié)點(diǎn)編輯器框架,并提供技術(shù)詳解和代碼實(shí)現(xiàn)。

對(duì)惹,這里有一個(gè)游戲開發(fā)交流小組,希望大家可以點(diǎn)擊進(jìn)來一起交流一下開發(fā)經(jīng)驗(yàn)呀!

一、GraphView簡介

GraphView是Unity提供的一個(gè)用于創(chuàng)建節(jié)點(diǎn)編輯器的UI組件。它允許開發(fā)者以圖形化的方式展示和編輯節(jié)點(diǎn)及其連接。GraphView提供了豐富的API,使得開發(fā)者可以輕松地自定義節(jié)點(diǎn)、邊、面板和工具欄等。

二、節(jié)點(diǎn)編輯器框架設(shè)計(jì)

在創(chuàng)建一個(gè)節(jié)點(diǎn)編輯器框架時(shí),我們需要考慮以下幾個(gè)關(guān)鍵部分:

  1. 節(jié)點(diǎn)(Node):節(jié)點(diǎn)是編輯器中的基本元素,它代表了一個(gè)可以執(zhí)行特定操作的單元。每個(gè)節(jié)點(diǎn)都應(yīng)該有一個(gè)唯一的標(biāo)識(shí)符、一個(gè)標(biāo)題、一個(gè)或多個(gè)輸入/輸出端口,以及用于顯示和操作節(jié)點(diǎn)的UI元素。
  2. 邊(Edge):邊用于連接節(jié)點(diǎn),表示節(jié)點(diǎn)之間的數(shù)據(jù)流或邏輯依賴關(guān)系。在GraphView中,邊通常由兩個(gè)端口(一個(gè)輸入端口和一個(gè)輸出端口)組成。
  3. 面板(Panel):面板是節(jié)點(diǎn)的容器,它提供了用于添加、刪除和移動(dòng)節(jié)點(diǎn)的界面。面板還可以包含工具欄、小地圖等輔助工具。
  4. 工具欄(Toolbar):工具欄提供了用于創(chuàng)建新節(jié)點(diǎn)、保存和加載編輯器狀態(tài)、撤銷和重做操作等功能的按鈕和菜單。
  5. 數(shù)據(jù)存儲(chǔ):為了持久化編輯器狀態(tài),我們需要將節(jié)點(diǎn)的數(shù)據(jù)和連接關(guān)系存儲(chǔ)在一個(gè)可序列化的數(shù)據(jù)結(jié)構(gòu)中。在Unity中,ScriptableObject是一個(gè)常用的選擇。

三、技術(shù)詳解

  1. 創(chuàng)建節(jié)點(diǎn)和邊
  • 節(jié)點(diǎn)可以通過繼承GraphView的Node類來創(chuàng)建。在節(jié)點(diǎn)類中,我們需要重寫B(tài)uildContextualMenu方法來添加右鍵菜單項(xiàng),如添加輸入/輸出端口、刪除節(jié)點(diǎn)等。
  • 邊可以通過GraphView的Edge類來創(chuàng)建。在創(chuàng)建邊時(shí),我們需要指定邊的輸入和輸出端口,并處理邊的繪制和連接邏輯。
  1. 管理節(jié)點(diǎn)和邊的數(shù)據(jù)
  • 我們可以使用ScriptableObject來存儲(chǔ)節(jié)點(diǎn)的數(shù)據(jù)和連接關(guān)系。每個(gè)節(jié)點(diǎn)可以有一個(gè)對(duì)應(yīng)的ScriptableObject來存儲(chǔ)其特定的數(shù)據(jù)。
  • 連接關(guān)系可以通過存儲(chǔ)邊的輸入和輸出端口的標(biāo)識(shí)符來表示。
  1. 實(shí)現(xiàn)撤銷和重做功能
  • 撤銷和重做功能可以通過維護(hù)一個(gè)操作歷史記錄來實(shí)現(xiàn)。每次對(duì)編輯器進(jìn)行更改時(shí),都可以將更改作為一個(gè)操作添加到歷史記錄中。
  • 撤銷操作可以回滾到歷史記錄中的上一個(gè)狀態(tài),重做操作可以恢復(fù)到下一個(gè)狀態(tài)。
  1. 實(shí)現(xiàn)保存和加載功能
  • 保存功能可以將編輯器的當(dāng)前狀態(tài)序列化為一個(gè)文件或字符串,并保存到磁盤上。
  • 加載功能可以從磁盤上讀取文件或字符串,并將其反序列化為編輯器的狀態(tài)。

四、代碼實(shí)現(xiàn)

以下是一個(gè)簡單的節(jié)點(diǎn)編輯器框架的代碼實(shí)現(xiàn)示例:

| | using UnityEngine; |
| | using UnityEditor; |
| | using UnityEngine.UIElements; |
| | using UnityEditor.UIElements; |
| | |
| | // 定義一個(gè)用于存儲(chǔ)節(jié)點(diǎn)數(shù)據(jù)的ScriptableObject |
| | [CreateAssetMenu(fileName = "NewNodeGraph", menuName = "NodeGraph/NodeGraph")] |
| | public class NodeGraph : ScriptableObject |
| | { |
| | // 存儲(chǔ)節(jié)點(diǎn)和邊的數(shù)據(jù) |
| | public List<NodeBaseData> nodes = new List<NodeBaseData>(); |
| | public List<NodeLinkData> edges = new List<NodeLinkData>(); |
| | } |
| | |
| | // 定義一個(gè)用于存儲(chǔ)節(jié)點(diǎn)基礎(chǔ)數(shù)據(jù)的類 |
| | [Serializable] |
| | public abstract class NodeBaseData |
| | { |
| | public string GUID; |
| | public string NodeName = "NodeBase"; |
| | public Rect Position = Rect.zero; |
| | // 其他節(jié)點(diǎn)數(shù)據(jù) |
| | } |
| | |
| | // 定義一個(gè)用于存儲(chǔ)邊數(shù)據(jù)的類 |
| | [Serializable] |
| | public class NodeLinkData |
| | { |
| | public string BaseNodeGUID; |
| | public string OutputPortName; |
| | public string TargetNodeGUID; |
| | public string TargetPortName; |
| | } |
| | |
| | // 定義一個(gè)節(jié)點(diǎn)類,繼承自GraphView的Node類 |
| | public class MyNode : Node |
| | { |
| | // 節(jié)點(diǎn)數(shù)據(jù) |
| | public NodeBaseData nodeData; |
| | |
| | // 構(gòu)造函數(shù) |
| | public MyNode() |
| | { |
| | // 設(shè)置節(jié)點(diǎn)標(biāo)題和樣式 |
| | title = "My Node"; |
| | styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>("Packages/com.unity.uielements/Editor/Resources/Styles/GraphView.uss")); |
| | |
| | // 添加輸入/輸出端口 |
| | var inputPort = new Port(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(float)); |
| | inputPort.portName = "Input"; |
| | inputContainer.Add(inputPort); |
| | |
| | var outputPort = new Port(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(float)); |
| | outputPort.portName = "Output"; |
| | outputContainer.Add(outputPort); |
| | |
| | // 添加右鍵菜單 |
| | this.RegisterCallback<MouseDownEvent>(OnMouseDown); |
| | } |
| | |
| | // 處理右鍵菜單事件 |
| | private void OnMouseDown(MouseDownEvent evt) |
| | { |
| | if (evt.button == MouseButton.RightMouse) |
| | { |
| | var menu = new GenericMenu(); |
| | menu.AddItem(new GUIContent("Delete Node"), false, () => { DeleteNode(); }); |
| | menu.ShowAsContext(); |
| | evt.StopPropagation(); |
| | } |
| | } |
| | |
| | // 刪除節(jié)點(diǎn) |
| | private void DeleteNode() |
| | { |
| | // 從GraphView中移除節(jié)點(diǎn) |
| | graphView.RemoveElement(this); |
| | // 從NodeGraph中移除節(jié)點(diǎn)數(shù)據(jù)(需要自行實(shí)現(xiàn)) |
| | } |
| | } |
| | |
| | // 定義一個(gè)節(jié)點(diǎn)視圖類,繼承自GraphView |
| | public class MyGraphView : GraphView |
| | { |
| | // 構(gòu)造函數(shù) |
| | public MyGraphView(EditorWindow window, StyleSheet styleSheet) |
| | { |
| | this.styleSheets.Add(styleSheet); |
| | this.AddManipulator(new ContextualMenuManipulator(OnContextualMenu)); |
| | this.AddManipulator(new SelectionDragManipulator()); |
| | this.AddManipulator(new RectangleSelector()); |
| | this.AddManipulator(new ZoomManipulator()); |
| | this.AddManipulator(new PanManipulator()); |
| | |
| | // 初始化節(jié)點(diǎn)和邊(需要自行實(shí)現(xiàn)) |
| | } |
| | |
| | // 處理右鍵菜單事件 |
| | private void OnContextualMenu(ContextualMenuPopulateEvent evt) |
| | { |
| | var menu = new GenericMenu(); |
| | menu.AddItem(new GUIContent("Create Node"), false, () => { CreateNode(); }); |
| | menu.ShowAsContext(); |
| | } |
| | |
| | // 創(chuàng)建節(jié)點(diǎn) |
| | private void CreateNode() |
| | { |
| | var newNode = new MyNode(); |
| | newNode.SetPosition(new Rect(mousePosition, Vector2.one * 100)); |
| | this.Add(newNode); |
| | // 添加節(jié)點(diǎn)數(shù)據(jù)到NodeGraph中(需要自行實(shí)現(xiàn)) |
| | } |
| | } |
| | |
| | // 定義一個(gè)編輯器窗口類,用于顯示節(jié)點(diǎn)編輯器 |
| | public class NodeEditorWindow : EditorWindow |
| | { |
| | private MyGraphView graphView; |
| | private NodeGraph nodeGraph; |
| | |
| | // 構(gòu)造函數(shù) |
| | [MenuItem("Window/Node Editor")] |
| | public static void ShowWindow() |
| | { |
| | var window = GetWindow<NodeEditorWindow>("Node Editor"); |
| | window.minSize = new Vector2(800, 600); |
| | } |
| | |
| | // 初始化編輯器窗口 |
| | private void OnEnable() |
| | { |
| | // 加載或創(chuàng)建NodeGraph |
| | nodeGraph = AssetDatabase.LoadAssetAtPath<NodeGraph>("Assets/NodeGraphs/MyNodeGraph.asset"); |
| | if (nodeGraph == null) |
| | { |
| | nodeGraph = ScriptableObject.CreateInstance<NodeGraph>(); |
| | AssetDatabase.CreateAsset(nodeGraph, "Assets/NodeGraphs/MyNodeGraph.asset"); |
| | AssetDatabase.SaveAssets(); |
| | } |
| | |
| | // 初始化GraphView |
| | var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Packages/com.unity.uielements/Editor/Resources/Styles/GraphView.uss"); |
| | graphView = new MyGraphView(this, styleSheet); |
| | graphView.StretchToParentSize(); |
| | rootVisualElement.Add(graphView); |
| | |
| | // 初始化節(jié)點(diǎn)和邊(根據(jù)nodeGraph加載數(shù)據(jù)) |
| | // 需要自行實(shí)現(xiàn) |
| | } |
| | |
| | // 保存編輯器狀態(tài) |
| | private void OnDisable() |
| | { |
| | // 保存nodeGraph到磁盤(需要自行實(shí)現(xiàn)) |
| | } |
| | } |

五、總結(jié)

本文介紹了如何使用Unity3D的GraphView組件創(chuàng)建一個(gè)簡單的節(jié)點(diǎn)編輯器框架。我們詳細(xì)討論了節(jié)點(diǎn)編輯器框架的設(shè)計(jì)、技術(shù)實(shí)現(xiàn)和代碼示例。通過自定義節(jié)點(diǎn)、邊、面板和工具欄等組件,開發(fā)者可以輕松地創(chuàng)建出功能強(qiáng)大的節(jié)點(diǎn)編輯器,以滿足游戲開發(fā)中的復(fù)雜需求。希望本文能為Unity3D開發(fā)者提供有價(jià)值的參考和指導(dǎo)。

更多教學(xué)視頻

Unity3Dwww.bycwedu.com/promotion_channels/2146264125

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容