xNode - A general purpose node editor

xNode lets you make your own node graph systems in Unity. It is easy to use, and comes without bloat.

You can use xNode for any kind of node based solution. Dialogue systems, state machines, procgen graphs, you name it.

Feature highlight

  • Pan/Zoom functionality

  • Connection creation / removal

  • Lightweight in runtime

  • Very little boilerplate code

  • Strong separation of editor and runtime code

  • 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

  • Add/remove connection ports dynamically through editor code

  • Simple, intuitive API. No need to ever touch core code.

  • Node inspectors copy inspector functionality - works with property drawers and attributes


Node example:

public class MathNode : Node {
   // Adding [Input] or [Output] is all you need to do to register a field as a valid port on your node
   [Input] public float a;
   [Input] public float b;
   // The value of an output node field is not used for anything, but could be used for caching output results
   [Output] public float result;
   [Output] public float sum;

   // xNode will display this as an editable field - just like the normal inspector would
   public MathType mathType = MathType.Add;
   public enum MathType { Add, Subtract, Multiply, Divide}
 
   // GetValue should be overridden to return a value for any specified output port
   public override object GetValue(NodePort port) {

       // Get new a and b values from input connections. Fallback to field values if input is not connected
       float a = GetInputValue<float>("a", this.a);
       float b = GetInputValue<float>("b", this.b);

       // After you've gotten your input values, you can perform your calculations and return a value
       if (port.fieldName == "result")
           switch(mathType) {
               case MathType.Add: default: return a + b;
               case MathType.Subtract: return a - b;
               case MathType.Multiply: return a * b;
               case MathType.Divide: return a / b;
           }
       else if (port.fieldName == "sum") return a + b;
       else return 0f;
   }
}

If you want to discuss or need help using xNode, feel free to do so on this thread, or join us on Discord :sunglasses:
You can find it on its GitHub page

20 Likes

Sounds great! Bookmarked for any future need. Thanks & GL!

1 Like

Example xNode usage. Here I have built a dialogue system. No core modifications.

2 Likes

Hey, I was wondering how xNode compares to “Node Editor Framework” from Seneral.

I am looking for a framework to create a state-machine design tool for app states.

Visuals are not that important. But it has to work reliable.

I am not 100% confident in how Node Editor Framework works, but i think the main differences lie in API and editor code.
xNode keeps a strict separation of runtime and editor which means your editor code is kept out of your runtime builds, and using it will feel very familiar to regular custom inspector scripting. For most nodes you don’t even need to touch editor code - the system will automatically serialize and display fields just like in the inspector. NEF editor code, unless wrapped in #if UNITY_EDITOR is included in your build.

API-Wise, xNode is very intuitive and follows typical Unity conventions, so using it will be easy if coming from a Unity background.

xNode is also very new and simple, whereas NEF has existed for a long time and has a lot of features and contributors.

There may be too many differences to list, and i do not know NEF well enough to make a proper comparison, but xNode is definitely capable for making a state machine.

Update: xNode now supports [ContextMenu] attribute. Add custom context menus to nodes and node graphs.

3 Likes

Update:

  • Added global xNode preferences - Modify grid and type colors
  • Added NodeGraphEditor support - You can now add node type constraints on a per-graphtype basis.
  • Added grid snap - Toggleable in ‘preferences’
  • 3303140--256271--2017-11-28_10-18-52.gif*
3 Likes

This looks nice… Gonna have a play soon! Thanks

1 Like

Update: xNode is now available on the asset store!
(Although it is a 4 weeks behind the github repo)

4 Likes

Hi, I’m getting a lot of errors on Unity 2017.2.0p3. Any idea why? I tried the AssetStore version and the github version (master branch).

This looks beautiful.

This looks awesome, thanks for sharing it

It seems like you already have a global class named Node. XNode became confused and used your global class instead. This is something I did not foresee and I have now strictly defined namespaces across all scripts. Try the newest Github version. Should work. Thank you for pointing it out! :]

3 Likes

Will give it a try after this weekend! Thanks!

1 Like

Thank you for sharing such a great project! I like it so much!

Could you please show us more examples?
I am personally interested in examples on:

  1. graph with delayed execution (one node executed, wait for some time, next node executed)
  2. connection between NodeGraph and MonoBehaviour (do we need to use NodeCache?).

