Search Unity

Simple node editor

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

  1. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Oh, that is the error you were previously referring to. I forgot to ask when you got it because I wasn't getting it when using RTNodeEditor as you said. Still can't reproduce it though, trying hard both in the EditorWindow and in RTNodeEditor... can you make sure in a clean copy of the repo and explain in more detail?

    Regarding your second problem, there is no 'official' method/callback for 'canvas-loaded' yet but I plan on implementing such callbacks. I'll adress that soon, too:)
    For now you can try typical scriptable object callbacks (serialization in specific, maybe OnEnable). Or even simpler you can load additional stuff in NodeGUI or at Calculate when you notice you haven't initialized yet;)
     
  2. DrEvil

    DrEvil

    Joined:
    Aug 11, 2012
    Posts:
    22
    DrawTexture references Hidden/GUITextureClip_ChannelControl, where do I get that?
     
  3. Aramilion

    Aramilion

    Joined:
    Apr 15, 2016
    Posts:
    23
    @M_Dizzle When you are storing node references, or reference something in this nodes, theese references are lost every single time you load the Node Canvas because thoose loaded nodes are new instances, with references to old instances (I hope you get it). you can check it by seting inspector to Debug Mode, and checking instance ID of referenced Node, and checking if it matches the node itself you think it should reference.

    That happens when you reload the scripts, leave Play mode etc. At least in my version of NodeEditor which is a bit outdated now. But i think it is still a problem. Basicly every single time you reload canvas, you need to reassign them.

    I store list of connected Nodes in my Node, so when i reload canvas, i have to go in this node, clear this list, and add nodes through Output[0].connections

    Hope it helped somehow. :)
     
  4. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I built that functionality of DrawTexture initially to debug textures for my erosion simulation, didn't intend to put it in there because I doubt anyone would make use of it. But if you need to shuffle channels while drawing your textures, here you go:
    Code (csharp):
    1.  
    2. Shader "Hidden/GUITextureClip_ChannelControl"
    3. {
    4.     Properties
    5.     {
    6.         _MainTex ("Texture", Any) = "white" {}
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.  
    12.         Tags { "ForceSupported" = "True" }
    13.  
    14.         Lighting Off
    15.         Blend SrcAlpha OneMinusSrcAlpha
    16.         Cull Off
    17.         ZWrite Off
    18.         ZTest Always
    19.  
    20.         Pass
    21.         {
    22.             CGPROGRAM
    23.             #pragma vertex vert
    24.             #pragma fragment frag
    25.  
    26.             #include "UnityCG.cginc"
    27.  
    28.             struct appdata_t {
    29.                 float4 vertex : POSITION;
    30.                 half4 color : COLOR;
    31.                 float2 texcoord : TEXCOORD0;
    32.             };
    33.  
    34.             struct v2f {
    35.                 float4 vertex : SV_POSITION;
    36.                 half4 color : COLOR;
    37.                 float2 texcoord : TEXCOORD0;
    38.                 float2 clipUV : TEXCOORD1;
    39.             };
    40.  
    41.             sampler2D _MainTex;
    42.             sampler2D _GUIClipTexture;
    43.  
    44.             uniform float4 _MainTex_ST;
    45.             uniform fixed4 _Color;
    46.             uniform float4x4 unity_GUIClipTextureMatrix;
    47.  
    48.             // Ints pointing to the channel to represent: 0-black - 1-red - 2-green - 3-blue - 4-alpha - 5-white
    49.             uniform int shuffleRed = 1, shuffleGreen = 2, shuffleBlue = 3, shuffleAlpha = 4;
    50.  
    51.             v2f vert (appdata_t v)
    52.             {
    53.                 v2f o;
    54.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    55.  
    56.                 float4 eyePos = mul(UNITY_MATRIX_MV, v.vertex);
    57.                 o.clipUV = mul(unity_GUIClipTextureMatrix, eyePos);
    58.  
    59.                 o.color = v.color;
    60.                 o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
    61.                 return o;
    62.             }
    63.  
    64.             half shuffleChannel (half4 srcCol, int shuffle)
    65.             {
    66.                 if (shuffle <= 0)
    67.                     return 0;
    68.                 if (shuffle == 1)
    69.                     return srcCol.r;
    70.                 if (shuffle == 2)
    71.                     return srcCol.g;
    72.                 if (shuffle == 3)
    73.                     return srcCol.b;
    74.                 if (shuffle == 4)
    75.                     return srcCol.a;
    76.                 return 1;
    77.             }
    78.  
    79.             half4 frag (v2f i) : SV_Target
    80.             {
    81.                 half4 rawCol = tex2D(_MainTex, i.texcoord);
    82.                 half4 shuffledCol = half4 ( shuffleChannel (rawCol, shuffleRed),
    83.                                             shuffleChannel (rawCol, shuffleGreen),
    84.                                             shuffleChannel (rawCol, shuffleBlue),
    85.                                             shuffleChannel (rawCol, shuffleAlpha));
    86.                
    87.                 half4 col = shuffledCol * i.color;
    88.                 col.a *= 2.0f * tex2D(_GUIClipTexture, i.clipUV).a;
    89.                 return col;
    90.             }
    91.             ENDCG
    92.         }
    93.     }
    94. }
    95.  
    Not very efficient but works if you want to inspect certain channels of a texture;)
    But for the repo code, I think I should remove the function as I don't want this shader in there for an utility functionality that is probably barely needed...
     
  5. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thank you @Aramilion, now I figure the problem @M_Dizzle has...
    Basically everything you described is true but there is an easy other solution. Then it's similar to the case I mentioned previously:
    as Nodes are basically scriptable objects. But, notice the summary of GetScriptableObjects: You don't need that function, as other Nodes or NodeInputs/NodeOutputs are already saved by the system.
    But in order for your references to work, you need to update them. That's what the second function, CopyScriptableObjects, is for. So you can basically do in your SyntheNode:
    Code (csharp):
    1. //sequencer in, Audio out
    2. public NodeInput sequencerInput;
    3. public NodeOutput audioOut;
    4.  
    5. protected internal override void CopyScriptableObjects (System.Func<ScriptableObject, ScriptableObject> replaceSerializableObject)
    6. { // Replace references with new copies from the saving system
    7.     sequencerInput = replaceSerializableObject.Invoke (sequencerInput);
    8.     audioOut = replaceSerializableObject.Invoke (audioOut);
    9. }
    Of course for such simple inner-node references that do not require special setup it might be easier to use some upcoming callbacks for loaded canvas, etc. or simply to not serialize the references and re-setup when null;)
    These functions are more intended for node-specific ScriptableObject Data or complex cross-node references.
     
  6. M_Dizzle

    M_Dizzle

    Joined:
    Dec 10, 2013
    Posts:
    20
    Wow, thanks for the feedback @Seneral & @Aramilion I'll give it a try when I get home tonight. I'll test the framework from a new project too, to see if it's something I've done that's giving me errors.

    I was thinking I'd eventually have to write my own file load/save system for runtime anyway, as you can't create ScriptableObjects outside of Unity (I think?) So I was thinking that I'd write my own serialization system for each of these SynthControls, possibly outputting to JSON or something, and then have a loader that creates the nodes from scratch and sets the values of the various controls within them.
     
  7. vicenterusso

    vicenterusso

    Joined:
    Jan 8, 2013
    Posts:
    130
    Random question: I will use this node asset mainly at runtime. can I save it and retrieve the node information? I mean, lets say that each node have a "bool currentNode", like current level.. can I save and load without any problem? I had some problems with scritpable object in the past..
     
  8. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    I had a few bad assumptions that I am fixing now. I really didn't have enough time to properly think it through. I am refactoring it now, before I give you the final I will document it. Its not as bad as it seems (Other than the recursion).

    The basic idea is to generate the whole tree.
     
  9. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Ok, here it is. I need to properly plumb it to the node editor (Right now it is relaying out on ever frame, which kinda is dumb).

    I got rid of Canvas size, that was just a dumb hack.

    A single node

    upload_2016-9-15_12-10-43.png

    Having laid out a bunch of nodes with children
    upload_2016-9-15_12-12-49.png
     
  10. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yeah, normal variables are safed like normal. There are some problems that exist with every ScriptableObject based structure that you are probably familar with, for example you cannot save new content at runtime. But you can create the information in the editor and properly access it at runtime.
    If you want to save at runtime, a custom save system is needed, which was actually concepted since a long time but haven't found a real serious need for it yet;)
    But seems like @M_Dizzle plans on doing one:)

    That would be cool, I'll be excited to see your results:) I thought of XML-substance-style save files before, makes them easily readable;)
     
  11. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    That looks really promising! When can we test it out? :D
    Jk, take your time. Thank you for that awesome contribution:)
     
  12. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Ok, it works :).

    Code :)

    Code (CSharp):
    1.     public static class LayoutTools
    2.     {
    3.         private static IDictionary<Node, Rect> canvasUsed = new Dictionary<Node, Rect>();
    4.         private const float LAYOUT_SEP_Y = 30;
    5.         private const float LAYOUT_SEP_X = 30;
    6.  
    7.         public static void GenerateLayout(NodeCanvas canvas)
    8.         {
    9.             canvasUsed.Clear();
    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.  
    33.             float parentWidth = 0, parentHeight = 0;
    34.             foreach(Node node in parentNodes)
    35.             {
    36.                 parentWidth += node.rect.width + LAYOUT_SEP_X*2;
    37.                 parentHeight += node.rect.height + LAYOUT_SEP_Y * 2;
    38.             }
    39.  
    40.             int nodeCounter = 0;
    41.             foreach(Node node in parentNodes)
    42.             {
    43.                 float nodeX;
    44.                 if (parentNodes.Count == 1)
    45.                 {
    46.                     nodeX = 0;
    47.                 }
    48.                 else
    49.                 {
    50.                     if (nodeCounter % 2 == 0)
    51.                     {
    52.                         nodeX = nodeCounter * (-1 * (parentWidth / 2));
    53.                     } else
    54.                     {
    55.                         nodeX = -1 * nodeCounter * (-1 * (parentWidth / 2));
    56.                     }
    57.                 }
    58.                 float nodeY = 0;
    59.                 node.rect.center = new Vector2(nodeX, nodeY);
    60.                 bool overlap = true;
    61.                 int counter = 1;
    62.                 while (overlap)
    63.                 {
    64.                     overlap = false;
    65.                     foreach (KeyValuePair<Node, Rect> kvp in canvasUsed)
    66.                     {
    67.                         if (node.rect.Overlaps(kvp.Value,true))
    68.                             overlap = true;
    69.                     }
    70.                     if (overlap)
    71.                     {
    72.                         if(counter%2==0)
    73.                         {
    74.                             node.rect.x = (nodeX + (counter * (node.rect.width / 16)));
    75.                         } else
    76.                         {
    77.                             node.rect.x = (nodeX - (counter * (node.rect.width / 16)));
    78.                         }
    79.                     }
    80.                     counter++;
    81.                 }
    82.                     canvasUsed.Add(node, node.rect);
    83.                 if (node.Inputs != null)
    84.                 {                  
    85.                     foreach (NodeInput ni in node.Inputs)
    86.                     {                      
    87.                         Node childNode = ni.GetNodeAcrossConnection();
    88.                         if(childNode!=null)
    89.                             placeNode(childNode, ni.side, node);
    90.                     }
    91.                 }
    92.             }
    93.         }
    94.  
    95.         /// <summary>
    96.         /// Place Node will build the tree as compact as it can
    97.         /// </summary>
    98.         /// <param name="node">The node we are working on</param>
    99.         /// <param name="side">The side it is from the parent</param>
    100.         /// <param name="parentNode">The parent</param>
    101.         private static void placeNode(Node node, NodeSide side, Node parentNode)
    102.         {
    103.             Vector2 center = new Vector2();
    104.             int siblings = siblingCount(node, true);
    105.             float nodeX = 0, nodeY = 0;
    106.             bool placed = false;
    107.             float nodeOffsetX = 0, nodeOffsetY = 0;
    108.             while (!placed)
    109.             {
    110.                 switch (side)
    111.                 {
    112.                     case (NodeSide.Bottom):
    113.                         nodeY = parentNode.rect.yMax + LAYOUT_SEP_Y + (node.rect.height);
    114.                         nodeX = parentNode.rect.center.x - ((siblings / 2) * (LAYOUT_SEP_X + node.rect.width));
    115.                         foreach(NodeOutput no in node.Outputs)
    116.                         {
    117.                             if(no.side==NodeSide.Top)
    118.                             {
    119.                                 nodeY += node.rect.height;
    120.                             }
    121.                         }
    122.                         foreach (NodeInput no in node.Inputs)
    123.                         {
    124.                             if (no.side == NodeSide.Top)
    125.                             {
    126.                                 nodeY += node.rect.height;
    127.                             }
    128.                         }
    129.  
    130.                         break;
    131.                     case (NodeSide.Top):
    132.                         nodeY= parentNode.rect.yMin - LAYOUT_SEP_Y - (node.rect.height / 2);
    133.                         nodeX = parentNode.rect.center.x - ((siblings / 2) * (LAYOUT_SEP_X + node.rect.width));
    134.                         foreach(NodeOutput no in node.Outputs)
    135.                         {
    136.                             if (no.side == NodeSide.Bottom)
    137.                             {
    138.                                 nodeY -= node.rect.height;
    139.                             }
    140.                         }
    141.                         foreach (NodeInput no in node.Inputs)
    142.                         {
    143.                             if (no.side == NodeSide.Bottom)
    144.                             {
    145.                                 nodeY -= node.rect.height;
    146.                             }
    147.                         }
    148.                         break;
    149.                     case (NodeSide.Left):
    150.                         nodeY = parentNode.rect.center.y - ((siblings / 2) * (LAYOUT_SEP_Y + node.rect.height));
    151.                         nodeX = parentNode.rect.xMin - LAYOUT_SEP_X - (node.rect.width / 2);
    152.                         foreach (NodeOutput no in node.Outputs)
    153.                         {
    154.                             if (no.side == NodeSide.Left)
    155.                             {
    156.                                 nodeX += node.rect.width;
    157.                             }
    158.                         }
    159.                         break;
    160.                     case (NodeSide.Right):
    161.                         nodeY = parentNode.rect.center.y - ((siblings / 2) * (LAYOUT_SEP_Y + node.rect.height));
    162.                         nodeX = parentNode.rect.xMax + LAYOUT_SEP_X + (node.rect.width / 2);
    163.                         foreach (NodeOutput no in node.Outputs)
    164.                         {
    165.                             if (no.side == NodeSide.Right)
    166.                             {
    167.                                 nodeX -= node.rect.width;
    168.                             }
    169.                         }
    170.  
    171.                         break;
    172.                 }
    173.                 Rect nodeRect = node.rect;
    174.                 nodeRect.center = new Vector2(nodeX + nodeOffsetX, nodeY + nodeOffsetY);
    175.                 bool overlap = false;
    176.                 foreach(KeyValuePair<Node,Rect> kvp in canvasUsed)
    177.                 {
    178.                     if (nodeRect.Overlaps(kvp.Value,true))
    179.                         overlap = true;
    180.                 }
    181.                 if(overlap)
    182.                 {
    183.                     switch (side)
    184.                     {
    185.                         case (NodeSide.Bottom):
    186.                             nodeOffsetX += (node.rect.width / 16);
    187.                             break;
    188.                         case (NodeSide.Top):
    189.                             nodeOffsetX += (node.rect.width / 16);
    190.                             break;
    191.                         case (NodeSide.Left):
    192.                             nodeOffsetY += (node.rect.height / 16);
    193.                             break;
    194.                         case (NodeSide.Right):
    195.                             nodeOffsetY += (node.rect.height / 16);
    196.                             break;
    197.  
    198.                     }
    199.                 } else
    200.                 {
    201.                     node.rect = nodeRect;
    202.                     canvasUsed.Add(node, nodeRect);
    203.                     placed = true;
    204.                 }
    205.             }
    206.             foreach (KeyValuePair<Node, NodeSide> kvp in getChildrenDirection(node))
    207.                 placeNode(kvp.Key, kvp.Value,node);
    208.         }
    209.  
    210.         private static List<Node> getChildren(Node n)
    211.         {
    212.             List<Node> returnList = new List<Node>();
    213.             if(!isLeaf(n))
    214.             {
    215.                 foreach(NodeInput ni in n.Inputs)
    216.                 {
    217.                     Node nd = ni.GetNodeAcrossConnection();
    218.                     if (nd != null)
    219.                         returnList.Add(nd);
    220.                 }
    221.             }
    222.             return (returnList);
    223.         }
    224.  
    225.         private static IDictionary<Node,NodeSide> getChildrenDirection(Node n)
    226.         {
    227.             IDictionary<Node, NodeSide> returnList = new Dictionary<Node, NodeSide>();
    228.  
    229.             foreach (NodeInput ni in n.Inputs)
    230.             {
    231.                 Node nd = ni.GetNodeAcrossConnection();
    232.                 if (nd != null)
    233.                     returnList.Add(nd,ni.side);
    234.             }
    235.             return (returnList);
    236.         }
    237.  
    238.         private static bool isLeaf(Node n)
    239.         {
    240.             if(n.Outputs==null)
    241.             {
    242.                 return false;
    243.             }
    244.             foreach(NodeOutput no in n.Outputs)
    245.             {
    246.                 if(no.GetNodeAcrossConnection()!=null)
    247.                 {
    248.                     return true;
    249.                 }
    250.             }
    251.  
    252.             return false;
    253.         }
    254.  
    255.         private static int siblingCount(Node node, bool useNodeType)
    256.         {
    257.             // This shouldnt be used on parent nodes
    258.             if(node.Outputs==null)
    259.             {
    260.                 return -1;
    261.             }
    262.  
    263.             int count = 0;
    264.             foreach(NodeOutput no in node.Outputs)
    265.             {
    266.                 Node parentNode = no.GetNodeAcrossConnection();
    267.                 if(parentNode!=null)
    268.                 {
    269.                     foreach(NodeInput ni in parentNode.Inputs)
    270.                     {
    271.                         Node n = ni.GetNodeAcrossConnection();
    272.                         if (n != null)
    273.                         {
    274.                             if (useNodeType)
    275.                             {
    276.                                 if (n.GetID == node.GetID)
    277.                                 {
    278.                                     count++;
    279.                                 }
    280.                             }
    281.                             else
    282.                             {
    283.                                 count++;
    284.                             }
    285.                         }
    286.                     }
    287.                 }
    288.             }
    289.             return count;
    290.         }
    291.     }
    292.  
     
  13. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Examples, autogenerated:

    Single node, clearly a parent:
    upload_2016-9-15_17-5-25.png


    One child with children:
    upload_2016-9-15_17-6-15.png

    A mess of children:
    upload_2016-9-15_17-7-1.png

    Notes:
    * It doesn't respect minimum distances correctly (Bug). Ill fix this
    * The drawing as you can see is based on the Output and Input locations.
    * Its all recursive, so in theory should work N deep (N being as big of number as you like, until you overflow the stack).
     
  14. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Sorry for spam, the bug was bothering me. I fixed it :)
    Code (CSharp):
    1.     public static class LayoutTools
    2.     {
    3.         private static IDictionary<Node, Rect> canvasUsed = new Dictionary<Node, Rect>();
    4.         private const float LAYOUT_SEP_Y = 30;
    5.         private const float LAYOUT_SEP_X = 30;
    6.  
    7.         public static void GenerateLayout(NodeCanvas canvas)
    8.         {
    9.             canvasUsed.Clear();
    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.  
    33.             float parentWidth = 0, parentHeight = 0;
    34.             foreach(Node node in parentNodes)
    35.             {
    36.                 parentWidth += node.rect.width + LAYOUT_SEP_X*2;
    37.                 parentHeight += node.rect.height + LAYOUT_SEP_Y * 2;
    38.             }
    39.  
    40.             int nodeCounter = 0;
    41.             foreach(Node node in parentNodes)
    42.             {
    43.                 float nodeX;
    44.                 if (parentNodes.Count == 1)
    45.                 {
    46.                     nodeX = 0;
    47.                 }
    48.                 else
    49.                 {
    50.                     if (nodeCounter % 2 == 0)
    51.                     {
    52.                         nodeX = nodeCounter * (-1 * (parentWidth / 2));
    53.                     } else
    54.                     {
    55.                         nodeX = -1 * nodeCounter * (-1 * (parentWidth / 2));
    56.                     }
    57.                 }
    58.                 float nodeY = 0;
    59.                 node.rect.center = new Vector2(nodeX, nodeY);
    60.                 bool overlap = true;
    61.                 int counter = 1;
    62.                 Rect paddedRect = new Rect(node.rect);
    63.                 paddedRect.width += LAYOUT_SEP_X;
    64.                 paddedRect.height += LAYOUT_SEP_Y;
    65.                 while (overlap)
    66.                 {
    67.                     overlap = false;
    68.                     foreach (KeyValuePair<Node, Rect> kvp in canvasUsed)
    69.                     {
    70.                         if (paddedRect.Overlaps(kvp.Value,true))
    71.                             overlap = true;
    72.                     }
    73.                     if (overlap)
    74.                     {
    75.                         if(counter%2==0)
    76.                         {
    77.                             paddedRect.x = (nodeX + (counter * (node.rect.width / 16)));
    78.                         } else
    79.                         {
    80.                             paddedRect.x = (nodeX - (counter * (node.rect.width / 16)));
    81.                         }
    82.                     }
    83.                     counter++;
    84.                 }
    85.                 node.rect.x = paddedRect.x;
    86.                 node.rect.y = paddedRect.y;
    87.                 canvasUsed.Add(node, paddedRect);
    88.                 if (node.Inputs != null)
    89.                 {                  
    90.                     foreach (NodeInput ni in node.Inputs)
    91.                     {                      
    92.                         Node childNode = ni.GetNodeAcrossConnection();
    93.                         if(childNode!=null)
    94.                             placeNode(childNode, ni.side, node);
    95.                     }
    96.                 }
    97.             }
    98.         }
    99.  
    100.         /// <summary>
    101.         /// Place Node will build the tree as compact as it can
    102.         /// </summary>
    103.         /// <param name="node">The node we are working on</param>
    104.         /// <param name="side">The side it is from the parent</param>
    105.         /// <param name="parentNode">The parent</param>
    106.         private static void placeNode(Node node, NodeSide side, Node parentNode)
    107.         {
    108.             Vector2 center = new Vector2();
    109.             int siblings = siblingCount(node, true);
    110.             float nodeX = 0, nodeY = 0;
    111.             bool placed = false;
    112.             float nodeOffsetX = 0, nodeOffsetY = 0;
    113.             while (!placed)
    114.             {
    115.                 switch (side)
    116.                 {
    117.                     case (NodeSide.Bottom):
    118.                         nodeY = parentNode.rect.yMax + LAYOUT_SEP_Y + (node.rect.height);
    119.                         nodeX = parentNode.rect.center.x - ((siblings / 2) * (LAYOUT_SEP_X + node.rect.width));
    120.                         foreach(NodeOutput no in node.Outputs)
    121.                         {
    122.                             if(no.side==NodeSide.Top)
    123.                             {
    124.                                 nodeY += node.rect.height;
    125.                             }
    126.                         }
    127.                         foreach (NodeInput no in node.Inputs)
    128.                         {
    129.                             if (no.side == NodeSide.Top)
    130.                             {
    131.                                 nodeY += node.rect.height;
    132.                             }
    133.                         }
    134.  
    135.                         break;
    136.                     case (NodeSide.Top):
    137.                         nodeY= parentNode.rect.yMin - LAYOUT_SEP_Y - (node.rect.height / 2);
    138.                         nodeX = parentNode.rect.center.x - ((siblings / 2) * (LAYOUT_SEP_X + node.rect.width));
    139.                         foreach(NodeOutput no in node.Outputs)
    140.                         {
    141.                             if (no.side == NodeSide.Bottom)
    142.                             {
    143.                                 nodeY -= node.rect.height;
    144.                             }
    145.                         }
    146.                         foreach (NodeInput no in node.Inputs)
    147.                         {
    148.                             if (no.side == NodeSide.Bottom)
    149.                             {
    150.                                 nodeY -= node.rect.height;
    151.                             }
    152.                         }
    153.                         break;
    154.                     case (NodeSide.Left):
    155.                         nodeY = parentNode.rect.center.y - ((siblings / 2) * (LAYOUT_SEP_Y + node.rect.height));
    156.                         nodeX = parentNode.rect.xMin - LAYOUT_SEP_X - (node.rect.width / 2);
    157.                         foreach (NodeOutput no in node.Outputs)
    158.                         {
    159.                             if (no.side == NodeSide.Left)
    160.                             {
    161.                                 nodeX += node.rect.width;
    162.                             }
    163.                         }
    164.                         break;
    165.                     case (NodeSide.Right):
    166.                         nodeY = parentNode.rect.center.y - ((siblings / 2) * (LAYOUT_SEP_Y + node.rect.height));
    167.                         nodeX = parentNode.rect.xMax + LAYOUT_SEP_X + (node.rect.width / 2);
    168.                         foreach (NodeOutput no in node.Outputs)
    169.                         {
    170.                             if (no.side == NodeSide.Right)
    171.                             {
    172.                                 nodeX -= node.rect.width;
    173.                             }
    174.                         }
    175.  
    176.                         break;
    177.                 }
    178.                 Rect nodeRect = node.rect;
    179.                 nodeRect.center = new Vector2(nodeX + nodeOffsetX, nodeY + nodeOffsetY);
    180.                 bool overlap = false;
    181.                 Rect paddedRect = new Rect(nodeRect);
    182.                 paddedRect.width += LAYOUT_SEP_X;
    183.                 paddedRect.height += LAYOUT_SEP_Y;
    184.                
    185.                 foreach(KeyValuePair<Node,Rect> kvp in canvasUsed)
    186.                 {
    187.                     if (paddedRect.Overlaps(kvp.Value,true))
    188.                         overlap = true;
    189.                 }
    190.                 if(overlap)
    191.                 {
    192.                     switch (side)
    193.                     {
    194.                         case (NodeSide.Bottom):
    195.                             nodeOffsetX += (node.rect.width / 16);
    196.                             break;
    197.                         case (NodeSide.Top):
    198.                             nodeOffsetX += (node.rect.width / 16);
    199.                             break;
    200.                         case (NodeSide.Left):
    201.                             nodeOffsetY += (node.rect.height / 16);
    202.                             break;
    203.                         case (NodeSide.Right):
    204.                             nodeOffsetY += (node.rect.height / 16);
    205.                             break;
    206.  
    207.                     }
    208.                 } else
    209.                 {
    210.                     node.rect = nodeRect;
    211.                     canvasUsed.Add(node, paddedRect);
    212.                     placed = true;
    213.                 }
    214.             }
    215.             foreach (KeyValuePair<Node, NodeSide> kvp in getChildrenDirection(node))
    216.                 placeNode(kvp.Key, kvp.Value,node);
    217.         }
    218.  
    219.         private static List<Node> getChildren(Node n)
    220.         {
    221.             List<Node> returnList = new List<Node>();
    222.             if(!isLeaf(n))
    223.             {
    224.                 foreach(NodeInput ni in n.Inputs)
    225.                 {
    226.                     Node nd = ni.GetNodeAcrossConnection();
    227.                     if (nd != null)
    228.                         returnList.Add(nd);
    229.                 }
    230.             }
    231.             return (returnList);
    232.         }
    233.  
    234.         private static IDictionary<Node,NodeSide> getChildrenDirection(Node n)
    235.         {
    236.             IDictionary<Node, NodeSide> returnList = new Dictionary<Node, NodeSide>();
    237.  
    238.             foreach (NodeInput ni in n.Inputs)
    239.             {
    240.                 Node nd = ni.GetNodeAcrossConnection();
    241.                 if (nd != null)
    242.                     returnList.Add(nd,ni.side);
    243.             }
    244.             return (returnList);
    245.         }
    246.  
    247.         private static bool isLeaf(Node n)
    248.         {
    249.             if(n.Outputs==null)
    250.             {
    251.                 return false;
    252.             }
    253.             foreach(NodeOutput no in n.Outputs)
    254.             {
    255.                 if(no.GetNodeAcrossConnection()!=null)
    256.                 {
    257.                     return true;
    258.                 }
    259.             }
    260.  
    261.             return false;
    262.         }
    263.  
    264.         private static int siblingCount(Node node, bool useNodeType)
    265.         {
    266.             // This shouldnt be used on parent nodes
    267.             if(node.Outputs==null)
    268.             {
    269.                 return -1;
    270.             }
    271.  
    272.             int count = 0;
    273.             foreach(NodeOutput no in node.Outputs)
    274.             {
    275.                 Node parentNode = no.GetNodeAcrossConnection();
    276.                 if(parentNode!=null)
    277.                 {
    278.                     foreach(NodeInput ni in parentNode.Inputs)
    279.                     {
    280.                         Node n = ni.GetNodeAcrossConnection();
    281.                         if (n != null)
    282.                         {
    283.                             if (useNodeType)
    284.                             {
    285.                                 if (n.GetID == node.GetID)
    286.                                 {
    287.                                     count++;
    288.                                 }
    289.                             }
    290.                             else
    291.                             {
    292.                                 count++;
    293.                             }
    294.                         }
    295.                     }
    296.                 }
    297.             }
    298.             return count;
    299.         }
    300.     }
    301.  
     
  15. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thanks, @rohmer!
    But I'm getting this error when auto-layouting in the calculation example...
     
  16. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Thats really odd. That should not happen.. It should never try to duplicate the node in the used area dictionary. Let me take a look, I may key off a checksum of the node or something.
     
  17. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Well maybe you can simply check if it's already in the list? ;)
    Or can you test if you also get it in the Calculation example canvas or if it's something specific.
     
  18. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Ok, fixed and some layout additions
    Code (CSharp):
    1.     public static class LayoutTools
    2.     {
    3.         private static IDictionary<Node, Rect> canvasUsed = new Dictionary<Node, Rect>();
    4.         private const float LAYOUT_SEP_Y = 30;
    5.         private const float LAYOUT_SEP_X = 30;
    6.  
    7.         public static void GenerateLayout(NodeCanvas canvas)
    8.         {
    9.             canvasUsed.Clear();
    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.  
    33.             // This defined the input location for the parent, based on count of inputs.  That way
    34.             // we can push the parents in the opposite direction
    35.             int xShift = 0;
    36.             int yShift = 0;
    37.  
    38.             float parentWidth = 0, parentHeight = 0;
    39.             foreach(Node node in parentNodes)
    40.             {
    41.                 parentWidth += node.rect.width + LAYOUT_SEP_X*2;
    42.                 parentHeight += node.rect.height + LAYOUT_SEP_Y * 2;
    43.                 foreach(NodeInput ni in node.Inputs)
    44.                 {
    45.                     switch(ni.side)
    46.                     {
    47.                         case (NodeSide.Bottom):
    48.                             yShift--;
    49.                             break;
    50.                         case (NodeSide.Left):
    51.                             xShift--;
    52.                             break;
    53.                         case (NodeSide.Right):
    54.                             xShift++;
    55.                             break;
    56.                         case (NodeSide.Top):
    57.                             yShift++;
    58.                             break;
    59.                     }
    60.                 }
    61.             }
    62.  
    63.             int nodeCounter = 0;
    64.             foreach(Node node in parentNodes)
    65.             {
    66.                 float nodeX;
    67.                 if (parentNodes.Count == 1)
    68.                 {
    69.                     nodeX = 0;
    70.                 }
    71.                 else
    72.                 {
    73.                     if (nodeCounter % 2 == 0)
    74.                     {
    75.                         nodeX = nodeCounter * (-1 * (parentWidth / 2));
    76.                     } else
    77.                     {
    78.                         nodeX = -1 * nodeCounter * (-1 * (parentWidth / 2));
    79.                     }
    80.                 }
    81.                 float nodeY=0;
    82.                 if (xShift<0)
    83.                 {
    84.                     nodeX -= 500;
    85.                 }
    86.                 if(xShift>0)
    87.                 {
    88.                     nodeX += 500;
    89.                 }
    90.                 if(yShift<0)
    91.                 {
    92.                     nodeY -= 500;
    93.                 }
    94.                 if(yShift>0)
    95.                 {
    96.                     nodeY += 500;
    97.                 }
    98.                
    99.                 node.rect.center = new Vector2(nodeX, nodeY);
    100.                 bool overlap = true;
    101.                 int counter = 1;
    102.                 Rect paddedRect = new Rect(node.rect);
    103.                 paddedRect.width += LAYOUT_SEP_X;
    104.                 paddedRect.height += LAYOUT_SEP_Y;
    105.                 while (overlap)
    106.                 {
    107.                     overlap = false;
    108.                     foreach (KeyValuePair<Node, Rect> kvp in canvasUsed)
    109.                     {
    110.                         if (paddedRect.Overlaps(kvp.Value,true))
    111.                             overlap = true;
    112.                     }
    113.                     if (overlap)
    114.                     {
    115.                         if(counter%2==0)
    116.                         {
    117.                             paddedRect.x = (nodeX + (counter * (node.rect.width / 16)));
    118.                         } else
    119.                         {
    120.                             paddedRect.x = (nodeX - (counter * (node.rect.width / 16)));
    121.                         }
    122.                     }
    123.                     counter++;
    124.                 }
    125.                 node.rect.x = paddedRect.x;
    126.                 node.rect.y = paddedRect.y;
    127.                 if (canvasUsed.ContainsKey(node))
    128.                 {
    129.                     canvasUsed[node] = paddedRect;
    130.                 }
    131.                 else
    132.                 {
    133.                     canvasUsed.Add(node, paddedRect);
    134.                 }
    135.                 if (node.Inputs != null)
    136.                 {                  
    137.                     foreach (NodeInput ni in node.Inputs)
    138.                     {                      
    139.                         Node childNode = ni.GetNodeAcrossConnection();
    140.                         if(childNode!=null)
    141.                             placeNode(childNode, ni.side, node);
    142.                     }
    143.                 }
    144.             }
    145.         }
    146.  
    147.         /// <summary>
    148.         /// Place Node will build the tree as compact as it can
    149.         /// </summary>
    150.         /// <param name="node">The node we are working on</param>
    151.         /// <param name="side">The side it is from the parent</param>
    152.         /// <param name="parentNode">The parent</param>
    153.         private static void placeNode(Node node, NodeSide side, Node parentNode)
    154.         {
    155.             Vector2 center = new Vector2();
    156.             int siblings = siblingCount(node, true);
    157.             float nodeX = 0, nodeY = 0;
    158.             bool placed = false;
    159.             float nodeOffsetX = 0, nodeOffsetY = 0;
    160.             while (!placed)
    161.             {
    162.                 switch (side)
    163.                 {
    164.                     case (NodeSide.Bottom):
    165.                         nodeY = parentNode.rect.yMax + LAYOUT_SEP_Y + (node.rect.height/2);
    166.                         nodeX = parentNode.rect.center.x - ((siblings / 2) * (LAYOUT_SEP_X + node.rect.width));
    167.                         foreach(NodeOutput no in node.Outputs)
    168.                         {
    169.                             if(no.side==NodeSide.Top)
    170.                             {
    171.                                 nodeY += node.rect.height;
    172.                             }
    173.                         }
    174.                         foreach (NodeInput no in node.Inputs)
    175.                         {
    176.                             if (no.side == NodeSide.Top)
    177.                             {
    178.                                 nodeY += node.rect.height;
    179.                             }
    180.                         }
    181.  
    182.                         break;
    183.                     case (NodeSide.Top):
    184.                         nodeY= parentNode.rect.yMin - LAYOUT_SEP_Y - (node.rect.height / 2);
    185.                         nodeX = parentNode.rect.center.x - ((siblings / 2) * (LAYOUT_SEP_X + node.rect.width));
    186.                         foreach(NodeOutput no in node.Outputs)
    187.                         {
    188.                             if (no.side == NodeSide.Bottom)
    189.                             {
    190.                                 nodeY -= node.rect.height;
    191.                             }
    192.                         }
    193.                         foreach (NodeInput no in node.Inputs)
    194.                         {
    195.                             if (no.side == NodeSide.Bottom)
    196.                             {
    197.                                 nodeY -= node.rect.height;
    198.                             }
    199.                         }
    200.                         break;
    201.                     case (NodeSide.Left):
    202.                         nodeY = parentNode.rect.center.y - ((siblings / 2) * (LAYOUT_SEP_Y + node.rect.height))/2;
    203.                         nodeX = parentNode.rect.xMin - LAYOUT_SEP_X - (node.rect.width / 2);
    204.                         foreach (NodeOutput no in node.Outputs)
    205.                         {
    206.                             if (no.side == NodeSide.Left)
    207.                             {
    208.                                 nodeX += node.rect.width;
    209.                             }
    210.                         }
    211.                         break;
    212.                     case (NodeSide.Right):
    213.                         nodeY = parentNode.rect.center.y - ((siblings / 2) * (LAYOUT_SEP_Y + node.rect.height))/2;
    214.                         nodeX = parentNode.rect.xMax + LAYOUT_SEP_X + (node.rect.width / 2);
    215.                         foreach (NodeOutput no in node.Outputs)
    216.                         {
    217.                             if (no.side == NodeSide.Right)
    218.                             {
    219.                                 nodeX -= node.rect.width;
    220.                             }
    221.                         }
    222.  
    223.                         break;
    224.                 }
    225.                 Rect nodeRect = node.rect;
    226.                 nodeRect.center = new Vector2(nodeX + nodeOffsetX, nodeY + nodeOffsetY);
    227.                 bool overlap = false;
    228.                 Rect paddedRect = new Rect(nodeRect);
    229.                 paddedRect.width += LAYOUT_SEP_X;
    230.                 paddedRect.height += LAYOUT_SEP_Y;
    231.                
    232.                 foreach(KeyValuePair<Node,Rect> kvp in canvasUsed)
    233.                 {
    234.                     if (paddedRect.Overlaps(kvp.Value,true))
    235.                         overlap = true;
    236.                 }
    237.                 if(overlap)
    238.                 {
    239.                     switch (side)
    240.                     {
    241.                         case (NodeSide.Bottom):
    242.                             nodeOffsetX += (node.rect.width / 16);
    243.                             break;
    244.                         case (NodeSide.Top):
    245.                             nodeOffsetX += (node.rect.width / 16);
    246.                             break;
    247.                         case (NodeSide.Left):
    248.                             nodeOffsetY += (node.rect.height / 16);
    249.                             break;
    250.                         case (NodeSide.Right):
    251.                             nodeOffsetY += (node.rect.height / 16);
    252.                             break;
    253.  
    254.                     }
    255.                 } else
    256.                 {
    257.                     node.rect = nodeRect;
    258.                     if(canvasUsed.ContainsKey(node))
    259.                     {
    260.                         canvasUsed[node] = paddedRect;
    261.                     } else
    262.                     {
    263.                         canvasUsed.Add(node, paddedRect);
    264.                     }                  
    265.                     placed = true;
    266.                 }
    267.             }
    268.             foreach (KeyValuePair<Node, NodeSide> kvp in getChildrenDirection(node))
    269.                 placeNode(kvp.Key, kvp.Value,node);
    270.         }
    271.  
    272.         private static List<Node> getChildren(Node n)
    273.         {
    274.             List<Node> returnList = new List<Node>();
    275.             if(!isLeaf(n))
    276.             {
    277.                 foreach(NodeInput ni in n.Inputs)
    278.                 {
    279.                     Node nd = ni.GetNodeAcrossConnection();
    280.                     if (nd != null)
    281.                         returnList.Add(nd);
    282.                 }
    283.             }
    284.             return (returnList);
    285.         }
    286.  
    287.         private static IDictionary<Node,NodeSide> getChildrenDirection(Node n)
    288.         {
    289.             IDictionary<Node, NodeSide> returnList = new Dictionary<Node, NodeSide>();
    290.  
    291.             foreach (NodeInput ni in n.Inputs)
    292.             {
    293.                 Node nd = ni.GetNodeAcrossConnection();
    294.                 if (nd != null)
    295.                     returnList.Add(nd,ni.side);
    296.             }
    297.             return (returnList);
    298.         }
    299.  
    300.         private static bool isLeaf(Node n)
    301.         {
    302.             if(n.Outputs==null)
    303.             {
    304.                 return false;
    305.             }
    306.             foreach(NodeOutput no in n.Outputs)
    307.             {
    308.                 if(no.GetNodeAcrossConnection()!=null)
    309.                 {
    310.                     return true;
    311.                 }
    312.             }
    313.  
    314.             return false;
    315.         }
    316.  
    317.         private static int siblingCount(Node node, bool useNodeType)
    318.         {
    319.             // This shouldnt be used on parent nodes
    320.             if(node.Outputs==null)
    321.             {
    322.                 return -1;
    323.             }
    324.  
    325.             int count = 0;
    326.             foreach(NodeOutput no in node.Outputs)
    327.             {
    328.                 Node parentNode = no.GetNodeAcrossConnection();
    329.                 if(parentNode!=null)
    330.                 {
    331.                     foreach(NodeInput ni in parentNode.Inputs)
    332.                     {
    333.                         Node n = ni.GetNodeAcrossConnection();
    334.                         if (n != null)
    335.                         {
    336.                             if (useNodeType)
    337.                             {
    338.                                 if (n.GetID == node.GetID)
    339.                                 {
    340.                                     count++;
    341.                                 }
    342.                             }
    343.                             else
    344.                             {
    345.                                 count++;
    346.                             }
    347.                         }
    348.                     }
    349.                 }
    350.             }
    351.             return count;
    352.         }
    353.     }
     
  19. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    That's definitely a good start! :)
    Though sometimes the order of the nodes in horizontal direction doesn't make much sense because connections wind back when there would be a configuration that would not need that.
    Also, maybe you can add a min x/y seperation for connections seperately, so if there is a connection is either x/y direction or even both that is has more clearance?

    Anyway, that is already really cool. But I'm thinking for some canvases a simple tree structure would suffice, I think it's a bit easier to implement than reinventing the wheel.
    Thank you rohmer!
     
  20. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    upload_2016-9-16_12-4-50.png

    An example of a fairly complicated structure laid out
     
    Seneral likes this.
  21. rohmer

    rohmer

    Joined:
    Mar 24, 2014
    Posts:
    23
    Some useful additions I think:
    1. Take into account the connectors so that they don't overlap and don't look odd.
    2. Potentially add threading, as a lot of this could be done multithreaded
     
    Seneral likes this.
  22. Deleted User

    Deleted User

    Guest

    I was just planning build a node based system for my landscape generator and this is already looking very promising. I will look into the code in the coming weeks and if I can help out coding-wise I will ...
     
  23. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thanks:)
    Although, if you're looking for a good node based landscape generator you might want to take a look at TC2. Have not tested it yet, but MapMagic is also node-based. But I suppose if you decided to make your own you have good reasons;)
     
  24. PitHeimes

    PitHeimes

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

    thanks for the update on the SceneSaveCaching. It works great in terms of script updated. But canvas is still lost when entering playmode. At least for my setup.

    Thanks for the constant updates :) We really appreciate your effort :)
     
  25. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thanks for the report and your kind words:)
    Was an easy fix, more than that it's now way more simple. It's not using some sketchy events to find a time to load the cache but simply tries to load it when the canvas is null... Seems obvious lol
    Now the cache is always reliably loaded:)
     
  26. Deleted User

    Deleted User

    Guest

    Indeed, I have a different audience targeted and als a different end-goal *smiles*
     
  27. kimsama

    kimsama

    Joined:
    Jan 2, 2009
    Posts:
    166
    Great works.

    I tried an experiment which mixing given animation clips and playing with Playable APIs, found on here. I used 'master' branch and figuring out of all features and making custom node were not difficult.

    By the way, it seems to take a bit long time when it create a new node on a canvas. Why? Hope to it will fixed in future commit.

    And other things to mention are that sometimes it creates two node not a one when it creates a new node.

    All textures which for resenting node and canvas are under 'Resources/Textures' folder. Doesn't it make sense to put those under 'Editor Default Resources' folder which is only for editor script?

    Cheers,

    -Kim
     
  28. RaHaN_GS

    RaHaN_GS

    Joined:
    Nov 9, 2015
    Posts:
    4
    Hi Everyone,

    I stumbled upon this amazing project, and immediately thought it could be the solution to what I'm trying to build in my game : a creative crafting system where you connect modules to combine, alter, and tamper with several effects.

    Problem is: I'm far below the level of code required to mod the framework as it is today into a "playable" thing in my game... so before I try and wrap my head around creating nodes, connections, and having all that available to the player at runtime for doing what my GD wants, I wanted to ask your input about the feasibility of those:

    - How hard would it be to code an "inventory" of nodes, so that the player could only use so many of the nodes he found (each node being a component of the "recipe" he'd create)?
    - How hard would it be to define "modifier modules" to slot into nodes (also inventory dependent), in order to alter their calculations/effects, like for example: an "Energy Converter Node" could take an "Energy" value as an input and output a "Damage" value, but if "modded" with an "Inverter Modifier" it would switch inputs and outputs to do the opposite? Or with the same example, a "Fire Modifier" would transform the "Damage" output into a "Fire Damage" output?
    - I couldn't find any repository of custom nodes shared by the community for that framework - is there one (would certainly help me better understand how all that works)?

    Here's an image to help understand the type of things I'd intent the framework to do for me:


    Thanks for reading me and any answer you can contribute !
    Cheers
     
  29. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I'll check out your repo to get an idea of what you're doing:)

    Regarding long creation times, that is indeed a problem. Not too much noticeable on PC but on my weak tablet-pc it may take up to 1 second...
    It is caused almost solely by the caching system. When you load a canvas, a lastSession.asset copy is created and is directly being worked on. All changes are automatically being applied to it, modifications of the state and existing nodes but also the addition of new nodes. The latter one has to be done 'manually', as nodes are their own scriptable object, so when a node is created, it has to be saved to lastSession.asset. This is the operation that takes so long...
    The benefit of that way of caching is that even if the editor crashes for some reason your progress is secured.

    If you want to disable the cache system completely, you can do so in NodeEditorWindow.cs when NodeEditorUserCache is created (which handles all the caching):
    Change
    Code (csharp):
    1. canvasCache = new NodeEditorUserCache(Path.GetDirectoryName(AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject (this))));
    to
    Code (csharp):
    1. canvasCache = new NodeEditorUserCache();
    and it will automatically be disabled.
    I might add a toggle for each canvas in the GUI aswell...
    Also, it would be possible to make a less agressive cache system that only saves a copy when the editor is closed down or every 1min or so. But this is currently not implemented.

    Do you have a way to reproduce the creation of two nodes? It has never happened to me, but I guess by now you're using the framework more than me :p

    Every resource is in 'Resources/Textures' so it can be accessed at runtime for those that want to use the runtime node editor or create their own (f.E. @RaHaN_GS :) ). If you do not need that, you can move it and other parts of the framework around of course. There are two things to consider:
    The 'editorPath' in NodeEditor.cs at the top for moving the whole framework:
    Code (csharp):
    1. public static string editorPath = "Assets/Plugins/Node_Editor/";
    and the line 73
    Code (csharp):
    1. ResourceManager.SetDefaultResourcePath (editorPath + "Resources/");
    to set the resource path to look for. You can change it to a editor-only path for graphics if you want in your final extension:)
    Just note I cannot change that as there are lots of people wanting to use it at runtime, me included:)

    Hope that explains some confusions:)
     
    Last edited: Oct 1, 2016
  30. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Hi @RaHaN_GS !
    I'm certain you're able to do exactly that, although as you're planning to make a runtime version for the player to use, you'd first have to consider the problem of saving at runtime. Currently, it is not possible to save scriptable objects and thus canvases at runtime. You can only load in those you have created in the editor:/
    That would not be a too big problem if you are going to save these recipes seperately in a text/XML file anyway. Then you could recreate the canvas by this information in a 'simple' tree layout by code. But if you don't have very much coding experience, this might be really nasty. Maybe I can create an example for this though:)

    After you considered that:
    1. Assuming you've already an inventory system (if not, it'd be better to make it seperate!), the problem would be to limit the amount of one type of node to create, right?
    Due to the modular system of nodes, you would need to modify the context filling function found in NodeEditorInputControls (first function):
    Code (csharp):
    1. [ContextFillerAttribute (ContextType.Canvas)]
    2. private static void FillAddNodes (NodeEditorInputInfo inputInfo, GenericMenu canvasContextMenu)
    3. { // Show all nodes, and if a connection is drawn, only compatible nodes to auto-connect
    4.     NodeEditorState state = inputInfo.editorState;
    5.     List<Node> displayedNodes = state.connectOutput != null? NodeTypes.getCompatibleNodes (state.connectOutput) : NodeTypes.nodes.Keys.ToList ();
    6.     foreach (Node compatibleNode in displayedNodes)
    7.     {
    8.         if (NodeCanvasManager.CheckCanvasCompability (compatibleNode, inputInfo.editorState.canvas.GetType ()))
    9.             canvasContextMenu.AddItem (new GUIContent ("Add " + NodeTypes.nodes[compatibleNode].adress), false, CreateNodeCallback, new NodeEditorInputInfo (compatibleNode.GetID, state));
    10.     }
    11. }
    Maybe add a get-property 'IsAvailable' to Node.cs (or by extending with the 'partial' keyword) and use that to determine if that type of node is available to the player.
    If, on the other hand, you have different types of resources (rather than recipe parts as I've understood) that the player needs in his inventory then it'd be best to create a general Resource-Node for every possible resource (and a resource type property obviously) and make that handle this.
    Hope that is not too confusing:/


    2. Regarding node modifiers, you can have inputs used as modifier-slots.
    @romer seems to be doing something like this, good visible in this post. Especially the use of the 'Material' nodes;)
    You can also make some custom GUI inside the node if that suits you better. If you want something like a extension like in your nice graphic you could override Node.DrawNode or extend NodeInput for different visualisation (or simply change graphic in ConnectionTypes). You see there're lots of possibilities for you here, depends on what you think is nicer.
    After that your node could change it's behaviour in the Calculation-methode depending on the modifications (or completely let the modification be up to the modification node for most flexibility) :)


    3. You can take a look at the Example/- subbranches on the repo. There's TextureComposer, a simple example made by me very early in the project and a more complex dialogue system made by @ChicK00o :)
    Highly recommended to check these out:)

    Btw, how did you made your graph-graphic? Looks really good! I'm searching for a good graph-visualization program (could use Node Editor Framework but would need to make all graphics/icons for it to look how I want and I'm not a good artist:/)
     
  31. RaHaN_GS

    RaHaN_GS

    Joined:
    Nov 9, 2015
    Posts:
    4
    Hi Seneral, thanks for your answers :) I'll look into it, even though I'm still way behind in terms of just getting the basics, I think... :p

    As for the thing I use for GD docs is the ever so infamous Visio. But you could find a ton of (even better suited) alternatives, especially if what you're looking for is something specifically for graph-visualization, just by taking a look at what's out there in terms of Mind Mapping software. Some of those are even free and all.

    Is there already some work that has been done for saving data from the Node Framework to XML or Txt files?

    As for the resources part, the crafting system is actually two fold: on part is the classic system need x mount of this, y amount of that to create a specific Part, and each Part has a Quality and Type that would in fine be represented as a Node in the second half of the crafting system.

    I guess you could say that the Node Framework would serve as an Engineering Gameplay, more than a crafting one, the latter being used to actually craft nodes to be used by the Engineering GP Loop.
     
    Last edited: Oct 2, 2016
  32. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Hey!
    I'm currently on a trip and I only have WLan in a few spots so responses might be slow and short until saturday...

    For Runtime saving, you might check with @M_Dizzle because he showed interest in that a while ago. Don't know if he started. Other than that, there hasn't been any progress so far!

    Got your Plan. Definitely possible with the Framework, so basically instead of getting node types to show up in the canvas context menu, you want the parts the player has? You'd need to modify the context filler function I linked above for that... you can also mix that with the dynamic nodes if you want (f.E. add the parts in a submenu)!
    Hope you can get the hang of the framework soon, I recommend to check the docs linked in the repo:)
     
    Last edited: Oct 7, 2016
  33. lloydhooson

    lloydhooson

    Joined:
    Apr 7, 2008
    Posts:
    77
    Hi, I've been looking through the node editor and It's really impressive. I have a question though, how do you save the node with overwrite. I cannot get the node editor to edit a file and save back to the file without unity re-creating the meta and therefore killing the links in the scene.

    Any help much appreciated.

    Thanks.
     
  34. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Hey, thanks for you feedback.
    Indeed a good point, haven't yet thought of that.
    I'll try to make a fix asap but it probably does require major changes to the save system because the edits have to be noted and manually applied to the stored canavs when overwriting. But maybe I'll instead find a way to preserve the meta file...
     
  35. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    @lloydhooson Implemented overwriting in this commit.
    Please tell me if that works for you:)

    - Implemented overwriting of canvas keeping references to the canvas intact, with both scene and asset saving
    - Fixed lastSession duplication error
     
    manpower13 likes this.
  36. lloydhooson

    lloydhooson

    Joined:
    Apr 7, 2008
    Posts:
    77
    Thanks, that's looking really good. Working well.

    To expand the feature would it be possible to have a save button that does not require re-selecting the file, such as "Save" and "Save As" feature. This would then be super easy for editing the file testing and re-saving the changes.

    However as it currently is working really well. Thanks.
     
  37. lloydhooson

    lloydhooson

    Joined:
    Apr 7, 2008
    Posts:
    77
    @Seneral I've had a pop at what I suggested as I have a complicated folder structure and navigating it tiresome. I have attached the edited Node Editor window to this post, as the commit on github seems to be not in a branch that is allowing me to work with with a pull request and I don't know which branch this version is from. I have used playerprefs to save and load the file paths, as well as display the quick save button and label on your working file. I know not the best solution.
     

    Attached Files:

  38. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Good idea! Was already working on it when you posted your update though...
    Basically I made everything like you but chose to store the path in the loaded canvas instead of the EditorPrefs, which allows to use that information to correctly identify the canvas (instead of always showing 'lastSession' as the loaded canvas at the top).
    Seems you got the develop branch btw;)

    Commit
    - Added 'Save Canvas' button immediately saving changes to the loaded canvas
    - Fixed loaded canvas name always appearing as 'lastSession'
    - Improved SideWindow GUI slightly
    - Increased min scale from 2 to 4
     
  39. NiloBR

    NiloBR

    Joined:
    Sep 9, 2012
    Posts:
    92
    Hello... that is a great framework... quite useful...
    Can someone tell me how can i add a node by code?? i cant find that out...
    What i want is have a permanent node on the canvas, so i need to create it every time a new canvas is created....
    Thx
     
  40. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    That's currently only possible with workarounds... You can create a node like this:
    Code (csharp):
    1. Node.Create ("calcNode", Vector2.zero);
    when you're outside of the regular canvas drawing loop (for example external scripts) you need to make sure your canvas is currently active in order to add the node to the right canvas. In that case, enclose that line like this:
    Code (csharp):
    1. NodeEditor.BeginEditingCanvas (canvas);
    2. Node.Create ("calcNode", Vector2.zero);
    3. NodeEditor.EndEditingCanvas ();
    But you have to find a place to put it in (like in CanvasCache.CreateNewCanvas) but that will not guarantee the node will be added when the canvas was created by other code.

    I'm currently working on expanding the CanvasType and CanvasCalculation modularity (see issue #70), which will make it as easy as adding it to an OnCreate function.
    Also note for custom canvases with custom rules you might want to make use of the CanvasTypes system, so you could still use the default canvas normally;) That later allows you to make use of the callbacks found there to make implementing stuff like this easier;)

    If you also only want one of these nodes in the canvas, for example a root node, and you don't want the user to create more than the first one, you can hide the canvas in the context menu (node attribute overload) so it can only be added by code.
     
  41. NiloBR

    NiloBR

    Joined:
    Sep 9, 2012
    Posts:
    92
    thx very much... will try it out
     
  42. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I'm actually nearly finished with the development of said canvas type modularity which will make it way easier (actually have an example running right now!).
    Maybe I'll even commit it today (as of this 'evening', it's 00:07 here...), but cannot promise;)
     
  43. NiloBR

    NiloBR

    Joined:
    Sep 9, 2012
    Posts:
    92
    cool... that is awesome....
    i dont want to bother you again but can i ask you one more question?... how do you think is the best way to have tabs, each tab with a nodeCanvas, and a single save file?
     
  44. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Sorry, missed your response.
    Shouldn't be that hard actually, if you can code a bit, the single save file will be a bit hard at most...

    First, add functionality of saving multiple canvases to one file to NodeCanvasSaveManager (or make a seperate class so updating is not such a pain). It should not be that hard, it's basically only repeating the inner body of the save/load functions for each canvas. Just make sure you use AddSubAssets so that every canvas will be saved as an subasset and not overwrite the others. When loading you then have to fetch every nodeCanvas as there are multiple in that save file, so use ResourceManager.LoadResources<NodeCanvas> instead.
    Modify NodeEditorUserCache accordingly, too, or better make a new copy with that change. Basically you only need to regard for storing and saving of the canvases.
    Then, you'd need to modify the window to make the tasks accessible (through a toolbar for example) and drawing only the selected one (or multiple at the same time, also possible).

    I can't think of any real problems here, but if they do arise, don't hesitate to ask! :)
     
  45. NiloBR

    NiloBR

    Joined:
    Sep 9, 2012
    Posts:
    92

    what i actually ended up doing was changing from a list of nodes to a nested list of nodes, so i can have a separated list for each tab, made a few adjustments and it worked like a charm... in the end i have only one NodeCanvas, so the save file did not change much...

    thx for answer... u r the best... i hope i can contribute to the repo at some point...
     
  46. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Cool! Regarding the update I was talking about, I just made a PR including what I have been working on. It's quite a major change and there is a slight compability issue with previous saves, thus the PR. Also I'd like to hear other opinions and ideas which callbacks/methods are still missing in NodeCanvas/NodeCanvasTraversal ;)

    Additionally I included an example canvas type that forces one (and only one) root node on the canvas with a custom traversal algorithm etc. You might want to check that out, too:)

    Link to PR
    Feedback and suggestions very much appreciated!! :)
     
  47. NiloBR

    NiloBR

    Joined:
    Sep 9, 2012
    Posts:
    92
    Definitely... will check it out...
     
  48. smitci

    smitci

    Joined:
    May 4, 2016
    Posts:
    3
    Hi
    How change node to Input value from script on runtime. And recalculate canvas
     
  49. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Depends on which branch from the repo you're using... Use something like that to assign the value to a node:
    Code (csharp):
    1. Node node = nodeCanvas.nodes[i];
    2. node.someValue = newValue;
    or you could make the node fetch it's value from the script instead.
    The reference to the nodeCanvas can simply be cached and assigned from the inspector for both asset and scene saves.

    In order to recalculate, use this in the main branch:
    Code (csharp):
    1. NodeEditor.RecalculateFrom (node); // OR
    2. NodeEditor.RecalculateAll (nodeCanvas);
    In the develop branch, recalculation is a bit different:
    Code (csharp):
    1. NodeEditor.Calculator.RecalculateFrom (node); // OR
    2. NodeEditor.Calculator.RecalculateAll (nodeCanvas);
    In the upcoming update (#109) for the develop branch it's more generic:
    Code (csharp):
    1. nodeCanvas.TraverseAll (); // OR
    2. nodeCanvas.OnChange (node);
    Sounds a bit confusing, but you probably have the master branch:) I'll try to integrate that into the docs at some point!
     
  50. Aramilion

    Aramilion

    Joined:
    Apr 15, 2016
    Posts:
    23
    Hi, Seneral, how would you go about centering canvas view on given Node.
    I've looked at NodeEditorState and was playing around with panOffset and zoomPanAdjust, but can't make it work to center on my node. I was using rect.center for it.
    For example, if my node is at
    (524.2, 13.6), reslt I get is (1372.6, 759.4)
    (2131, -1004), result I get is (3264.2, 142.6)
    Definietly something is wrong here :)

    [EDIT] my framework version is really old! Just after you've added Scene saving and some other QoL improvements.