Ich entwickle ein knotenbasiertes Editor-Tool in Unity mit reinem UIToolkit (keine vorhandenen Graph-Frameworks). Die Voraussetzung ist, dass das Diagramm szenenbasiert sein muss, es sich also um ein Monoverhalten und nicht um ein ScriptableObject handelt, wie es bei den meisten anderen Diagrammsystemen der Fall ist.
In jedem Knoten kann es PropertyFields geben, die möglicherweise eine Benutzeroberfläche für benutzerdefinierte Eigenschaften zeichnen.
Ich habe zwei Ansätze implementiert, beide mit ihren Nachteilen und Vorteilen.
1. Im Speicher ScriptableObjects für das Diagramm und die Knoten, etwa so:
public class GraphComponent : MonoBehaviour
{
[SerializeReference]
[HideInInspector]
public GraphData graphData;
...
public class GraphData : ScriptableObject
{
[HideInInspector] public string GUID = Guid.NewGuid().ToString();
[HideInInspector]
[SerializeReference]
public List nodes = new();
...
public abstract class NodeData : ScriptableObject
{
[NodeHideVariable]
public string guid = "";
[SerializeReference]
public GraphData graphData;
...
Dann gibt es natürlich viele von ViualElement abgeleitete Klassen, die definieren, wie Knoten, Kanten usw. aussehen.
In der NodeElement-Klasse gibt es Methoden dazu Erstellen Sie eine Knoten-VisualElement-Hierarchie. Die wichtigste davon ist BuildValues(), das über die Felder iteriert und PropertyFields erstellt, ungefähr so:
public void BuildValues()
{
var _editor = Editor.CreateEditor(nodeData);
var sp = _editor.serializedObject.GetIterator();
sp.NextVisible(true);
while (sp.NextVisible(false))
{
System.Type t = sp.serializedObject.targetObject.GetType();
FieldInfo f = null;
f = t.GetField(sp.propertyPath);
if (f != null)
{
var _hideAttribute = f.GetCustomAttribute(typeof(NodeHideVariable), true);
if (_hideAttribute == null)
{
var _property = new PropertyField();
_property.BindProperty(sp);
valuesContent.Add(_property);
}
}
}
_fromNodeInspector = false;
_editor.serializedObject.ApplyModifiedProperties();
}
Dieser Ansatz läuft äußerst effizient und verarbeitet problemlos Diagramme mit 200 Knoten und mehr, mit einer butterweichen Framerate im Editor. Absolute Freude, mit zu arbeiten. Es hat die speicherinternen ScriptableObjects wie erwartet in die Unity-Szene serialisiert.
Der Haken: Es funktioniert offensichtlich nicht, wenn Sie eine GraphComponent zu einem Fertigteil machen möchten, indem Sie es in das Projektfenster ziehen.
Das graphData wird überhaupt keine Knoten enthalten, da Verweise auf ScriptableObjects in Unity nicht in Fertighäuser serialisiert werden.
Ich müsste also tief in die Generierung eintauchen ScriptableObject-Unterassets für jeden Knoten und irgendwie manuelles Auflösen der internen Verweise auf Szenenobjekte, die sich innerhalb der Prefab-Hierarchie befinden (falls das überhaupt möglich ist).
Also habe ich einen zweiten Ansatz versucht:
2. Verwenden Sie System.Serializable-Klassen für GraphData und NodeData wie folgt:
GraphComponent bleibt unverändert.
[System.Serializable]
public class GraphData
{
[HideInInspector]
[SerializeReference]
public MonoBehaviour monoBehaviour; // store to which GraphComponent we are attached
[HideInInspector] public string GUID = Guid.NewGuid().ToString();
[HideInInspector]
[SerializeReference]
public List nodes = new();
...
[System.Serializable]
public abstract class NodeData
{
[NodeHideVariable]
public string guid = "";
[SerializeReference]
public GraphData graphData;
...
Das neue BuildValues() durchläuft auch die GraphComponent als SerializedObject und bindet PorpertyFields an seine SerializedProperties entlang der Serialisierungskette:
public void BuildValues() {
SerializedObject serializedObject = new SerializedObject(graphData.monoBehaviour);
serializedObject.Update();
// Find GraphData property
SerializedProperty graphDataProperty = serializedObject.FindProperty("graphData");
// Find nodes list
SerializedProperty nodesProperty = graphDataProperty.FindPropertyRelative("nodes");
// Find index of nodeData in nodes list by comparing GUIDs
int nodeIndex = -1;
for (int i = 0; i < nodesProperty.arraySize; i++)
{
SerializedProperty nodeProperty = nodesProperty.GetArrayElementAtIndex(i);
SerializedProperty guidProperty = nodeProperty.FindPropertyRelative("guid");
if (guidProperty != null && guidProperty.stringValue == nodeData.guid)
{
nodeIndex = i;
break;
}
}
if (nodeIndex == -1)
{
Debug.LogError("NodeData not found in GraphData nodes list.");
return;
}
// Get SerializedProperty for nodeData
SerializedProperty nodeDataProperty = nodesProperty.GetArrayElementAtIndex(nodeIndex);
// Clear existing content
valuesContent.Clear();
// Iterate over nodeData fields
SerializedProperty iterator = nodeDataProperty.Copy();
SerializedProperty endProperty = nodeDataProperty.GetEndProperty();
// Move into the first child of nodeDataProperty
if (iterator.Next(true))
{
int startingDepth = iterator.depth; // Record the starting depth
do
{
// Stop if we've reached the end property or exited the current depth
if (SerializedProperty.EqualContents(iterator, endProperty) || iterator.depth != startingDepth)
break;
// Get the corresponding FieldInfo
FieldInfo fieldInfo = nodeData.GetType().GetField(iterator.name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
if (fieldInfo == null) continue;
// Create PropertyField
PropertyField propertyField = new PropertyField(iterator.Copy());
propertyField.Bind(serializedObject);
valuesContent.Add(propertyField);
} while (iterator.NextVisible(false));
}
// Apply changes when needed
serializedObject.ApplyModifiedProperties();
}
Dieser Ansatz funktioniert perfekt und natürlich mit Prefabs, aber sobald Sie mehr als 30-35 Knoten haben, verzögert er sich wie verrückt und macht das Diagrammtool unbrauchbar.
Von Profiler läuft Ich habe festgestellt, dass Unity bei jeder kleinen Änderung die gesamte Struktur serialisiert, da die PropertyFields an das übergeordnete Monobehaviour gebunden sind. Wenn Sie also beispielsweise einen einzelnen Knoten verschieben, werden alle anderen Knoten aktualisiert und gerendert.
Ich habe versucht, schlau zu sein und die NodeData-Objekte in eine NodeDataWrapper-Klasse (abgeleitet von ScriptableObject) zu packen, die nur einen Verweis darauf enthält NodeData und binden Sie die PropertyFields stattdessen an diese ScriptableObject-Wrapper, aber es gibt keinen Unterschied in der Leistung.
Die Frage ist also: Kann dieser letztere Ansatz durchgeführt werden? effizient beim Rendern, während System.Serializable-Klassen verwendet werden, um die Prefab-Workflow-Kompatibilität aufrechtzuerhalten?
Update: Ich habe auch eine benutzerdefinierte Eigenschaftsschublade für meine NodeData-Objekte ausprobiert, damit Unity dies tun würde Automatische Bindung an die Eigenschaften, aber die Leistung ist immer noch verzögert und unterscheidet sich nicht von zuvor.
Leistung vs. Flexibilität in einem Unity Node Graph Editor Tool ⇐ C#
-
- Similar Topics
- Replies
- Views
- Last post