My feature request: could you please include ExposedInput functionality (like in Graphmesh) into the core of xNode?

I’m glad you like it :slight_smile:

I try to develop as many examples as I can, but time has been a little tight lately. Also I’m working on a serializable function class that could benefit xNode state machines and dialogue systems big time.

  1. The way I do node execution is create a new base class deriving from Node having a virtual Trigger() method. From the starting node, simply loop through and call Trigger() on all output connections. You can impose a delay by triggering the next node in a coroutine with a WaitForSeconds preceeding it.
IEnumerator TriggerNextNodes(NodePort outputPort, float delay) {
    yield return new WaitForSeconds(delay);
    for (int i = 0; i < outputPort.ConnectionCount; i++) {
        TriggerableNode nextNode = port.GetConnection(i).node;
        if (nextNode != null) nextNode.Trigger();
    }
}
  1. Not sure exactly what you mean. Your node graph cannot have direct references to your scene, as it is a project asset, but your scene can reference a NodeGraph simply by adding a public field for it. You shouldn’t have to tamper with NodeCache in any case.

As for ExposedInput, I don’t feel it’s good enough yet. It is very complex and not very userfriendly. I might add it at some point when I feel it’s ready.

Thank you for your feedback! :smile:

Hello, beautifull asset, im really hoping to work a lot with Your framework, but i cant get it to work somehow
im getting this error :

Assets/xNode-master/Scripts/Editor/NodeEditorBase.cs(39,42): error CS0117: NodeEditorWindow' does not contain a definition for GetDerivedTypes’

Edit: it seems that StrumpyShaderEditor was the culprit.

Im kinda interested in using this framework for visual/classic hybrid scripting.

Thanks :slight_smile:
Errors like these occur when other plugins forget to put their code in namespaces. I have tried preventing it by strongly typing class namespaces in xNode by typing:

XNodeEditor.NodeEditorWindow.Bla();

instead of:

using XNodeEditor;
NodeEditorWindow.Bla();

I have done so now as well for NodeEditorWindow so if you update you shouldn’t be having any issues with your shader editor.

Works well, Extremely well-done recommended for anyone who wants to get there systems up and running in only a few hours!

1 Like

Siccity, thank you for your quick response!

Thank you, I will keep my eye on it.

Oh, I see. I need to be more specific.

Actually I’ve already figured out something. I will put it here for other users.

Problem:
Make a binary function, calculated using xNode, the results are returned to MonoBehaviour component.

Solution:

  1. Make an input node
using XNode;

namespace MyNodes
{
    [System.Serializable]
    public class InputNode : Node
    {
        public string name;
        [Output] public float input;

        public override object GetValue(NodePort port)
        {
            return input;
        }
    }
}
  1. Make an output node
using XNode;

namespace MyNodes
{
    [System.Serializable]
    public class OutputNode : Node
    {
        [Input] public float result;
    }
}
  1. Make a new type of graph for binary functions
using UnityEngine;
using System;
using XNode;
using MyNodes;


[Serializable, CreateAssetMenu(fileName = "xNodeBinaryFunction", menuName = "Node Graph/xNodeBinaryFunction")]
public class xNodeBinaryFunction : NodeGraph {
    public float calculate(float x, float y)
    {
        // I know this part is inefficient/long/slow, please take it easy, it is just an example
        foreach (var node in nodes)
        {
            if (node is InputNode)
            {
                var temp = (InputNode) node;
                if (temp.name == "x") temp.input = x;
                if (temp.name == "y") temp.input = y;
            }
        }

        foreach (var node in nodes)
        {
            if (node is OutputNode)
            {
                var temp = (OutputNode) node;
                return temp.GetInputValue("result", fallback: 0f);
            }
        }
        return 0;
    }
}
  1. Create an “xNodeBinaryFunction” graph (scriptable object) with two input nodes “x” and “y”, and an output node
    3328451--259354--xNode.png

  2. Create a component, which uses this graph

using UnityEngine;

public class myComponent : MonoBehaviour
{
    public xNodeBinaryFunction function; // don't forget to attach your graph here

    void Start ()
    {
        print(function.calculate(2, 3));
    }
}

Siccity, do you think this example can be simplified?