mirror of
https://github.com/Siccity/xNode.git
synced 2025-12-20 17:26:02 +08:00
Merge branch 'master' of https://github.com/Siccity/xNode into examples
This commit is contained in:
commit
becd710fe0
23
CONTRIBUTING.md
Normal file
23
CONTRIBUTING.md
Normal file
@ -0,0 +1,23 @@
|
||||
## Contributing to xNode
|
||||
💙Thank you for taking the time to contribute💙
|
||||
|
||||
If you haven't already, join our [Discord channel](https://discord.gg/qgPrHv4)!
|
||||
|
||||
## Pull Requests
|
||||
Try to keep your pull requests relevant, neat, and manageable. If you are adding multiple features, try splitting them into separate commits.
|
||||
* Avoid including irellevant whitespace or formatting changes.
|
||||
* Comment your code.
|
||||
* Spell check your code / comments
|
||||
|
||||
## New features
|
||||
xNode aims to be simple and extendible, not trying to fix all of Unity's shortcomings.
|
||||
|
||||
If your feature aims to cover something not related to editing nodes, it generally won't be accepted. If in doubt, ask on the Discord channel.
|
||||
|
||||
## Coding conventions
|
||||
Skim through the code and you'll get the hang of it quickly.
|
||||
* Methods, Types and properties PascalCase
|
||||
* Variables camelCase
|
||||
* Public methods XML commented
|
||||
* Open braces on same line as condition
|
||||
* 4 spaces for indentation.
|
||||
@ -3,7 +3,7 @@
|
||||
[](https://raw.githubusercontent.com/Siccity/xNode/master/LICENSE.md)
|
||||
[](https://github.com/Siccity/xNode/wiki)
|
||||
|
||||
[Go to Downloads](https://github.com/Siccity/xNode/releases) / [Go to Asset Store](http://u3d.as/108S)
|
||||
[Downloads](https://github.com/Siccity/xNode/releases) / [Asset Store](http://u3d.as/108S) / [Documentation](https://github.com/Siccity/xNode/wiki)
|
||||
|
||||
### xNode
|
||||
Thinking of developing a node-based plugin? Then this is for you. You can download it as an archive and unpack to a new unity project, or connect it as git submodule.
|
||||
@ -20,7 +20,6 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo
|
||||
* No runtime reflection (unless you need to edit/build node graphs at runtime. In this case, all reflection is cached.)
|
||||
* Does not rely on any 3rd party plugins
|
||||
* Custom node inspector code is very similar to regular custom inspector code
|
||||
* For a full list of features, see [this page](https://github.com/Siccity/xNode/wiki/Full-features-list)
|
||||
|
||||
### Node example:
|
||||
```csharp
|
||||
|
||||
@ -140,8 +140,12 @@ namespace XNodeEditor {
|
||||
} else if (!IsHoveringNode) {
|
||||
// If click outside node, release field focus
|
||||
if (!isPanning) {
|
||||
// I've got no idea which of these do what, so we'll just reset all of it.
|
||||
GUIUtility.hotControl = 0;
|
||||
GUIUtility.keyboardControl = 0;
|
||||
EditorGUIUtility.editingTextField = false;
|
||||
EditorGUIUtility.keyboardControl = 0;
|
||||
EditorGUIUtility.hotControl = 0;
|
||||
}
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
@ -166,8 +170,12 @@ namespace XNodeEditor {
|
||||
}
|
||||
break;
|
||||
case EventType.KeyDown:
|
||||
if (e.keyCode == KeyCode.Delete) RemoveSelectedNodes();
|
||||
else if (e.keyCode == KeyCode.D && e.control) DublicateSelectedNodes();
|
||||
if (EditorGUIUtility.editingTextField) break;
|
||||
else if (e.keyCode == KeyCode.F) Home();
|
||||
break;
|
||||
case EventType.ValidateCommand:
|
||||
if (e.commandName == "SoftDelete") RemoveSelectedNodes();
|
||||
else if (e.commandName == "Duplicate") DublicateSelectedNodes();
|
||||
Repaint();
|
||||
break;
|
||||
case EventType.Ignore:
|
||||
|
||||
@ -10,7 +10,6 @@ namespace XNodeEditor.Internal {
|
||||
public class NodeEditorBase<T, A, K> where A : Attribute, NodeEditorBase<T, A, K>.INodeEditorAttrib where T : NodeEditorBase<T,A,K> where K : ScriptableObject {
|
||||
/// <summary> Custom editors defined with [CustomNodeEditor] </summary>
|
||||
private static Dictionary<Type, T> editors;
|
||||
private static Dictionary<ScriptableObject, SerializedObject> serializeds;
|
||||
public K target;
|
||||
public SerializedObject serializedObject;
|
||||
|
||||
@ -19,17 +18,10 @@ namespace XNodeEditor.Internal {
|
||||
Type type = target.GetType();
|
||||
T editor = GetEditor(type);
|
||||
editor.target = target;
|
||||
editor.serializedObject = GetSerialized(target);
|
||||
editor.serializedObject = new SerializedObject(target);
|
||||
return editor;
|
||||
}
|
||||
|
||||
private static SerializedObject GetSerialized(K target) {
|
||||
if (target == null) return null;
|
||||
if (serializeds == null) serializeds = new Dictionary<ScriptableObject, SerializedObject>();
|
||||
if (!serializeds.ContainsKey(target)) serializeds.Add(target, new SerializedObject(target));
|
||||
return serializeds[target];
|
||||
}
|
||||
|
||||
private static T GetEditor(Type type) {
|
||||
if (type == null) return null;
|
||||
if (editors == null) CacheCustomEditors();
|
||||
|
||||
@ -364,8 +364,8 @@ namespace XNodeEditor {
|
||||
GUIContent content = new GUIContent();
|
||||
content.text = type.PrettyName();
|
||||
if (hoveredPort.IsStatic && hoveredPort.IsOutput) {
|
||||
object obj = ObjectFromFieldName(hoveredPort.node, hoveredPort.fieldName);
|
||||
if (obj != null) content.text += " = " + obj.ToString();
|
||||
object obj = hoveredPort.node.GetValue(hoveredPort);
|
||||
content.text += " = " + (obj != null ? obj.ToString() : "null");
|
||||
}
|
||||
Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content);
|
||||
Rect rect = new Rect(Event.current.mousePosition - (size), size);
|
||||
|
||||
@ -70,13 +70,13 @@ namespace XNodeEditor {
|
||||
switch (showBacking) {
|
||||
case XNode.Node.ShowBackingValue.Unconnected:
|
||||
// Display a label if port is connected
|
||||
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.styles.outputPort, GUILayout.MinWidth(30));
|
||||
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
|
||||
// Display an editable property field if port is not connected
|
||||
else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
|
||||
break;
|
||||
case XNode.Node.ShowBackingValue.Never:
|
||||
// Display a label
|
||||
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.styles.outputPort, GUILayout.MinWidth(30));
|
||||
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
|
||||
break;
|
||||
case XNode.Node.ShowBackingValue.Always:
|
||||
// Display an editable property field
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XNodeEditor {
|
||||
public static class NodeEditorResources {
|
||||
@ -15,9 +16,9 @@ namespace XNodeEditor {
|
||||
// Styles
|
||||
public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } }
|
||||
public static Styles _styles = null;
|
||||
|
||||
public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } }
|
||||
public class Styles {
|
||||
public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
|
||||
public GUIStyle inputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
|
||||
|
||||
public Styles() {
|
||||
GUIStyle baseStyle = new GUIStyle("Label");
|
||||
@ -27,10 +28,6 @@ namespace XNodeEditor {
|
||||
inputPort.alignment = TextAnchor.UpperLeft;
|
||||
inputPort.padding.left = 10;
|
||||
|
||||
outputPort = new GUIStyle(baseStyle);
|
||||
outputPort.alignment = TextAnchor.UpperRight;
|
||||
outputPort.padding.right = 10;
|
||||
|
||||
nodeHeader = new GUIStyle();
|
||||
nodeHeader.alignment = TextAnchor.MiddleCenter;
|
||||
nodeHeader.fontStyle = FontStyle.Bold;
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace XNodeEditor {
|
||||
/// <summary> A set of editor-only utilities and extensions for UnityNodeEditorBase </summary>
|
||||
public static class NodeEditorUtilities {
|
||||
|
||||
/// <summary>C#'s Script Icon [The one MonoBhevaiour Scripts have].</summary>
|
||||
private static Texture2D scriptIcon = (EditorGUIUtility.IconContent("cs Script Icon").image as Texture2D);
|
||||
|
||||
public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute {
|
||||
object[] attribs = classType.GetCustomAttributes(typeof(T), false);
|
||||
return GetAttrib(attribs, out attribOut);
|
||||
@ -85,5 +93,86 @@ namespace XNodeEditor {
|
||||
}
|
||||
} else return type.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Creates a new C# Class.</summary>
|
||||
[MenuItem("Assets/Create/xNode/Node C# Script", false, 89)]
|
||||
private static void CreateNode() {
|
||||
string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs");
|
||||
if (guids.Length == 0) {
|
||||
Debug.LogWarning("xNode_NodeTemplate.cs.txt not found in asset database");
|
||||
return;
|
||||
}
|
||||
string path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
CreateFromTemplate(
|
||||
"NewNode.cs",
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Creates a new C# Class.</summary>
|
||||
[MenuItem("Assets/Create/xNode/NodeGraph C# Script", false, 89)]
|
||||
private static void CreateGraph() {
|
||||
string[] guids = AssetDatabase.FindAssets("xNode_NodeGraphTemplate.cs");
|
||||
if (guids.Length == 0) {
|
||||
Debug.LogWarning("xNode_NodeGraphTemplate.cs.txt not found in asset database");
|
||||
return;
|
||||
}
|
||||
string path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
CreateFromTemplate(
|
||||
"NewNodeGraph.cs",
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
public static void CreateFromTemplate(string initialName, string templatePath) {
|
||||
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(
|
||||
0,
|
||||
ScriptableObject.CreateInstance<DoCreateCodeFile>(),
|
||||
initialName,
|
||||
scriptIcon,
|
||||
templatePath
|
||||
);
|
||||
}
|
||||
|
||||
/// Inherits from EndNameAction, must override EndNameAction.Action
|
||||
public class DoCreateCodeFile : UnityEditor.ProjectWindowCallback.EndNameEditAction {
|
||||
public override void Action(int instanceId, string pathName, string resourceFile) {
|
||||
Object o = CreateScript(pathName, resourceFile);
|
||||
ProjectWindowUtil.ShowCreatedAsset(o);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates Script from Template's path.</summary>
|
||||
internal static UnityEngine.Object CreateScript(string pathName, string templatePath) {
|
||||
string className = Path.GetFileNameWithoutExtension(pathName).Replace(" ", string.Empty);
|
||||
string templateText = string.Empty;
|
||||
|
||||
UTF8Encoding encoding = new UTF8Encoding(true, false);
|
||||
|
||||
if (File.Exists(templatePath)) {
|
||||
/// Read procedures.
|
||||
StreamReader reader = new StreamReader(templatePath);
|
||||
templateText = reader.ReadToEnd();
|
||||
reader.Close();
|
||||
|
||||
templateText = templateText.Replace("#SCRIPTNAME#", className);
|
||||
templateText = templateText.Replace("#NOTRIM#", string.Empty);
|
||||
/// You can replace as many tags you make on your templates, just repeat Replace function
|
||||
/// e.g.:
|
||||
/// templateText = templateText.Replace("#NEWTAG#", "MyText");
|
||||
|
||||
/// Write procedures.
|
||||
|
||||
StreamWriter writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding);
|
||||
writer.Write(templateText);
|
||||
writer.Close();
|
||||
|
||||
AssetDatabase.ImportAsset(pathName);
|
||||
return AssetDatabase.LoadAssetAtPath(pathName, typeof(Object));
|
||||
} else {
|
||||
Debug.LogError(string.Format("The template file was not found: {0}", templatePath));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Scripts/Editor/Resources/ScriptTemplates.meta
Normal file
10
Scripts/Editor/Resources/ScriptTemplates.meta
Normal file
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86b677955452bb5449f9f4dd47b6ddfe
|
||||
folderAsset: yes
|
||||
timeCreated: 1519049391
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,9 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
[CreateAssetMenu]
|
||||
public class #SCRIPTNAME# : NodeGraph {
|
||||
#NOTRIM#
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8165767f64da7d94e925f61a38da668c
|
||||
timeCreated: 1519049802
|
||||
licenseType: Free
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
public class #SCRIPTNAME# : Node {
|
||||
|
||||
// Use this for initialization
|
||||
protected override void Init() {
|
||||
base.Init();
|
||||
#NOTRIM#
|
||||
}
|
||||
|
||||
// Return the correct value of an output port when requested
|
||||
public override object GetValue(NodePort port) {
|
||||
return null; // Replace this
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85f6f570600a1a44d8e734cb111a8b89
|
||||
timeCreated: 1519049802
|
||||
licenseType: Free
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -202,6 +202,10 @@ namespace XNode {
|
||||
foreach (NodePort port in Ports) port.ClearConnections();
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return JsonUtility.ToJson(this).GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class InputAttribute : Attribute {
|
||||
|
||||
@ -9,8 +9,15 @@ namespace XNode {
|
||||
public enum IO { Input, Output }
|
||||
|
||||
public int ConnectionCount { get { return connections.Count; } }
|
||||
/// <summary> Return the first connection </summary>
|
||||
public NodePort Connection { get { return connections.Count > 0 ? connections[0].Port : null; } }
|
||||
/// <summary> Return the first non-null connection </summary>
|
||||
public NodePort Connection {
|
||||
get {
|
||||
for (int i = 0; i < connections.Count; i++) {
|
||||
if (connections[i] != null) return connections[i].Port;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public IO direction { get { return _direction; } }
|
||||
public Node.ConnectionType connectionType { get { return _connectionType; } }
|
||||
@ -54,8 +61,7 @@ namespace XNode {
|
||||
if (attribs[i] is Node.InputAttribute) {
|
||||
_direction = IO.Input;
|
||||
_connectionType = (attribs[i] as Node.InputAttribute).connectionType;
|
||||
}
|
||||
else if (attribs[i] is Node.OutputAttribute) {
|
||||
} else if (attribs[i] is Node.OutputAttribute) {
|
||||
_direction = IO.Output;
|
||||
_connectionType = (attribs[i] as Node.OutputAttribute).connectionType;
|
||||
}
|
||||
@ -79,7 +85,7 @@ namespace XNode {
|
||||
_direction = direction;
|
||||
_node = node;
|
||||
_dynamic = true;
|
||||
_connectionType = connectionType;
|
||||
_connectionType = connectionType;
|
||||
}
|
||||
|
||||
/// <summary> Checks all connections for invalid references, and removes them. </summary>
|
||||
@ -225,15 +231,17 @@ namespace XNode {
|
||||
connections.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
// Remove the other ports connection to this port
|
||||
for (int i = 0; i < port.connections.Count; i++) {
|
||||
if (port.connections[i].Port == this) {
|
||||
port.connections.RemoveAt(i);
|
||||
if (port != null) {
|
||||
// Remove the other ports connection to this port
|
||||
for (int i = 0; i < port.connections.Count; i++) {
|
||||
if (port.connections[i].Port == this) {
|
||||
port.connections.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Trigger OnRemoveConnection
|
||||
node.OnRemoveConnection(this);
|
||||
port.node.OnRemoveConnection(port);
|
||||
if (port != null) port.node.OnRemoveConnection(port);
|
||||
}
|
||||
|
||||
public void ClearConnections() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user