Search Unity

Simple node editor

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

  1. Aramilion

    Aramilion

    Joined:
    Apr 15, 2016
    Posts:
    23
    @TheCaptainJuneBug You can have 1 output branch to multiple inputs. So node A output is connected to inputs of nodes B, C, D...
    The reference in B,C,D to node A will be always in theese nodes ( this.Inputs[0].connection.body ). I always have only 1 input, so i can use that. :)

    NodeCanvas.png
     
    TheCaptainJuneBug likes this.
  2. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    @Aramilion is right, you can use the current system to represent tree canvases. It is not optimal regarding it's implementation, but with you own traversion or lookup code it is just fine, if you know what I mean.

    Additionally, but this is only visual, you can make the connections go vertical by using the apropriate overload when creating the knobs:)
    There are also other options you have that depend on what you will use it for. So if you can clarify I will probably be able to tell you more;)

    Also, I plan to make the traversial algorithm dynamic aswell. Currently, you have the standard calculation 'traversal algorithm' that traverses the nodes in a way that every node is able to calculate upon their predecessor's outputs. Then you'd also be able to implement your own traversial algorithm with other behaviour, for example if the canvas only represents an internal structure of your data, then when a change was made (the traversial algorithm is triggered) you could update your internal data. Or something completely different:)

    Btw, pic looks really cool Aramilion!
     
    Last edited: Jun 17, 2016
  3. Stormy102

    Stormy102

    Joined:
    Jan 17, 2014
    Posts:
    495
    Is there a way to query information on this and just using the node editor as a graphical way of displaying information?
     
  4. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yes, from external scripts you can get the currently loaded canvas like this:
    Code (csharp):
    1. NodeEditorWindow.MainNodeCanvas
    and then you could acess all your nodes and their connections with this (warning, quick pseudocode):
    Code (csharp):
    1. Node node = canvas.nodes[1]; // Get a node to access it's values
    2. Node[] predecessors = node.Inputs.Where((NodeInput in) => in.connection != null).Select((NodeInput in) => in.connection.body).ToArray (); // Get all predecessor nodes
    3. Node[] followUps = node.Outputs.SelectMany((NodeOutput out) => out.connections).Select((NodeInput in) => in.body).ToArray (); // Get all following nodes
    Or you could of course communicate to the rest of your system from within the nodes, for example in calculate or in the GUI if(GUI.changed). But then you would have to place your script to communicate with in an editor folder so that the script order allows you to access it;)

    Finally, there's also the option to use events. For an example, look at NodeEditorCallbackReceiver for ways to receive events (by extending a special monobehaviour or subscribing to static callbacks). You can then obviously extend this with your custom events relatively easily and then call something like this:
    Code (csharp):
    1. NodeEditorCallbacks.IssueMyEvent (params)
    If you need a different way, please tell me:)
     
    Stormy102 likes this.
  5. Stormy102

    Stormy102

    Joined:
    Jan 17, 2014
    Posts:
    495
    Ok, so I was thinking of defining a name for something, lets say "Simple Wall". I wanted to have stats, e.g. Weight: 20.5, Health = 60, Tag = "WoodThin". Then, for each upgrade, it moves along, to say "Reinforced Simple Wall". It's just easier than having a load of really user-hating variables in sub-classes and stuff like that. Any clues? (Beware, noob just entered this thread :eek:)
     
  6. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Do you already have concept about what structure you want to store your data in? You could either use the Node editor only as visualization and editing program and else don't depend on it, in that case you will have to store everything in a lightweight ScriptableObject and create a wrapper. Or you could store it in the canvas, even at runtime, which is the easiest to setup for you but you'd have the extra hassle of a storage format you have not so much control over...

    1. For a seperate Scriptable Object storage you'd need a good understanding of ScriptableObjects as they can be a real hassle. I'd recommend to first get this working without the Node Editor. Then you'd need to do a 'wrapper'; So when double clicking your custom asset you would want to open up the the node editor with a generated canvas that represents your storage file. It is possible but you'd have to take care of your format, a wrapper to transform it into a correctly laid-out canvas and an updater that adapts your format to changes made to the canvas in one of the the adove mentioned ways. I can help you with the node editor related stuff if you want, but still pretty though.

    2. Second option is to use the Node Editor save format. You'd need to access the canvas similary to the first way mentioned above, with a bit extras. Then you could extract information out of it. For an example take a look at RTCanvasCalculator in the develop branch. It is relatively easy to set up, you just need one simple extra node which I can help you with if you need. It would store the properties (weight, health, etc.) and have connections to other nodes it depends on or derives to (for example in a skill or crafting tree). These would be accessible by a script similar to above linked RTCanvasCalculator and mentioned in my previous post.

    Hope that helps you get started, if you need further help no problem:)
     
    Stormy102 likes this.
  7. Stormy102

    Stormy102

    Joined:
    Jan 17, 2014
    Posts:
    495
    I'm happy to just store them in the same way as they are displayed (strings, ints). I've already got quite a bit of knowledge with scriptable objects, so that isn't really a problem. What I essentially want is the ability to add and remove nodes and have it in a nice UI format. Which format would you recommend? I only want to access them at runtime, no need to edit them.
     
  8. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I meant for the editor editing, runtime would be imposssible without some major other stuff (serializing to file).
    I'd personally go with the canvas save format (1.) as it's easier, but if you already have the system up and running or you don't want to depend on the Node Editor Framework (for loading and general API) you might want to consider creating a wrapper instead (2.).
     
    Stormy102 likes this.
  9. Stormy102

    Stormy102

    Joined:
    Jan 17, 2014
    Posts:
    495
    At the moment, I want to handle the UI first, then saving it to a file (SerializableObject). Finally, I want to be able to reference the save and load from that?
    Also, I noticed that you can't have two connections going into one node. Can I change this or is it fixed?
     
  10. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    If you are ok with using the canvas at runtime to fetch your data, you'll just need to create one node basically and you're ready to create your datasets.
    That's currently not possible, it has been requested a few posts above and while I've already started working on it, there is a major problem I've yet not changed. Basically, imagine having both Inputs and Outputs allow multiple connections. How would you then delete specific connections? Currently you just loose the connection from the inout knob, but when there's multiple, it is not possible. I would have to find a way of selecting connections which is a bulk of work. So bear with me as I try to find enough time;)
    As a workaround you can just use multiple input knobs if you are ok with a limit or you could create knobs dynamically (shown a few posts before on the last page) which might introduce other problems as it has not yet been tested too extensively.
     
    Stormy102 likes this.
  11. Shiikarii

    Shiikarii

    Joined:
    Feb 6, 2014
    Posts:
    89
    Hello everyone,

    first of all I wanna say that this framework is absolutely amazing, but I have serveral problems when it I came to runtime calculations.

    Im working on a procedural pipline.. here r some images for better understandings:

    mesh_matrix.PNG procedural_editor.PNG

    So my problem is that i don't know how to call the calulcate method. It seems to work when I update the "AxisNode" with the X/Y/Z & Seed Values but I always get the same output from the "DisplayNode" and the worklist is also still the same. Here is the debug log - have a look at the code and u'll see how this debug is ment.

    debug_log.PNG

    Code (CSharp):
    1. public void BuildMap(NodeCanvas biom, float seed)
    2.     {
    3.         map = new float[width + 1, height + 1, depth + 1];
    4.  
    5.         this.biom = biom;
    6.  
    7.         AxisInput input = null;
    8.         DisplayNode output = null;
    9.  
    10.         foreach(Node node in biom.nodes)
    11.         {
    12.             if (node.GetType() == typeof(AxisInput))
    13.                 input = (AxisInput)node;
    14.             else if (node.GetType() == typeof(DisplayNode))
    15.                 output = (DisplayNode)node;
    16.         }
    17.  
    18.         //perlin = new PerlinNoise(seed);
    19.         //voronoi = new VoronoiNoise(seed);
    20.  
    21.         for (int x = 0; x < map.GetLength(0); x++)
    22.         {
    23.             for(int y = 0; y < map.GetLength(1); y++)
    24.             {
    25.                 for(int z = 0; z < map.GetLength(2); z++)
    26.                 {
    27.                     float volume = 0;
    28.  
    29.                     input.Seed = seed;
    30.                     input.X = x;
    31.                     input.Y = y;
    32.                     input.Z = z;
    33.  
    34.                     NodeEditor.curNodeCanvas = biom;
    35.                     NodeEditor.checkInit();
    36.                     NodeEditor.workList = biom.nodes;
    37.                     NodeEditor.StartCalculation();
    38.                     NodeEditor.Update();
    39.  
    40.                     volume += output.value;
    41.                     Debug.Log(input.X + "/" + input.Y + "/" + input.Z + " = " + output.value + " Work: " + NodeEditor.workList.Count);
    42.  
    43.                     //volume += perlin.FractalNoise3D(position.x + x, position.y + y, position.z + z, oct, frq, amp);
    44.                     //volume += (float)voronoi.VoronoiNoise3D(position.x + x, position.y + y, position.z + z, 0.02f, displacement, true) / 4;
    45.  
    46.                     float densityX = curveX.Evaluate((position.x + x) / (numX * width));
    47.                     float densityY = curveY.Evaluate((position.y + y) / (numY * height));
    48.  
    49.                     volume -= densityX + densityY;
    50.  
    51.                     map[x, y, z] = volume;
    52.                 }
    53.             }
    54.         }
    55.     }
    As u can see in this code i started adding simple perlin & voronoi noise and add them to the "volume", but I wanna have a more generic way how i can add multiple layers of noise.

    BR
    Shiikarii
     
  12. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Looks interesting!
    So what you basically want to do is let the canvas calculate for each terrain cell?
    To be honest, that will slow down the generation alot! The calculation has a very decent overhead.
    What you would want to do instead is only let it calculate once and it would create one action that handles the generation.
    So, your connection type would be of type Func<float, float, float, float>; Basically of return type float (for volume) with three input parameters (X, Y, Z). It would be build up ONCE during calculation and you'd execute it for each cell then. It's alot faster, trust me;)
    So a Node could output this:
    Code (csharp):
    1. Func<float, float, float, float> outputOperation = (X,Y,Z) => inputOperation (X,Y,Z) * doOperation(X,Y,Z)
    or similar. This way you'd end up with a single function to call. You could certainly optimise it even further by caching intermediate results but this would be a good start.
    With that said, the aproach you got is certainly right. But you don't have to specify the inputs manually, if you don't need to. Just do:
    Code (csharp):
    1. NodeEditor.checkInit();
    2. NodeEditor.RecalculateAll (biome);
    Then with the one-time calculation approach mentioned above you'd then need to fetch all outputs (assuming you have multiple channels, or one if you only have height/volume) and extract it's final value (here a Func<float, float, float, float>).

    Hope this helps you and leads you on the right track;)

    EDIT: YOu might want to take a look at this post and the followup ones, this guy basically want's to do the same. I've said him the same that there is a better approach and even made a graphic here trying to illustrate it;)
     
    Last edited: Jun 19, 2016
  13. Shiikarii

    Shiikarii

    Joined:
    Feb 6, 2014
    Posts:
    89
    Thank you for the fast reply Seneral,

    ok, I'll try to make a new connection like "float[,,]" and let the canvas calculate the hole array at once. That sounds indeed faster.

    i could give the "AxisNode" the following values to calculate the hole array:
    - float[,,]
    - Vector3 offset
    - seed

    Then i should calculate the canvas right?
    U said that i simply have to do that:

    Code (CSharp):
    1. NodeEditor.checkInit();
    2. NodeEditor.RecalculateAll (biome);
    Everytime when i try to call the "RecalculateAll" method, this error shows up:
    error.PNG
    I guess it is not the workList?

    and finally when that is done i should read the float[,,] from the "displayNode"?

    Sry for my simple questions, I just started yesterday with this framework ;)

    BR
    Shiikarii
     
  14. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yeah float[,,] with the given parameters will work just fine:) Not what I proposed but still alot better than calculating over and over again for each cell;)
    RecalculateAll simply calculates from every node that has no predecessors, that usually means it will work just fine, just as it does in your case. But the error that is thrown is in your AxisInput Node Calculation Method, trying to access an output from the Outputs list that does not exist. Additionally to a typical error with indices it might also be that you added the output creation code after you created the node in the canvas (so the node on the canvas doesn't have the output). So re-creating the node should work in that case.

    If you have specific input nodes you want to start calculating from, then (and only then) you need access to workList. After setting the workList to the input nodes and calling StartCalculation (Not RecalculateAll) it will calculate every node in the worklist and their child nodes recursively. But that should not be important for your use case.

    Fetching the result is done by finding the last node in the calculation chain. This can be done fully automatically by searching for a node that has no childs. But as there may be multiple matching nodes, it might not be desireable for you. Usually in terrain generators (assuming that's what your after) you have a special output node type which can only exist once.
    Either way you'd then fetch the result from that node.
     
  15. Shiikarii

    Shiikarii

    Joined:
    Feb 6, 2014
    Posts:
    89
    It doesn't work..

    what am I doing wrong?
    sry for all the trouble..

    temporary I'll use the simplified(slow) method until i get the pipeline to work.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using NodeEditorFramework;
    4. using NodeEditorFramework.Utilities;
    5.  
    6. [System.Serializable]
    7. [Node (false, "AxisInput")]
    8. public class AxisInput : Node
    9. {
    10.     public const string ID = "axisNode";
    11.     public override string GetID { get { return ID; } }
    12.  
    13.     public float Seed = 0;
    14.     public float X = 0, Y = 0, Z = 0;
    15.  
    16.     public override Node Create(Vector2 pos)
    17.     {
    18.         AxisInput node = CreateInstance<AxisInput>();
    19.  
    20.         node.name = "Axis Input";
    21.         node.rect = new Rect(pos.x, pos.y, 100, 100);
    22.  
    23.         node.CreateOutput("Seed", "Float", NodeSide.Right, 30);
    24.         node.CreateOutput("X", "Float", NodeSide.Right, 49);
    25.         node.CreateOutput("Y", "Float", NodeSide.Right, 67);
    26.         node.CreateOutput("Z", "Float", NodeSide.Right, 86);
    27.  
    28.         return node;
    29.     }
    30.  
    31.     protected internal override void NodeGUI()
    32.     {
    33.         Seed = RTEditorGUI.FloatField(new GUIContent("Seed", ""), Seed);
    34.  
    35.         X = RTEditorGUI.FloatField(new GUIContent("X", ""), X);
    36.         Y = RTEditorGUI.FloatField(new GUIContent("Y", ""), Y);
    37.         Z = RTEditorGUI.FloatField(new GUIContent("Z", ""), Z);
    38.  
    39.         if (GUI.changed)
    40.             NodeEditor.RecalculateFrom(this);
    41.     }
    42.  
    43.     public override bool Calculate()
    44.     {
    45.         Outputs[0].SetValue<float>(Seed);
    46.         Outputs[1].SetValue<float>(X);
    47.         Outputs[2].SetValue<float>(Y);
    48.         Outputs[3].SetValue<float>(Z);
    49.  
    50.         return true;
    51.     }
    52. }
    53.  
    Code (CSharp):
    1. public void BuildMap(NodeCanvas biome, float seed)
    2.     {
    3.         map = new float[width + 1, height + 1, depth + 1];
    4.  
    5.         this.biome = biome;
    6.  
    7.         AxisInput input = null;
    8.         DisplayNode output = null;
    9.  
    10.         foreach(Node node in biome.nodes)
    11.         {
    12.             if (node.GetType() == typeof(AxisInput))
    13.                 input = (AxisInput)node;
    14.             else if (node.GetType() == typeof(DisplayNode))
    15.                 output = (DisplayNode)node;
    16.         }
    17.  
    18.         //perlin = new PerlinNoise(seed);
    19.         //voronoi = new VoronoiNoise(seed);
    20.  
    21.         for (int x = 0; x < map.GetLength(0); x++)
    22.         {
    23.             for(int y = 0; y < map.GetLength(1); y++)
    24.             {
    25.                 for(int z = 0; z < map.GetLength(2); z++)
    26.                 {
    27.                     float volume = 0;
    28.  
    29.                     input.Create(Vector2.zero);
    30.  
    31.                     input.Seed = seed;
    32.                     input.X = x;
    33.                     input.Y = y;
    34.                     input.Z = z;
    35.  
    36.                     NodeEditor.checkInit();
    37.                     NodeEditor.RecalculateAll(biome);
    38.  
    39.                     volume += output.value;
    40.                     Debug.Log(input.X + "/" + input.Y + "/" + input.Z + " = " + output.value);
    41.  
    42.                     //volume += perlin.FractalNoise3D(position.x + x, position.y + y, position.z + z, oct, frq, amp);
    43.                     //volume += (float)voronoi.VoronoiNoise3D(position.x + x, position.y + y, position.z + z, 0.02f, displacement, true) / 4;
    44.  
    45.                     float densityX = curveX.Evaluate((position.x + x) / (numX * width));
    46.                     float densityY = curveY.Evaluate((position.y + y) / (numY * height));
    47.  
    48.                     volume -= densityX + densityY;
    49.  
    50.                     map[x, y, z] = volume;
    51.                 }
    52.             }
    53.         }
    54.     }
     
  16. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I don't have the full code so I can't determine what's wrong. I recommend you to make a debug environment. First, do not use the BuildMap method. It makes it harder to debug. Instead, use the node only, input different parameters in the GUI (position and seed) and check the output. Where does it not work? This will probably help you identify your problem;) Incase it's a Node Editor related problem, I'd be glad to help you out:)
     
  17. TheCaptainJuneBug

    TheCaptainJuneBug

    Joined:
    Sep 29, 2013
    Posts:
    9
    Sorry this is off topic from the original question about the tree functionality. I will respond to that specifically soon.

    But i just wanted to let you know i requested for a pull request for my keyboard support to the editor Pull Request. This is my actual first github contribution so i hope im doing this right :)
     
  18. TheCaptainJuneBug

    TheCaptainJuneBug

    Joined:
    Sep 29, 2013
    Posts:
    9
    I was able to get the exact functionality i was looking for thanks to both of your posts!. @Aramilion Your visual was GREAT HELP! and strategy to linking nodes together.

    @Seneral thank for you for supply this piece of code

    Code (CSharp):
    1. Node[] followUps = node.Outputs.SelectMany((NodeOutput out) => out.connections).Select((NodeInput in) => in.body).ToArray ();
    It was the last piece of the puzzle. And as for the traversal, yeah i just used a basic recursive traversal algorithm to walk through the nodes :). As you might be able to see my OCD kicking in with placement of the nodes. Reason why i added the keyboard support haha.

    Thank you both for your help!

     
  19. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Alright, good you're getting results you wanted! I also like to keep my canvases tidy, that's why there's snapping (Hold Control):D
     
  20. Stormy102

    Stormy102

    Joined:
    Jan 17, 2014
    Posts:
    495
    I've got an issue where the length of the outputs and inputs of my nodes are zero. Any ideas?
     
  21. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    So you mean you lost the information? Beware that if you change a node of yours in regards to structure (knobs, size, etc.) it will not update your already instantiated nodes. This may be the case for you. Else, if it really is 0 due to an error the newest deelop version is build to remove these on load and print a warning.
    If your nodes reference the knobs by hardcoded indices it will make the node throw an error of course, so in that case first make sure if there was a warning and if it is, try to reproduce it and send it to me:)
     
  22. Stormy102

    Stormy102

    Joined:
    Jan 17, 2014
    Posts:
    495
    I used the latest develop - might switch to the latest master. It's a node that has one output and one input, but is returning them as zero arrays in length.
     
  23. Stormy102

    Stormy102

    Joined:
    Jan 17, 2014
    Posts:
    495
    Need this bug fixes @Seneral - getting my node and checking the outputs.Count results in 0, this isn't correct as I have one or sometimes two outputs. Any ideas to whats going on?
     
  24. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I need a way to reproduce it, else I can't help you:/
    It is definitely an edge case as I didn't stumble over it before... Please try to create a new canvas with the node and if that will trigger that error, please send me your node code - else try to find a way to break it and report it:) Thank you!
     
    Stormy102 likes this.
  25. Stormy102

    Stormy102

    Joined:
    Jan 17, 2014
    Posts:
    495
    Here is my code so far for the node. It is fine during edit mode and displays the inputs and outputs. However, at runtime, the length of the inputs and outputs is 0, resulting in me being unable to find sub-nodes. Here is my code:
    Code (CSharp):
    1. using NodeEditorFramework.Utilities;
    2. using UnityEngine;
    3. using NodeEditorFramework;
    4. using System.Collections.Generic;
    5.  
    6. namespace DWS.Nodes
    7. {
    8.     [Node (false, "Buildings/Building Tier Node")]
    9.     public sealed class BuildingTierNode : Node
    10.     {
    11.         public const string ID = "buildingNode";
    12.         public override string GetID { get { return ID; } }
    13.  
    14.         public EBuildingUpgradeType buildingType;
    15.         public int buildingHealth = 100;
    16.         public float buildingWeight = 5f;
    17.         public string buildingTag = "";
    18.  
    19.         private bool buildingShowUpgradeRequirements = false;
    20.         public List<BuildingTierNodeUpgradeRequirements> buildingUpgradeRequirements = new List<BuildingTierNodeUpgradeRequirements>();
    21.         private int buildingUpgradeRequirementsCount = 1;
    22.  
    23.         public bool startOfTree = false;
    24.         public bool showCompleteUpgrade = true;
    25.         public bool showReinforcedUpgrade = true;
    26.         public bool endOfTree = false;
    27.  
    28.         public override Node Create(Vector2 pos)
    29.         {
    30.             BuildingTierNode node = CreateInstance<BuildingTierNode>();
    31.  
    32.             node.rect = new Rect(pos.x, pos.y, 350, 300);
    33.             node.name = "New Building Tier Node";
    34.  
    35.             node.CreateInput("Upgraded From (Complete Upgrade)", "Float");
    36.             node.CreateInput("Upgraded From (Reinforced Upgrade)", "Float");
    37.             node.CreateOutput("Upgrade To", "Float");
    38.  
    39.             return node;
    40.         }
    41.  
    42.         protected override void NodeGUI()
    43.         {
    44.             GUILayout.Space(10f);
    45.  
    46.             name = RTEditorGUI.TextField(new GUIContent("Building Name:", "The name of the building, just for easy locating"), name, GUI.skin.textField);
    47.  
    48.             buildingType = (EBuildingUpgradeType)RTEditorGUI.EnumPopup(new GUIContent("Building Type:", "The selected building type"), buildingType);
    49.  
    50.             buildingHealth = RTEditorGUI.IntField(new GUIContent("Max Health:", "The max health that this building will have"), buildingHealth);
    51.  
    52.             buildingWeight = RTEditorGUI.FloatField(new GUIContent("Weight", "The weight that this building has. Contributes to building stability"), buildingWeight);
    53.  
    54.             buildingTag = RTEditorGUI.TextField(new GUIContent("Tag:", "The tag that this building has"), buildingTag, GUI.skin.textField);
    55.  
    56.             if (!startOfTree)
    57.             {
    58.  
    59.                 buildingShowUpgradeRequirements = RTEditorGUI.Foldout(buildingShowUpgradeRequirements, new GUIContent("Show Upgrade Requirements", "Toggles visibility of Upgrade Requirements"));
    60.  
    61.                 if (buildingShowUpgradeRequirements)
    62.                 {
    63.                     buildingUpgradeRequirementsCount = RTEditorGUI.IntSlider(new GUIContent("Upgrade Requirements Count", "The number of upgrade requirements"), buildingUpgradeRequirementsCount, 1, 5);
    64.  
    65.                     while (buildingUpgradeRequirements.Count > buildingUpgradeRequirementsCount)
    66.                     {
    67.                         buildingUpgradeRequirements.RemoveAt(buildingUpgradeRequirements.Count - 1);
    68.                     }
    69.                     while (buildingUpgradeRequirements.Count < buildingUpgradeRequirementsCount)
    70.                     {
    71.                         buildingUpgradeRequirements.Add(new BuildingTierNodeUpgradeRequirements());
    72.                     }
    73.  
    74.                     GUILayout.BeginHorizontal();
    75.  
    76.                     GUILayout.Label(new GUIContent("Item ID:", "The Item ID of the required upgrade item"));
    77.                     GUILayout.Label(new GUIContent("Item Amount:", "The amount of the required upgrade item"));
    78.  
    79.                     GUILayout.EndHorizontal();
    80.  
    81.                     foreach (BuildingTierNodeUpgradeRequirements requirement in buildingUpgradeRequirements)
    82.                     {
    83.                         GUILayout.BeginHorizontal();
    84.  
    85.                         requirement.ItemID = RTEditorGUI.IntField(requirement.ItemID);
    86.                         requirement.ItemAmount = RTEditorGUI.IntField(requirement.ItemAmount);
    87.  
    88.                         if (requirement.ItemAmount < 1)
    89.                         {
    90.                             requirement.ItemAmount = 1;
    91.                         }
    92.  
    93.                         GUILayout.EndHorizontal();
    94.                     }
    95.  
    96.                 }
    97.             }
    98.  
    99.             GUILayout.Space(15f);
    100.  
    101.             GUILayout.BeginHorizontal();
    102.             if (!startOfTree)
    103.             {
    104.                 GUILayout.BeginVertical();
    105.  
    106.                 if (Inputs[0].name == "Upgraded From (Complete Upgrade)" && showCompleteUpgrade)
    107.                 {
    108.                     Inputs[0].DisplayLayout();
    109.                 }
    110.                 if (Inputs[1].name == "Upgraded From (Reinforced Upgrade)" && showReinforcedUpgrade)
    111.                 {
    112.                     Inputs[1].DisplayLayout();
    113.                 }
    114.  
    115.                 GUILayout.EndVertical();
    116.             }
    117.             GUILayout.BeginVertical();
    118.  
    119.             if (!endOfTree)
    120.             {
    121.                 foreach (NodeOutput output in Outputs)
    122.                 {
    123.                     output.DisplayLayout();
    124.                 }
    125.                 GUILayout.EndVertical();
    126.             }
    127.             GUILayout.EndHorizontal();
    128.  
    129.         }
    130.     }
    131. }
    IDK why its not working, but this is the code I am using to load the data. The NodeCanvas is assigned in the inspector, and at runtime it appears to loose the inputs and outputs:
    Code (CSharp):
    1. using UnityEngine;
    2. using NodeEditorFramework;
    3. using System.Linq;
    4.  
    5. namespace DWS.Utilities
    6. {
    7.     public sealed class LoadDataFromBuildingTierTree : MonoBehaviour
    8.     {
    9.  
    10.         public string buildingName = "Reinforced Wooden Wall";
    11.         public NodeCanvas canvas;
    12.  
    13.         private Node currentNode = null;
    14.  
    15.         public string reinforcedUpgradeNode;
    16.         public string completeUpgradeNodes;
    17.  
    18.         void Start()
    19.         {
    20.             currentNode = canvas.nodes.Single((Node node) => node is BuildingTierNode && (node as BuildingTierNode).name.ToLower() == buildingName.ToLower());
    21.             if (currentNode == null)
    22.             {
    23.                 Debug.LogError("[Node Configuration] Couldn't find " + buildingName + " node!");
    24.             }
    25.             Debug.Log((currentNode as BuildingTierNode).name);
    26.          
    27.             if (currentNode.name.ToLower().Contains("reinforced"))
    28.             {
    29.                 // We are going to a main node
    30.                 Debug.Log(currentNode.Outputs.Count); // Returns 0???
    31.  
    32.             }
    33.             else
    34.             {
    35.                 // We need to reference the next major upgrade AND the reinforced upgrade
    36.                Debug.Log(currentNode.Outputs.Count); // Returns 0???
    37.             }
    38.         }
    39.     }
    40. }
     
  26. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Oh, that's the reason. Didn't know you meant runtime;)
    So, it's actually quite simple without looking into the code. At runtime the save format is stored in a compressed state where duplicate references are removed (in this case Inputs and Outputs as they are stored in nodeKnobs, too).
    Normally this state is restored on load but when you reference the canvas asset directly (ok for runtime) then you need to manually call NodeEditorSaveManager.Uncompress (ref canvas). Sorry for confusion regarding this, there has been quite alot of it about this so it might be better to remove this behaviour even if that means we have duplicate references (not worse from a functionality point of view)...
     
    ChicK00o likes this.
  27. TheCaptainJuneBug

    TheCaptainJuneBug

    Joined:
    Sep 29, 2013
    Posts:
    9
    Actually now im having the same problem as @Stormy102. It was just working, closed unity and now it doesnt work. Output contains 0 everytime. When according to the visual screen i posted output shouldnt have 0 in the output.

    Edited: Due to time constraint, i just added a List<Node> variable to the node object and kept track of connected nodes that way. Given its a bit sloppy.

    Code (CSharp):
    1. if (Outputs[0].connections != null && Outputs[0].connections.Count > 0)
    2.             {
    3.                 foreach (NodeInput item in Outputs[0].connections)
    4.                 {
    5.                     if(!children.Contains((TreeNode)item.body))
    6.                     {
    7.                         children.Add((TreeNode)item.body);
    8.                     }
    9.                 }
    10.                 return true;
    11.             }
     
    Last edited: Jun 22, 2016
  28. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    So to sum up, I'm pretty sure there are only three cases where this can happen despite created Outputs/Inputs.

    1. Save/Load Error. In that case, a warning will be printed out, but the error will be automatically fixed by removing the null entry.
    2. You changed Output/Input creation in your Node type, but any already instantiated nodes will still have the old (Input/Output) setup.
    3.You're directly referencing the canvas. NOT ONLY FROM RUNTIME, but in every case where you reference the canvas asset directly (e.g. from a MonoBehaviour, EditorWindow, any other ScriptableObject, ...). In that case, you need to call NodeEditorSaveManager.Uncompress(ref canvas) once before accessing the connections!

    Due to the confusing nature of 3, I'll likely will be removing the behaviour which was originally thought to make the save file cleaner:confused:
    I hope this finally clears it up, it should be definitely one of these three cases:)
     
    Stormy102 likes this.
  29. Stormy102

    Stormy102

    Joined:
    Jan 17, 2014
    Posts:
    495
    Fixed!! Thanks for your help. Must be more descriptive next time :confused:

    Stormy
     
    Seneral likes this.
  30. Aramilion

    Aramilion

    Joined:
    Apr 15, 2016
    Posts:
    23
    @Seneral, which version of Unity are you using for this NodeEditor? I was using 5.1.2, than switched to 5.3.4 (you adviced me to do so). but in this version Unity have problems with buttons. (sometimes when you want to set button.interactable = value), it throws NullReferenceException, changing the value anyways and continuing code execution... o_O so i switched to 5.3.5 now becouse they fixed this problem in this version, but hell no i cannot use save to scene feature anymore in this version.

    Null reference exceptions i get:
    EDIT: I don't know what is happening. my C# project is acting weird. It doesn't see the namespaces. Like in UnityEngine there is only "Advertisements" :/ The errors i was getting were inside Framework, but after redownloading it and creating new project eerything seems to work now. (again in 5.3.4... Seems the most stable). Will workaround the interactable buttons with canvas groups.
     
    Last edited: Jun 23, 2016
  31. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    @Aramilion Thanks for the report, indeed I did not even download 5.3.5 yet. I will today and take a look to fix this error:)
     
  32. Aramilion

    Aramilion

    Joined:
    Apr 15, 2016
    Posts:
    23
    Hey! I was still working in my 5.3.4, changed some things. (only mine additions, not in framework) + i stopped using loading and saving canvasses to external assets (i'm just not displaying buttons :)).
    Today i decided to try my 5.3.5 again... works like a charm. i'm sure nothing i've done could fix the problems i was having. Seems to be ok now.
    My project i'm working on is *soon* to be in place i can share it with you :)
     
  33. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Cool! Sorry for the long absence (atleast regarding developmenet, still gave support of course). I was concentrating on other projects, major one being TC2 - it's just a hell lot of fun to play around with the beta, so amazing:D
     
  34. johnmcmahonart

    johnmcmahonart

    Joined:
    Jul 15, 2016
    Posts:
    19
    First off let me say great job this is what I think I need for my current project. I am new to unity so I apologize for the rather simple question to follow.

    From looking at the code and docs a little I can see how I can use this to create custom nodes for the library I am using (libnoise) but I don't understand how I get data out of the graph. From what I understand the node editor handles instantiating the custom node classes that I create but I can't figure out how I would get access to the data contained in one of those objects from another area of my unity project. I would like to use this framework to build an editor for building the graph of generators and operators I need so that I could build meshes procedurally for an open world game and I don't want a static output like a file or array of data. I need to evaluate the graph for each calculation (in laymens terms, libnoise has cache nodes so you don't need to fully re-evaluate everything every time you make a call). I'm also not sure exactly how saving the canvas would work if the underlying classes I use aren't serializable (I feel I may need to fix this). Is there another way I should be looking at this problem?
     
  35. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Hi John!
    Cool idea and goal you have there, I'm having a fascination for these kind of things so I'll gladly help you to achieve this;) (I've not that much time to do this unfortunately)

    Please use the develop branch of the repo, it is a bit more stable in most parts but foremost contains way more features and improvements that help you in the long run.
    The docs are rather old, if I'd have time, accessing the data would be the first thing to write up properly... Similar things have been asked before so you might check these posts out:
    Here I explain how to properly reference the canvas externally
    Here I explain how you access the nodes easily and how to set up communication between nodes and you external scripts.

    If you have data (like the underlying classes from libnoise) and want to store it with the nodes it indeed has to be serializable. It is really unfortunate but it is in general very frustrating to use 3rd party libraries not designed for Unity because of serialization and other problems. So first, make sure libnoise works in Unity, maybe create a placeholder class that holds the libnoise data and check if it is serializable:)

    If you are having problems don't hesitate to ask, I can help you with your specific case.

    EDIT: I've found there is a port of libnoise to unity, check it out here. Maybe it's the way to go for you. On a side node I can also recommend the free asset Turbulence library if that suites your needs too, it has some great fast GPU noises!
     
  36. johnmcmahonart

    johnmcmahonart

    Joined:
    Jul 15, 2016
    Posts:
    19
    Thanks for the response. Yea I am already using the unity port of libnoise and actually already started serializing some of it so I can save some simple object settings from an editor script I've written so I know parts of it are serializable but I've only done a small bit not the entire library. I'll take a look at the links you posted when I get a chance. I figured someone would have wanted to do this already. Though from looking at Terrain composer I really think I may just buy that to do the bulk of my terrain creation duty but still build some kind of editor around libnoise for 2d stuff since it seems like a pretty nice library even though it isn't designed to run on a gpu.

    https://www.facebook.com/johnmcmahonart

    Has my current progress which is pretty early right now but I've only been doing this in my spare time for about a week so I'm happy with my current progress
     
  37. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Oh, so for terrain purposes I'd totall recommend TC2;) It's just the best you can get and definitely far easier than to develop something similar from scratch. But the choice is yours of course:)

    I don't know libnoise that much but if it doesn't run on the GPU I would rate it rather useless. This is a bit harsh, but I'd prefer it fast;) Again, I'd recommed Turbulence Library, might be better suited for you as it's a vast collection of GPU noises.
    On a different note, if all that you plan to do is procedural terrain creation or texture creation TC2 and Substance Designer are already good solutions. Well, the last is a bit expensive but I did buy it and did not regret it;)

    In the past I was also fascinated from this kind of parametric/procedural generation type of thing, tbh it was my initial motivation to develop this Node Editor Framework, but in the end I decided to built upon the base of other tools (TC2 and Substance Designer) and now I'm concentrating on the more advanced stuff (erosion simulation f.E.) :)
     
  38. ChicK00o

    ChicK00o

    Joined:
    Feb 26, 2014
    Posts:
    13
    Faced the same problem of Inputs and Outputs being Zero at runtime, just read your comment. I had come to the tread to ask you why, had you kept Inputs and Output "Non Serialized", and the reasons to have a different list of just Nodes. I solved it by just commenting out nodes, removing errors and making the Inputs and Outputs Serialised for now.
    I second the idea to remove the requirements for Uncompressing, and just keeping the Inputs and Outputs serialized
     
  39. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yep you're right, but I don't yet know when I'll be working on the project again (I will eventually though) :)
     
  40. johnmcmahonart

    johnmcmahonart

    Joined:
    Jul 15, 2016
    Posts:
    19
    Well what I am leaning to right now is making libnoise work together with the node framework is a good project just to show some coding skills and learn with. I was already planning to buy substance designer for texturing. I already have a product that does graphs but it isn't gpu accelerated either. I looked at the turbulance library but really need to spend more time trying to understand the shader code, which IMO is pretty unfriendly and weird semantically/syntactically
     
  41. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ok cool that is totally fine:)
    You actually don't need to understand the shader code in specific, it's actually very easy to use the shaders generated by the turbulence library in code to generate textures. Then you need simple blend/mask/whatever shader to mix various noises and that will do.
    The shaders of the turbulence library aren't necessarily limited to putting them on an object, you can use them to render noises to render textures, which then can be put into other materials to process further, everything on the GPU and everything simply controlled by scripting.
    If that's what you're going for I can provide you with some sample code. Just a few dozen lines, really:)
     
  42. johnmcmahonart

    johnmcmahonart

    Joined:
    Jul 15, 2016
    Posts:
    19
    I guess I'm not understanding how something works. How do you get the data off the GPU if you are using it to run the shader?

    edit: Is this what compute shaders are? I guess I looked at old stuff for unity because it sounded like a pain. Current docs seem pretty straight foward.. If you have some sample code I'm curious.
     
  43. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Sorry for the late reply.
    So, you might want to take into Graphics.Blit function. Basically, you're telling the material (in this case a material with the generated noise shader) to blit into a RenderTexture you set up. That means the shader is executed for a quad and the result is saved into the RenderTexture. What I do for my erosion project is to manually render the quad, which gives a bit more flexibility than the Graphics.Blit function. Here's some code:
    Code (csharp):
    1. // Prepare render and set target
    2. Graphics.SetRenderTarget (renderTexture);
    3. material.SetPass (0);
    4.  
    5. // Render onto the target RenderTexture
    6. GL.PushMatrix ();
    7. GL.LoadOrtho ();
    8. GL.Begin (GL.QUADS);
    9. GL.TexCoord2 (0.0f, 0.0f); GL.Vertex3 (0.0f, 0.0f, 0.1f);
    10. GL.TexCoord2 (1.0f, 0.0f); GL.Vertex3 (1.0f, 0.0f, 0.1f);
    11. GL.TexCoord2 (1.0f, 1.0f); GL.Vertex3 (1.0f, 1.0f, 0.1f);
    12. GL.TexCoord2 (0.0f, 1.0f); GL.Vertex3 (0.0f, 1.0f, 0.1f);
    13. GL.End ();
    14.  
    15. // renderTexture now contains the output of the 1. pass of material
    The RenderTexture still only represents the texture on the GPU - in order to convert it to normal textures, you have to download it like this:
    Code (csharp):
    1. RenderTexture.active = renderTexture;
    2. targetTexture2D.ReadPixels (new Rect (0, 0, renderTexture.width, renderTexture.height), 0, 0);
    3. targetTexture2D.Apply ();
    But if you will process the noise in multiple steps by different shaders (Blend or Masking noises for example) then you should input the intermediate renderTexture directly into the materials and only download the final result.

    It is a complex topic to get into but I hope this gave you an idea or atleast some keywords to search for:)

    Btw, Compute Shaders are a different topic again - this uses normal shaders, like the ones Turbulence library outputs. It has a wider range of supported devices (obviously, they are normal shaders;) ) but do not include some extra advanced features for more abstract calculations that ComputeShaders have. But for noise or generally image processing normal shaders are the best:)
     
  44. johnmcmahonart

    johnmcmahonart

    Joined:
    Jul 15, 2016
    Posts:
    19
    Thanks! That gives me some ideas on where to go. Yea this is a really complex topic :D
     
  45. johnmcmahonart

    johnmcmahonart

    Joined:
    Jul 15, 2016
    Posts:
    19
    I'm mainly interested in using noise to build heightmaps (maybe I wasn't clear in a prior post). Is there a good way to get texture data into a unity terrain object (I can think of ways but they seem clunky)? Also wasn't sure textures are best since for just raw noise data the data doesn't really need to go through the rest of the graphics pipeline (rasterization etc). However the stuff I read on compute shaders said there is a context switching penalty when using them because it takes the GPU out of the normal graphics processing mode. So dunno which way is best.
     
  46. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ok so if you plan to built a fully fleshed out terrain, I recommend TC2 again, because there's a lot more to a terrain than just heightmaps.
    But to generate heightmaps, normal shaders ARE the best. They are still images, basically. So it's going to be faster than the generalized compute shaders. I even went for normal shaders for my erosion simulation, simply because it basically operates on images and Compute Shaders won't give me any real benefit.
    But to generate good heightmaps you'd need to learn program shaders yourself, and if it is just for simple Blend/Masking shaders...
     
  47. johnmcmahonart

    johnmcmahonart

    Joined:
    Jul 15, 2016
    Posts:
    19
    I looked at TC2 again and can't tell if it will do what I want. It seems more stampy then full procedural, and I am interested in infinite world / very large world so I think I need to roll my own system. Not really opposed to building my own shaders at this point. The code I've looked at for simplex/perlin noise isn't bad (and other noise types just build ontop of that so it's not that bad). Most of the blend stuff I've seen is straight forward enough.
     
  48. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Well I use TC2 for fully procedural stuff too. Don't want to sound like I want to sell it to you, but it is most likely the best choice for you. Fact is, it was tailored to fully procedural before, but then with TC2 it 'adapted' a bit to the trend Gaia had set with stamps and included them as a feature. Don't be confused when Nathaniel primarily uses stamps in his example video, it is totally possible to create everything fully procedural (I do). There are some cool noise but not nearly as much as f.E: Turbulence library, so I plan to take a look if I can integrate them:)

    Anyway, you're right shaders seem simple enough and the mechanics of the utility shaders are, too. But still, there's a hell lot of work involved, even without a proper GUI. Just saying;) I won't recommend starting from scratch, this is what the Asset Store is for;) Nonetheless it is worth looking into shaers if you plan on doing GPGPU stuff like simulations, it is a fun topic once you get into it:)
     
  49. johnmcmahonart

    johnmcmahonart

    Joined:
    Jul 15, 2016
    Posts:
    19
    Thanks for the info on TC2 I guess I was confused since the examples Nathan shows don't show that particular feature and it is some what vague in the website if you can do fully procedural. I did read someone saying you couldn't ship anything with the TC2 asset included because of licensing but I can't find mention of that in the website. I would think if it supports full procedural, shipping a binary product that only uses the runtime would be fine (my application).
     
  50. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Well, who said that? Do you have a link? I'm very sure this is not the case and if it was, than by accident. I am in contact with the developer and he would never put so silly restrictions on his product when he designed it for this use;)
    Anyway, enough advertising TC2 lol:)