Search Unity

Simple node editor

Discussion in 'Immediate Mode GUI (IMGUI)' started by unimechanic, Jul 5, 2013.

  1. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Hi Seneral, I've downloaded the develop branch and everything seems to be working for me - although I didn't have any bugs before anyway (well, except my own!)

    Thanks again for all your hard work on this great framework!

    Is there an easy way to have the GUI update based on the calculation the canvas runs? I'm building a synth/sequencer and it'd be cool if I could have the display update to show which beat is currently playing



    I'm guessing I'd have to extend/write my own RT node editor?
     
  2. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thanks! There are some parts that are more or less prone to errors, one of them was the save system, which is why one of the goals recently was to fix bugs there...

    Regarding your question, it depends on what GUI-update you're speaking of... The node editor GUI should refresh on calculation so I'm imagining you want to display your beat at runtime? Then yes you need some kind of logic that makes the canvas calculate how you want it in the first place, like shown in RTCanvasCalculator.
    Then depending on what the nodes actually do and if it makes sense you can update the GUI from that calculator based of the canvas results or you could also make each node handle that by itself;)
     
  3. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Yeah, that's the idea. I've actually figured it out in the meantime (see below), although I'm getting an intermittent error


    I still have the same problem as before with NodeEditor.RecalculateAll(canvas) within the onAudioFilterRead when making my own Runtime Editor as with the simple calculator, so I had to rely on my own CalculateAll function that I put up previously. However, I get a very intermittent error due to threading now.

    Here's the offending lines

    Code (CSharp):
    1. public List<Node> getOutputNodes()
    2.         {
    3.             cache.AssureCanvas();
    4.             NodeCanvas temp = canvas;
    5.             return temp.nodes.Where((Node node) => (node.Outputs.Count == 0 && node.Inputs.Count != 0) || node.Outputs.TrueForAll((NodeOutput output) => output.connections.Count == 0)).ToList();
    6.         }
    This is just the method from your RTCanvasCalculator class, but even by copying the canvas to a new variable in attempt to avoid the enumeration issue didn't help.

    I haven't tried to iterate through the nodes yet though
     
  4. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Actually, ignore that, I wasn't actually making a copy of the canvas/nodes - the working version is this and no longer throws an error;
    Code (CSharp):
    1.  
    2. public List<Node> getOutputNodes()
    3.   {
    4.   cache.AssureCanvas();  
    5.   return canvas.nodes.ToList<Node>().Where((Node node) => (node.Outputs.Count == 0 && node.Inputs.Count != 0) || node.Outputs.TrueForAll((NodeOutput output) => output.connections.Count == 0)).ToList();
    6.   }
    7.  
     
  5. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    @Seneral - Is it possible to add/delete nodes in the runtime editor? The context menu doesn't appear on right click at runtime. Thanks!
     
  6. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    That's weird, will take a look into that and see it I have the same problem, it is probably related to onAudioFilterRead being called on a seperate thread...

    No, it is supposed to show even at runtime (one reason it was made in the first place)... again, will have a look tomorrow!

    Thanks for reporting both of these errors!
     
  7. Rimevel

    Rimevel

    Joined:
    Dec 8, 2015
    Posts:
    10
    Everything works now! Using the latest version on the dev branch and did some cleanup of my code. Also using the same method of loading the assets as you do.
    Only problem I have right now is my runtime system eats 1ms of cpu for each graph :p That is another problem though. Thanks for the help.
     
  8. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Cool! Regarding performance, can you tell me what exacly you're doing that it takes so long? Haven't yet optimised the calculation that much because I've not had any performance problems yet and didn't heard of any. Depending on the size of your canvas and nodes you probably can't calculate it each time but still, can you tell me more of what you're doing? So I can take a look? Thanks!
     
  9. holdingjupiter

    holdingjupiter

    Joined:
    Oct 27, 2014
    Posts:
    20
    tl;dr Any ideas for a noob on how to save a node layout, say by game object with a certain script e.g. "npcScript"?

    It seems obvious to me how to save the information created by the node editor to a script or a .asset through some user interface, but how would one save, for example, the placement of the nodes and which curves are being made so that it could be reproduced say: when you have a certain character selected in the scene view. I'm new to editor scripting so the thought of saving that manually sounds about as easy as putting a rocket into space and I don't see it covered in any of the scripts developed on this thread.

    I include the rest because I'm always looking for co-creators and this seems like an active thread. I worry about posting to much code to the forums as my goal is to put some of it in the asset store, but mostly I'm just tired of working in the service industry and think unity code might be a way out of that. Definitely I would rather have a game than an asset store item so if anyone has expressed interest in making skyrim style openworld experiences I have done most of the heavy lifting in Unity already and am seeking collaborators.

    I'm in the process of teaching myself to make immersive open world games in Unity(blender/c#/gimp/makehuman proficiency). My goal is designer friendly game making code and an ability to produce profound experiences as quickly as possible. (like making the gaming equivalent of short films maybe?) I wrote a sub-engine to do everything I'd like to do in an open world, but upon creating my first game my dialogue system fell apart (it was based on a massively nested (7?) serializable class, and I kept getting/setting references as opposed to values, so the conversation would lock at random points and fixing bugs would create more bugs.(<<< at this point I'd be interested in posting this code.)) so I thought a node editor would be the best way to create a new dialogue (mostly using resources in this thread) and it's going pretty well! It's still bare bones at this point but my nodes start with a "greet" node which cannot be attached to and a scalable button in the bottom right which gives you new conversation nodes. Then, when you click a node and hold right click a curve from that nodes and if you let go within another node it attaches, which allows me to create all sorts of complex conversation patterns (I also have it vertical scrolling).

    There are all sorts of things I have yet to implement that I have some strong ideas of how I would :

    1. I need a way to delete nodes/curves if I decide to change the paths of conversation later or make a mistake. Right now I mostly can't decide which interface is most elegant for the user to remove them.
    2. I need to create a menu the saves several node window states and attaches to a specific gameObject (or something similar) so the conversation can update depending on information from my quest system.
    3. It would be nice to zoom in and out just in case I wanted to make a truly massive dialogue, which I might!
    4. I have no idea how to create custom node windows, but nasoukikos' project has been a huge help and I can probably learn by reverse engineering the ones in that project.
    5. Use .asset's to save the information in the node editor as a nested list of strings for each character. The nestings would be: a. string for speech string array on each node b.each node kept in a list c.list of node lists to switch to depending on reaction to the quest system.// Seems pretty straightforward.
    6. I need to build a system that translates the .asset's to a system of inquiries and responses that appear in the new UI text objects at runtime. I've done this once before and the serialized classes i used threw a complex thing into something unmanageable. Mostly I'm worried about using the scroll rect and having it work intuitively for a series of inquiries(my dream is something bioware style but it's tough to pull off in unity). Last time I instantiated buttons with the inquiry into a vertical group in a scroll rect. It was met with some success although plagued by seeming like a scroll wheel as opposed to being menu option based(like it would be nice to get them to snap to a centered rect, even when just using the up/down keys).

    On my main inquiry my only thought is to read through seneral's code and reverse engineer the save feature there, but that code looks seriously dense, although I only opened the github this morning, so maybe I just don't understand it yet. Any ideas on any of the issues I put forward would be much appreciated, although I mostly came for some quick ideas on a save feature. This thread is an invaluable resource for unity designers and I appreciate everyone's input.
     
  10. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    @holdingjupiter Can comment on a few points that regard the framework itself but don't expect me to give you a detailed solution or comment:p Also, many of your points do not regard the node editor itself but rather design, and without claiming this thread for me or the project alone, it might be more successive to post something like this on a feedback thread;)
    Anyways, you seem to have decided to go with seperate connections between nodes instead using the NodeKnobs that are currently used. Assuming you've chosen this with care, you could take a look at the transition system branch at the repo. It hasn't been updated in a long time but it shows an approach to additional transition connections between nodes (created by right-clicking, has a temporary button in the middle to select). If you find a better solution to 1. than a button, go ahead and share, please;)
    If you're trying to make the dialogue react to quest states with 2. why not implementing conditional dialogue options instead? From what I understand, you want to create a dialogue tree for each possible quest state, right?
    Regarding 3., zooming is already a thing, just use the mouse wheel.
    What do you mean exactly with 4.? Popups triggered by nodes, node GUIs or an own editor window with the node editor embedded? Maybe I can help you there...
     
  11. holdingjupiter

    holdingjupiter

    Joined:
    Oct 27, 2014
    Posts:
    20
    Yeah these threads seem really editable so I can always change that later, just wanted to get it all in print first I guess. I seriously was considering finding a new thread for it all. Still processing your response seneral. Also, I copy pasted from page 1 of the thread and then built something out of the 1st 20 replies and my own knowledge of coding so have yet to put any of your code into a unity file at all yet. As a coder I'm all about simplicity. So I like to keep each individual script under 80 or so lines.

    For 1. When you right click in my extension, if you have a selected node, it draws a curve from the mouse position to the selected node, and when you let go, if it is inside another node rect it attaches. So I wonder if a mouseUp is the best response given the workflow I created. Like maybe a double click, or an options button on each node etc.

    For 4. I mean I just have empty node boxes in my editor window with a title that cannot be edited, but either I'd like a button on each node that leads to a pop up that allows you to set the inquiry and monologue for the node or a text box possibly with some up/down buttons for separate strings and a text box for inquiry,(eventually it would be nice to add extra functionally here like quest markers etc. too) and I don't really now how to do that but I see that it has been done on the thread.
     
    Last edited: Aug 25, 2016
  12. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ah ok, so yes for 1 that seems the most user friendly approach, have this implemented in the state machine branch as I said.
    For 4., that's actually one of the basics of node creation;) Use the overwritten method NodeGUI like the other nodes (you can check out the default ones for examples) and put any kind of control in it. I recommend to take a look at Editor GUI for this, or use RTEditorGUI if you consider using the node editor at runtime (won't be important for you unless you want to create an ingame dialogue editor;) ).
     
  13. Rimevel

    Rimevel

    Joined:
    Dec 8, 2015
    Posts:
    10
    I have a component called Flow that I connect a canvas asset to. This flow will at start find the "start" node and then traverse between nodes following all Path type connects. Path type being a custom type that is just a bool. Every time it lands on a node it runs a function doing something node specific and sets all output nodes and then moves on along the "path". Output node data is stored inside the flow component with a reference to the NodeOutput it belongs to. This is done to help produce distinct results between different flows components even if they share the exact same canvas.
    No calculations at all are done in the editor. It has all been moved to runtime.

    The problem I think is the fact that this system can't handle updating each frame. I am contemplating only traversing the canvas when values change or when an event node is triggered. Then some nodes would only run when forced to calculate and others would be having a life of their own running a routine each frame until done and then starting a new calculation from that point onward in the canvas. For example a "wait node". I feel this approach is a bit overkill but might be needed if other ways of optimizing fail.
     
  14. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Sorry, I was away. Yep can't say much about this, as you're using your own traversal system from what I understand. 1 ms still sounds like alot, like a huge canvas:/ Can't help you optimise unfortunately... but a good start sounds like finding the necessary nodes to calculate, maybe I can even built something like this into the standard calculation system.
     
  15. Rimevel

    Rimevel

    Joined:
    Dec 8, 2015
    Posts:
    10
    Actually 3 nodes were enough to reach 1ms :p
    After a lot of experimenting I actually started a new node project from scratch. Currently exploring different ways to structure the node data and the node to node relationship for as small performance hits as possible during runtime. I will let the editor handle restrictions and evaluation and just let the nodes do the traversal and execution of functionality.
     
    Last edited: Sep 1, 2016
  16. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ok I found the cause with this issue on the repo, maybe you actually are rc1 on github?
    Anyway, I'm making a fix for this, basically when not called from the GUI (RTCanvasCalculator being the only example) there's currently a bug where the framework reinitiates every frame... I think we've found the 1ms lol...
     
  17. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Should be fixed in this commit:)
    Seneral
     
  18. Rimevel

    Rimevel

    Joined:
    Dec 8, 2015
    Posts:
    10
    Will give it a test when I have time. Sounds like its highly likely that it was the culprit.
     
  19. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Finally!
    With the help of Baste I synced develop with master, now having all the great features of develop on master in a new stable release v0.2!

    With the new release, I also uploaded the mkDocs documentation that was build alongside this issue.

    Alot of work from alot of people went into this update, which was basically every commit since the 18th February onto develop! Thanks for everyone who contributed!

    The changelog:
     
    Last edited: Sep 5, 2016
    M_Dizzle likes this.
  20. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Hi @Seneral

    I've downloaded the new version although I haven't looked into any of the new features yet.

    Thought you'd like to know I found out why the popup menu wasn't working at runtime. Turns out it was a problem with the RTNodeEditor.cs script. GUI.BeginGroup/EndGroup is only called if the adapt to screen option is false. So to fix it, I just added an extra condition in case it was on.

    Here's the fixed OnGUI call from RTNodeEditor.cs :

    Code (CSharp):
    1. public void OnGUI ()
    2.         {
    3.             cache.AssureCanvas ();
    4.             NodeEditor.checkInit (true);
    5.             if (NodeEditor.InitiationError)
    6.             {
    7.                 GUILayout.Label ("Initiation failed! Check console for more information!");
    8.                 return;
    9.             }
    10.  
    11.             try
    12.             {
    13.                 canvasRect = screenSize ? new Rect(0, 0, Screen.width, Screen.height) : specifiedCanvasRect;
    14.                 if (!screenSize && specifiedRootRect.max != specifiedRootRect.min) GUI.BeginGroup (specifiedRootRect, NodeEditorGUI.nodeSkin.box);
    15.                 if (screenSize) GUI.BeginGroup(canvasRect, NodeEditorGUI.nodeSkin.box); //added this to make it work
    16.                 NodeEditorGUI.StartNodeGUI ();
    17.  
    18.              
    19.                 canvasRect.width -= 200;
    20.                 cache.editorState.canvasRect = canvasRect;
    21.                 NodeEditor.DrawCanvas (cache.nodeCanvas, cache.editorState);
    22.  
    23.                 GUILayout.BeginArea (new Rect (canvasRect.x + cache.editorState.canvasRect.width, cache.editorState.canvasRect.y, 200, cache.editorState.canvasRect.height), NodeEditorGUI.nodeSkin.box);
    24.                 SideGUI ();
    25.                 GUILayout.EndArea ();
    26.  
    27.                 NodeEditorGUI.EndNodeGUI ();
    28.  
    29.                 if (!screenSize && specifiedRootRect.max != specifiedRootRect.min) GUI.EndGroup ();
    30.                 if (screenSize) GUI.EndGroup(); //end group if adapt to screen on
    31.             }
    32.             catch (UnityException e)
    33.             { // on exceptions in drawing flush the canvas to avoid locking the ui.
    34.                 cache.NewNodeCanvas ();
    35.                 NodeEditor.ReInit (true);
    36.                 Debug.LogError ("Unloaded Canvas due to exception in Draw!");
    37.                 Debug.LogException (e);
    38.             }
    39.         }
    It's not a big thing, although it did take me a long time to figure it out, but I thought you'd like to know!

    Glad to see the new docs! I don't know if this is obvious to other people and I'm just thick, but one thing I've struggled with as I'm learning this framework is how to store classes and complex data inside nodes. I was finding that I'd assign something in the Create() function of a node and it would be empty when it came to the Calculate and GUI methods. The key is that in the Create method, you must save all these values back into the node object before it returns out. Like this:

    Code (CSharp):
    1. public class SynthNode : Node
    2.     {
    3.         public new const string ID = "synthNode";
    4.         public override string GetID { get { return ID; } }
    5.  
    6.         //custom classes in this node
    7.         [SerializeField]
    8.         public List<SynthControl> controls = new List<SynthControl>();
    9.         public MasterControl master;
    10.         public OscillatorControl osc1, osc2;
    11.  
    12.         public override Node Create(Vector2 pos)
    13.         {
    14.             SynthNode node = ScriptableObject.CreateInstance<SynthNode>();
    15.  
    16.             node.name = "Synth Node";
    17.             node.rect = new Rect(pos.x, pos.y, 150, 100);
    18.          
    19.             //store values in node.[value], not [value] to store them for use later
    20.             node.master = new MasterControl(node, "Synth");
    21.             node.osc1 = new OscillatorControl(node, "OSC 1");
    22.             node.osc2 = new OscillatorControl(node, "OSC 2");
    23.             node.master.CreateInputs(node);
    24.             node.osc1.CreateInputs(node);
    25.             node.osc2.CreateInputs(node);
    26.  
    27.             node.controls.AddRange(new SynthControl[] { node.master, node.osc1, node.osc2 });
    28.    
    29.  
    30.             return node;
    31.         }
    32.        
    33. protected override void NodeGUI()
    34.   {
    35. osc1.Draw();
    36. }
    37. }
    38.  
    It's something I didn't get, so may be worth talking about in the docs?
     
  21. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thanks so much for you note and your fix! I'll make a hotfix in a moment:)
    Also, no problem including that in the docs... it's weird, I know, based on a (weird/bad) design decision I made in the early days in order to make node fetching possible. Maybe I'll find a way to change that. For now, you can find more information why here:)
     
  22. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    M_Dizzle likes this.
  23. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Glad to help out in some small way!

    Maybe on that part of the doc include a small example? Like use node.myVar = value, not myVar = value
     
  24. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Well that's what the ExampleNode is for. Maybe instead I should include it in the docs through a screen/syntax highlighted text...
     
  25. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Fair enough :)

    I was wondering if you could give me a bit of help with the GUIScaleUtility

    I'm trying to make my own custom rotating knob control, but the zooming breaks the positioning - here's example screens: http://imgur.com/a/xshMA

    it works like this;

    Code (CSharp):
    1. public static float RotaryKnob(float value, float min, float max, string name)
    2.         {
    3.             float angleLimit = 130;
    4.             float angle = -angleLimit + ((angleLimit*2) * (value/(max-min))); //convert value to angle
    5.             float horizontalSpeed = 400;
    6.            
    7.             GUI.skin = getSkin(); //get my custom skin function
    8.             Rect rect = GUILayoutUtility.GetRect(64, 64); //get position
    9.  
    10.             GUI.Box(rect,name, getStyle("rotary_bg")); //draw knob background
    11.            
    12.             Matrix4x4 old = GUI.matrix; //store old rotation matrix
    13.  
    14.             Vector2 point = new Vector2(32,32); //center point to rotate around
    15.      
    16.             GUI.BeginGroup(rect); //group to apply rotation to
    17.  
    18.             GUIUtility.RotateAroundPivot(angle, point); //rotate in group context around point
    19.            
    20.             Rect knobRect = new Rect(0, 0, 64,64);
    21.  
    22. //draw and handle knob
    23.             if (GUI.RepeatButton(knobRect, "", getStyle("rotary_knob")))
    24.             {
    25.                 Debug.Log(name + " : " + value);
    26.                 Debug.Log(Input.GetAxis("Mouse X"));
    27.                 if (Input.GetAxis("Mouse X") > 0 && angle < angleLimit) {
    28.                     angle += horizontalSpeed * Time.deltaTime;
    29.                     Debug.Log("mouse moved right: " + angle);
    30.                 }
    31.                 if (Input.GetAxis("Mouse X") < 0 && angle > -angleLimit) {
    32.                     angle -= horizontalSpeed * Time.deltaTime;
    33.                     Debug.Log("mouse moved left: " + angle);
    34.                 }
    35.                 Debug.Log("angle: " + angle);
    36.             }
    37.            
    38.             GUI.EndGroup();
    39.            
    40.             value = min + (((angle + angleLimit)/(angleLimit*2))*(max- min)); //convert angle back to value
    41.             GUI.matrix = old; //replace old rotation matrix
    42.             GUI.skin = EndSkin(); //back to normal skin
    43.  
    44.             return value;
    45.         }
    I've been fiddling with the GUIScaleUtility, trying different things like scaling the Vector2 point or rect, but I don't really know what I'm doing with it - what should I be applying scaling to? (if anything?)
     
  26. PitHeimes

    PitHeimes

    Joined:
    Oct 18, 2015
    Posts:
    322
    Hey Seneral,

    thanks for this amazing framework. Brilliant.

    Just a quick question: Everytime I go into playmode and come back I have to reload the canvas from scene.
    I reload it, it is there, and after the first interaction (click or drag) it disappears again. I have to reload it again from scene and after that everything works fine. This is reproducable in my project a 10/10 times :(

    If you have some spare time, it would be great if you could guide me where I should take a look at :)
     
  27. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yes, seems that 'GUIUtility.RotateAroundPivot' does not play well with scaling. If there is any other way you can solve the rotation, I would really advise to use that. If you really need to use the GUI matrix rotation, then I'll help you get the position correctly adjusted (which is not easy)...

    As a quick explanation: Default Unity scaling is badly implemented, the clipping rect is actually scaled, too, so nodes would be cut off at the edges. GUIScaleUtility basically takes the group (and with some reflection magic) extends/shrinks the groups so when it is scaled down/up, it perfectly matches the window.
    This does cause a different problem though, which is offset: The origin MUST be the zooming origin, not (0,0)...
    So the scaling function returns an offset from the new (0,0) of the modified drawing rect to the zooming origin (the center in our case), which has to be applied on the topmost GUI stuff to work. Using that we have a central origin to work from, which is scaled to... Usually it is enough to apply it to topmost groups (like nodes).

    But in your case you would somehow need to transform the point so it works with the GUI matrix rotation.
    We have a few variables and functions available to accomplish that: NodeEditor.curEditorState.zoomPanAdjust being the offset, *.zoomPos the local center, *. zoom the current zoom.
    You might also need to transform the spaces of the rotation pivot using GUIScaleUtility.GUIToScreenSpace or *.GUIToScaledSpace. But as we cannot exactly say what's going on internally of the GUI system, it's basically a matter of trial and error:/

    If you figure you must use the GUI matrix scaling, I may be able to help you figure out a way:)
     
  28. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thanks Pit!
    Yeah, currently there is no caching system for scene saving as it is for asset saving.
    That does mean you can loose your work if you don't save before triggering an assembly reload (playmode switch, script reload). Thanks for pinging me on this, I've kinda forgot about it. It shouldn't be too hard to implement (not as hard as asset save caching, that is)... Probably at the weekend I'll find enough time:)
     
  29. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Damn, I was afraid it'd be that complex. Unfortunately there isn't another way to rotate GUI elements.

    So, I'll try looking into the curEditorState variables and see if I can figure something out from them and get back to you. If all else fails, I guess I could just pre-render all the rotation states to a texture and then use GUI.DrawTextureWithTexCoords to select the right state and essentially fake it.

    Or just use sliders instead and not bother with rotating knobs at all.

    Thanks for the tip - will look into it
     
  30. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Hi @Seneral

    I've found a couple of issues -

    1. If you create nodes in the runtime editor, stop playing and then restart, it throws this error:

    Am I missing something?

    2. I'm finding that if I create a board in the editor window, then I can't seem to read the inputs when in the runtime editor. Now this might be related to what I'm trying to do. To give a brief overview of what I'm doing;

    I've created reusable SynthControls that I can add to nodes - here's an example;

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6. using System.Text;
    7. using NodeEditorFramework;
    8. using NodeReactor;
    9. using NodeReactor.Utilities;
    10.  
    11. namespace NodeReactor
    12. {
    13.   [System.Serializable]
    14.   public class MasterControl : SynthControl
    15.   {
    16.   public float amp =0.7f;
    17.   public NodeInput ampInput;
    18.  
    19.   public MasterControl(SynthNodeBase node,string name) : base(node)
    20.   {
    21.   this.name = name;
    22.   }
    23.  
    24.   public override void CreateInputs(SynthNodeBase node)
    25.   {
    26.   base.CreateInputs(node);
    27.   ampInput = node.CreateInput("Amp", "Float", NodeSide.Top);
    28.   controlInputs.Add(ampInput);
    29.   }
    30.  
    31.   public override void DrawControlInputs()
    32.   {
    33.   base.DrawControlInputs();
    34.   }
    35.  
    36.   public override void DrawGUI()
    37.   {
    38.   base.DrawGUI();
    39.   GUILayout.BeginVertical();
    40.   if (ampInput.connection != null)
    41.   amp = Mathf.Abs(ampInput.connection.GetValue<float>());
    42.    
    43.   amp = SynthGUI.VerticalSlide(amp, 0, 1,"VOL");
    44.    
    45.   GUILayout.EndVertical();
    46.   }
    47.  
    48.   public override float Calculate()
    49.   {
    50.   base.Calculate();
    51.   if (ampInput.connection != null)
    52.   {
    53.   amp = Mathf.Abs(ampInput.connection.GetValue<float>());
    54.    
    55.   }
    56.   return amp;
    57.   }
    58.   }
    59. }
    60.  
    61.  
    and it's used in a node like this;

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Reflection;
    6. using NodeEditorFramework;
    7. using NodeEditorFramework.Utilities;
    8. //using UnityEditor;
    9. using System;
    10.  
    11. using NodeReactor.Utilities;
    12. namespace NodeReactor
    13. {
    14.   [Node(false, "Synth Composer/Synth")]
    15.   public class SyntheNode : SynthNodeBase
    16.   {
    17.   public new const string ID = "syntheNode";
    18.   public override string GetID { get { return ID; } }
    19.  
    20.   //sequencer in, Audio out
    21.   public NodeInput sequencerInput;
    22.   public NodeOutput audioOut;
    23.  
    24.   //controls in this node
    25.   public MasterControl master;
    26.   public OscillatorControl osc1, osc2;
    27.  
    28.   public override Node Create(Vector2 pos)
    29.   {
    30.   SyntheNode node = ScriptableObject.CreateInstance<SyntheNode>();
    31.  
    32.   node.name = "Synthesiser";
    33.   node.rect = new Rect(pos.x, pos.y, 150, 100);
    34.  
    35.   //store values in node.[value], not [value] to store them for use later
    36.   node.sequencerInput = node.CreateInput("Sequencer In", "SequencerData");
    37.   node.audioOut = node.CreateOutput("Audio Out", "Float");
    38.  
    39.   node.master = new MasterControl(node, "Synth");
    40.   node.osc1 = new OscillatorControl(node, "OSC 1");
    41.   node.osc2 = new OscillatorControl(node, "OSC 2");
    42.   node.master.CreateInputs(node);
    43.   node.osc1.CreateInputs(node);
    44.   node.osc2.CreateInputs(node);
    45.  
    46.   node.controls.AddRange(new SynthControl[] { node.master, node.osc1, node.osc2 });
    47.  
    48.  
    49.   return node;
    50.   }
    51.  
    52.   protected override void NodeGUI()
    53.   {
    54.   GUI.skin = SynthGUI.getSkin();
    55.   rect.width = 500; rect.height = 300;
    56.   GUILayout.BeginHorizontal(GUI.skin.box);
    57.  
    58.   GUILayout.BeginVertical(SynthGUI.getSkin().box, GUILayout.Width(80));
    59.   GUILayout.Label("SYNTH5000", SynthGUI.getSkin().label);
    60.   sequencerInput.DisplayLayout();
    61.   GUILayout.EndVertical();
    62.   base.NodeGUI();
    63.  
    64.   GUILayout.BeginVertical(SynthGUI.getSkin().box, GUILayout.Width(80));
    65.   audioOut.DisplayLayout();
    66.   GUILayout.EndVertical();
    67.  
    68.   GUILayout.EndHorizontal();
    69.  
    70.   GUILayout.BeginHorizontal();
    71.   GUILayout.Box("", GUILayout.Width(80));
    72.   master.DrawGUI();
    73.   osc1.DrawGUI();
    74.   osc2.DrawGUI();
    75.  
    76.   GUILayout.EndHorizontal();
    77.  
    78.   GUI.skin = SynthGUI.EndSkin();
    79.   if (GUI.changed)
    80.   NodeEditor.Calculator.RecalculateFrom(this);
    81.   }
    82.  
    83.   public override bool Calculate()
    84.   {
    85.  
    86.   float value = 0;
    87.   value += osc1.Calculate(osc1.freq);
    88.   value += osc2.Calculate(osc2.freq);
    89.   value /= 2;
    90.   value *= master.Calculate();
    91.  
    92.  
    93.   //Debug.Log("synnth  calculated");
    94.   //audioOut.SetValue<float[]>(data);
    95.   audioOut.SetValue<float>(value);
    96.   return true;
    97.   }
    98.   }
    99. }
    100.  
    If I create the nodes entirely in the runtime editor, it works (but will throw the first error upon re-running the programme) but if I create it in the editor and then load and run it, all the node inputs return 0.

    Sorry if the second part of this post is too unique to me to want to get involved with, but if you can point me right it'd be massively appreciated
     
  31. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Here is a useful little static I wrote for layout. Maybe you guys can get some use:
    Code (CSharp):
    1. public static class LayoutUtils
    2.     {      
    3.         public static Rect BoxWithChildren(Node n)
    4.         {
    5.             Rect retVal=new Rect();
    6.             retVal.xMax = float.MinValue;
    7.             retVal.yMax = float.MinValue;
    8.             if (n.rect.xMin < retVal.xMin)
    9.             {
    10.                 retVal.xMin = n.rect.xMin;
    11.             }
    12.             if (n.rect.yMin < retVal.yMin)
    13.             {
    14.                 retVal.yMin = n.rect.yMin;
    15.             }
    16.             if (n.rect.xMax > retVal.xMax)
    17.             {
    18.                 retVal.xMax = n.rect.xMax;
    19.             }
    20.             if (n.rect.yMax > retVal.yMax)
    21.             {
    22.                 retVal.yMax = n.rect.yMax;
    23.             }
    24.  
    25.             foreach (NodeOutput no in n.Outputs)
    26.             {
    27.                 Node node = no.GetNodeAcrossConnection();
    28.                 if (no != null)
    29.                 {
    30.                     Rect r = BoxWithChildren(node);
    31.                     if (r.xMin < retVal.xMin)
    32.                     {
    33.                         retVal.xMin = r.xMin;
    34.                     }
    35.                     if (n.rect.yMin < retVal.yMin)
    36.                     {
    37.                         retVal.yMin = r.yMin;
    38.                     }
    39.                     if (n.rect.xMax > retVal.xMax)
    40.                     {
    41.                         retVal.xMax = r.xMax;
    42.                     }
    43.                     if (n.rect.yMax > retVal.yMax)
    44.                     {
    45.                         retVal.yMax = r.yMax;
    46.                     }
    47.                 }
    48.             }
    49.             return retVal;
    50.         }
    51.     }
    52.  
     
    Seneral likes this.
  32. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Nice, thank you @rohmer:) Maybe we can create an auto-layouting system in the future which could use the same concept of reacting to events as the plans for the calculation are at the moment...
     
  33. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thank you for your report.
    This should cause the warning 'NodeCanvas 'Calculation Canvas' contained broken (null) nodes! Automatically fixed!' to show up, right?
    Currently the RTNodeEditor directly works with the canvases. When adding nodes it automatically references them in the save file, but at runtime it does NOT save the node as assets to the save file. So it will result in an null instead of a node the next time the canvas is loaded.
    It's an easy fix though, RTNodeEditor should just create a working copy to not disrupt the canvas save file (you can still manually apply the changes, though).
    It's fixed in this commit:)

    I'll need to take a closer look at your second problem, will come back to you later;)
     
  34. PitHeimes

    PitHeimes

    Joined:
    Oct 18, 2015
    Posts:
    322
    Hey Seneral,

    not putting any pressure of course, just a small "reminder" :p
    Any news on the Caching when using SceneSave yet? :)
     
  35. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ok can't exactly make out what your problem is. If your input would return 0, does that mean the canvas didn't get calculated at all? Have you made sure the values you set in your previous nodes is not 0?

    Maybe I understand you wrong and you mean your Inputs are objects you referenced and those references are lost. This could be the case if
    1. You reference scene objects from an asset-save (the other way around is fine)
    2. You reference other unity objects that are neither part of the scene nor saved as an asset. For example, a ScriptableObject. In that case you manually have to save them. If you want to save them along with the canvas save file, you can override the functions GetScriptableObjects and CopyScriptableObjects in Node:
    Code (csharp):
    1. #region Additional Serialization
    2. /// <summary>
    3. /// Returns all additional ScriptableObjects this Node holds.
    4. /// That means only the actual SOURCES, simple REFERENCES will not be returned
    5. /// This means all SciptableObjects returned here do not have it's source elsewhere
    6. /// </summary>
    7. public virtual ScriptableObject[] GetScriptableObjects () { return new ScriptableObject[0]; }
    8. /// <summary>
    9. /// Replaces all REFERENCES aswell as SOURCES of any ScriptableObjects this Node holds with the cloned versions in the serialization process.
    10. /// </summary>
    11. protected internal virtual void CopyScriptableObjects (System.Func<ScriptableObject, ScriptableObject> replaceSerializableObject) {}
    12. public void SerializeInputsAndOutputs(System.Func<ScriptableObject, ScriptableObject> replaceSerializableObject) {}
    13. #endregion
    If that is the case and you need further help getting this to work, I can help you out. Plan on doing a documentation article about this;)
     
  36. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yes I'm working on it, but thank you for pinging me;)
     
    PitHeimes likes this.
  37. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Actually I am in the process of coding that out.
     
  38. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Just finished it in this commit. My assumption that it wouldn't be as hard as asset saving turned out to be true, but still I haven't had much time on bug testing even though it should be solid. So please look out for any bug or oddity;)
     
  39. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Very cool, I'm excited to see what you'll come up with:)
     
  40. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Hi @Seneral

    Your first commit solved my first issue, but your most recent one throws a warning and an error when using the RT Node Editor.


    Also, these errors are reported when trying to build the project;

     
  41. vicenterusso

    vicenterusso

    Joined:
    Jan 8, 2013
    Posts:
    130
    I need to add a simple variable inside "Node", so I extended "Node". The problem happens when I have to extend many more classes, like NodeCanvas, NodeEditorWindow, NodeEditorSaveManager, etc

    So the question is: I dont want to touch this project code, I want to extend it (to support future implementation) .. can you see any viable solution?
     
  42. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thanks for the report once again, as I said I didn't test it in detail because I wanted to commit it that evening. Sorry that you've still an error to cope with:(
     
    M_Dizzle likes this.
  43. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I've thought about this before in this 'issue', basically I would declare NodeCanvas, Node and other major core classes as 'partial'. That way you can add unlimited additional source files also marked as partial with additional variables and functions.
    But you cannot change functions unlike f.E. Node where you could extend and override. That would be recommended for Node if what you add is specific to your use case, whereas for a general extension I would complement with a partial class addition.

    I did not yet try this concept so maybe you can mark them partial and see if that works for you and whether it suits your needs so I can think of implementing it into the framework?
     
  44. vicenterusso

    vicenterusso

    Joined:
    Jan 8, 2013
    Posts:
    130
    Interesting. Completely forgot the partial classes. I will try let you know, thanks

    Edit:

    @Seneral , it works, and I think its a good idea to implement on your repo. You just need to mark "Node" as partial and we are good to go..

    Code (CSharp):
    1. namespace NodeEditorFramework
    2. {
    3.     public partial class Node
    4.     {
    5.         public int MyVar;
    6.  
    7.     }
    8. }
     
    Last edited: Sep 14, 2016
  45. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Nice:) Will make a commit sometime this evening...
     
    vicenterusso likes this.
  46. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Here is the start of my autolayout. Not satisfied yet, but it works.
    Code (CSharp):
    1.     public class LayoutTools
    2.     {
    3.         private static IDictionary<Node, Rect> canvasUsed;
    4.         private const float LAYOUT_SEP_Y = 30;
    5.         private const float LAYOUT_SEP_X = 30;
    6.  
    7.         public static void GenerateLayout(NodeCanvas canvas, Rect canvasSize)
    8.         {
    9.             canvasUsed = new Dictionary<Node, Rect>();
    10.             IList<Node> parentNodes = new List<Node>();
    11.             foreach(Node node in canvas.nodes)
    12.             {
    13.                 bool isParent = true;
    14.                 if(node.Outputs!=null)
    15.                 {
    16.                     foreach(NodeOutput ni in  node.Outputs)
    17.                     {
    18.                         if(ni.GetNodeAcrossConnection()!=null)
    19.                         {
    20.                             isParent = false;
    21.                         }
    22.                     }
    23.                 }
    24.                 if (isParent)
    25.                     parentNodes.Add(node);
    26.             }
    27.  
    28.  
    29.             // We have our parent nodes, now we need to build the tree(s)
    30.             // We will begin by guessing the location of each parent node
    31.             int nodeCount = 0;
    32.             foreach(Node node in parentNodes)
    33.             {
    34.                 float nodeX = canvasSize.center.x-((canvasSize.width / parentNodes.Count) * (nodeCount + 1));
    35.                 float nodeY = canvasSize.center.y;
    36.                 node.rect.center = new Vector2(nodeX, nodeY);
    37.                 bool overlap = true;
    38.                 int counter = 1;
    39.                 while (overlap)
    40.                 {
    41.                     overlap = false;
    42.                     foreach (KeyValuePair<Node, Rect> kvp in canvasUsed)
    43.                     {
    44.                         if (node.rect.OverLaps(kvp.Value))
    45.                             overlap = true;
    46.                     }
    47.                     if (overlap)
    48.                     {
    49.                         if(counter%2==0)
    50.                         {
    51.                             node.rect.x = (nodeX + (counter * (node.rect.width / 16)));
    52.                         } else
    53.                         {
    54.                             node.rect.x = (nodeX - (counter * (node.rect.width / 16)));
    55.                         }
    56.                     }
    57.                     counter++;
    58.                 }
    59.                     canvasUsed.Add(node, node.rect);
    60.                 if (node.Inputs != null)
    61.                 {
    62.                     foreach (NodeInput ni in node.Inputs)
    63.                     {
    64.                         Node childNode = ni.GetNodeAcrossConnection();
    65.                         if(childNode!=null)
    66.                             placeNode(childNode, ni.side, node);
    67.                     }
    68.                 }
    69.             }
    70.         }
    71.  
    72.         /// <summary>
    73.         /// Place Node will build the tree as compact as it can
    74.         /// </summary>
    75.         /// <param name="node">The node we are working on</param>
    76.         /// <param name="side">The side it is from the parent</param>
    77.         /// <param name="parentNode">The parent</param>
    78.         private static void placeNode(Node node, NodeSide side, Node parentNode)
    79.         {
    80.             Vector2 center = new Vector2();
    81.             int siblings = siblingCount(node, false);
    82.             float nodeX = 0, nodeY = 0;
    83.             bool placed = false;
    84.             float nodeOffsetX = 0, nodeOffsetY = 0;
    85.             while (!placed)
    86.             {
    87.                 switch (side)
    88.                 {
    89.                     case (NodeSide.Bottom):
    90.                         nodeY = parentNode.rect.yMax - LAYOUT_SEP_Y - (node.rect.height / 2);
    91.                         nodeX = parentNode.rect.center.x - ((siblings / 2) * (LAYOUT_SEP_X + node.rect.width));
    92.                         break;
    93.                     case (NodeSide.Top):
    94.                         nodeY= parentNode.rect.yMin - LAYOUT_SEP_Y - (node.rect.height / 2);
    95.                         nodeX = parentNode.rect.center.x - ((siblings / 2) * (LAYOUT_SEP_X + node.rect.width));
    96.                         break;
    97.                     case (NodeSide.Left):
    98.                         nodeY = parentNode.rect.center.y - ((siblings / 2) * (LAYOUT_SEP_Y + node.rect.height));
    99.                         nodeX = parentNode.rect.xMin - LAYOUT_SEP_X - (node.rect.width / 2);
    100.                         break;
    101.                     case (NodeSide.Right):
    102.                         nodeY = parentNode.rect.center.y - ((siblings / 2) * (LAYOUT_SEP_Y + node.rect.height));
    103.                         nodeX = parentNode.rect.xMax + LAYOUT_SEP_X + (node.rect.width / 2);
    104.                         break;
    105.                 }
    106.                 Rect nodeRect = node.rect;
    107.                 nodeRect.center = new Vector2(nodeX + nodeOffsetX, nodeY + nodeOffsetY);
    108.                 bool overlap = false;
    109.                 foreach(KeyValuePair<Node,Rect> kvp in canvasUsed)
    110.                 {
    111.                     if (nodeRect.OverLaps(kvp.Value))
    112.                         overlap = true;
    113.                 }
    114.                 if(overlap)
    115.                 {
    116.                     switch (side)
    117.                     {
    118.                         case (NodeSide.Bottom):
    119.                             nodeOffsetX += (node.rect.width / 16);
    120.                             break;
    121.                         case (NodeSide.Top):
    122.                             nodeOffsetX += (node.rect.width / 16);
    123.                             break;
    124.                         case (NodeSide.Left):
    125.                             nodeOffsetY += (node.rect.height / 16);
    126.                             break;
    127.                         case (NodeSide.Right):
    128.                             nodeOffsetY += (node.rect.height / 16);
    129.                             break;
    130.  
    131.                     }
    132.                 } else
    133.                 {
    134.                     node.rect = nodeRect;
    135.                     canvasUsed.Add(node, nodeRect);
    136.                     placed = true;
    137.                 }
    138.             }
    139.             foreach (KeyValuePair<Node, NodeSide> kvp in getChildrenDirection(node))
    140.                 placeNode(kvp.Key, kvp.Value,node);
    141.         }
    142.  
    143.         private static List<Node> getChildren(Node n)
    144.         {
    145.             List<Node> returnList = new List<Node>();
    146.             if(!isLeaf(n))
    147.             {
    148.                 foreach(NodeInput ni in n.Inputs)
    149.                 {
    150.                     Node nd = ni.GetNodeAcrossConnection();
    151.                     if (nd != null)
    152.                         returnList.Add(nd);
    153.                 }
    154.             }
    155.             return (returnList);
    156.         }
    157.  
    158.         private static IDictionary<Node,NodeSide> getChildrenDirection(Node n)
    159.         {
    160.             IDictionary<Node, NodeSide> returnList = new Dictionary<Node, NodeSide>();
    161.             if (!isLeaf(n))
    162.             {
    163.                 foreach (NodeInput ni in n.Inputs)
    164.                 {
    165.                     Node nd = ni.GetNodeAcrossConnection();
    166.                     if (nd != null)
    167.                         returnList.Add(nd,ni.side);
    168.                 }
    169.             }
    170.             return (returnList);
    171.         }
    172.  
    173.         private static bool isLeaf(Node n)
    174.         {
    175.             if(n.Outputs==null)
    176.             {
    177.                 return false;
    178.             }
    179.             foreach(NodeOutput no in n.Outputs)
    180.             {
    181.                 if(no.GetNodeAcrossConnection()!=null)
    182.                 {
    183.                     return true;
    184.                 }
    185.             }
    186.  
    187.             return false;
    188.         }
    189.  
    190.         private static int siblingCount(Node node, bool useNodeType)
    191.         {
    192.             // This shouldnt be used on parent nodes
    193.             if(node.Outputs==null)
    194.             {
    195.                 return -1;
    196.             }
    197.  
    198.             int count = 0;
    199.             foreach(NodeOutput no in node.Outputs)
    200.             {
    201.                 Node parentNode = no.GetNodeAcrossConnection();
    202.                 if(parentNode!=null)
    203.                 {
    204.                     foreach(NodeInput ni in parentNode.Inputs)
    205.                     {
    206.                         Node n = ni.GetNodeAcrossConnection();
    207.                         if(useNodeType)
    208.                         {
    209.                             if(n.GetID==node.GetID)
    210.                             {
    211.                                 count++;
    212.                             }
    213.                         }
    214.                         else
    215.                         {
    216.                             count++;
    217.                         }
    218.                     }
    219.                 }
    220.             }
    221.             return count;
    222.         }
    223.     }
     
  47. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Was an easy fix in the end:) Made this commit.
    While doing this I asked myself whether it would be better to make use of the cache system in playmode in the editor... currently it is not using it.
    So when you open up RTNodeEditor, it will load the last canvas opened with the NodeEditor window or even works on the same canvas as the window when both are opened at the same time. And then RTNodeEditor would write to lastSession.asset and the lastSession scene save automatically.
    Do you prefer this or should I leave the caching system disabled in playmode?
    Might need a bit of change to work but it's fine.
     
  48. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thanks for so much effort, it seems really complicated:eek:
    Although I'm getting weird results... What is the canvasSize supposed to be set to? From what I can tell it's only used to offset the children.
    Maybe I'm not getting something here... Also note that I had to change the rect.OverLap method to rect.Overlap to work, maybe it was a modified, custom function?
     
  49. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Added partial keywords to core classes with this commit.

    Affected classes:
    Criteria were:
    If you think additional classes should be added, don't hesitate to ask.
    Note: I intentianally didn't include any utility and NodeEditorWindow, as that is recommended to fully replace if you want to modify it to your needs (there are no dependancies to it).
     
    vicenterusso likes this.
  50. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Hi @Seneral the latest commit is still throwing errors for me, the editor still works despite these errors, although whenever I create a new node, it creates 2.

    Can confirm that it works fine in play mode now though

    As for my other problem - it seems to be caused by the fact that the Create Method of the nodes isn't called when loading a canvas. Thing is, I'm trying to store references to my Input and Output nodes, as I'm adding controls that have their own set of nodes. By separating my controls from my nodes, I can reuse controls to make new synth components. This screenshot should help illustrate what I mean;



    A synthesiser might have 2 or more oscillators, but maybe you'd want to add an LFO (or other input) as a control for one of the controls on your synthesiser.

    So when creating a node, I've got this going on;

    Code (CSharp):
    1. public override Node Create(Vector2 pos)
    2.         {
    3.             SyntheNode node = ScriptableObject.CreateInstance<SyntheNode>();
    4.  
    5.             node.name = "Synthesiser";
    6.             node.rect = new Rect(pos.x, pos.y, 150, 100);
    7.  
    8.             //store values in node.[value], not [value] to store them for use later
    9.             node.sequencerInput = node.CreateInput("Sequencer In", "SequencerData");
    10.             node.audioOut = node.CreateOutput("Audio Out", "Float");
    11.  
    12.             node.master = new MasterControl(node, "Synth");
    13.             node.osc1 = new OscillatorControl(node, "OSC 1");
    14.             node.osc2 = new OscillatorControl(node, "OSC 2");
    15.             node.master.CreateInputs(node);
    16.             node.osc1.CreateInputs(node);
    17.             node.osc2.CreateInputs(node);
    18.  
    19.             node.controls.AddRange(new SynthControl[] { node.master, node.osc1, node.osc2 });
    20.  
    21.  
    22.             return node;
    23.         }
    The Oscillator Control is made simply;
    Code (CSharp):
    1. public OscillatorControl(SynthNodeBase node, string name) : base(node)
    2.         {
    3.             this.node = node;
    4.             this.name = name;
    5.         }
    and then CreateInputs is called immediately after;
    Code (CSharp):
    1. public override void CreateInputs(SynthNodeBase node)
    2.         {
    3.             base.CreateInputs(node);
    4.             ampInput = node.CreateInput("Amp", "Float", NodeSide.Top);
    5.             freqInput = node.CreateInput("Freq", "Float", NodeSide.Top);
    6.             phaseInput = node.CreateInput("Phase", "Float", NodeSide.Top);
    7.  
    8.             controlInputs.AddRange(new NodeInput[] { ampInput, freqInput, phaseInput });
    9.         }
    This works fine when making the board from scratch, but when I load it, these variables return Null, because Create() was never run.

    The problem is that these variables ampInput,freqInput and phaseInput are not created when loading a file. What am I doing wrong, is there a proper way of doing this? Is referencing the Input/Output List<> with a number the only way to access the nodes?

    Thanks!
     
    Seneral likes this.