Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Simple node editor

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

  1. brett_unity19

    brett_unity19

    Joined:
    May 1, 2018
    Posts:
    5
    First time post on Unity3D.... @Seneral - LOVE the node editor. I've been working with it for a couple of days and I'm looking for help on saving GameObjects referenced on a node. In my node definition, I have this variable name:


    [SerializeField]
    public GameObject ObjName;


    And in NodeGUI(), I have this:

    ObjName = (GameObject)EditorGUILayout.ObjectField(ObjName, typeof(GameObject), true, GUILayout.Width(250f), GUILayout.Height(20f));


    What else do I need to do to get the object to be saved? I suspect I'm not registering it correctly with the framework but didn't see how to do that.
     
  2. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Just about to leave:p
    Theoretically it's correct, but if that GO (or any other object) is stored in a Scene instead of the AssetDatabase (like Prefab), you can't reference it while the Canvas is save in the AssetDatabase. That would create a reference that couldn't be resolved when that specific scene is not loaded.
    Instead, you have to save the canvas aalon with that GameObject in the same scene. Sometimes that conflicts with other design/plans, but it's the only way apart from saving just the name/etc. and finding it in the scene (assuming that scene is loaded) which would be better for managers that are in every scene.
    So you can save in the scene from tr same popup, it will simply create an empty object in the current scene with a wrapper component that holds a copy of that canvas. Unity will now save it correctly inside the scene an maintain scene references!
    Hope that helps,
    Seneral
     
  3. brett_unity19

    brett_unity19

    Joined:
    May 1, 2018
    Posts:
    5
    Oh. Glad I asked! Thanks so much.
     
  4. KrytalityStudios

    KrytalityStudios

    Joined:
    Sep 12, 2016
    Posts:
    22
    I have a quick question on the implementation details of basically only drawing a certain portion of the canvas. The situation is this, right now I'm implementing the different nodes to be saved to my database, and I've changed the database idea up a little so that there is a limit on one database per project. Since that is so, I need to have some way to draw only a certain portion of the canvas, say just a single conversation and be able to switch between the different conversations to edit. I was just wondering about the implementation into doing that.
     
  5. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I'm not sure about your implementation, but you probably want to take a look at NodeEditor drawing loop, where the data of the canvas is processed. You probably have to select the correct NodeEditorState (view transformation, etc), the respective nodes and their connections only depending on what you want to draw.
    Sorry, on my trip atm, can't help you on code level.
     
  6. KrytalityStudios

    KrytalityStudios

    Joined:
    Sep 12, 2016
    Posts:
    22
    Thank you for your response! And that's fine because I have decided to roll my own sort of implementation of the Node Group class, called something like the NodeGroupVisible class or something like that, and basically the class will place all nodes created when it is active into its nodesToDraw var or something, and then that will allow the NodeEditor class to know what to draw in a nutshell. This will be the basis for allowing the UI to select a certain conversation or quest, and then only displaying the nodes related to that conversation or quest.

    The only thing is, and I may figure this out before you respond, but will not displaying the nodes stop them from getting messages? If so I guess somehow I could just create maybe something in the Node class that will make it stop responding to messages, but I don't really know. And it may not even be important o_O.

    Once again you really don't have to respond on a trip lol, but thanks anyway :)!
     
  7. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ya I'll try! :)
    If try are not drawn, there are no GUI controls at all, so they cannot be altered at all, if that is what you mean by receiving messages. Of course of you have a traversal routine in place, that would atils consider all notes (like the ccalculation routine). Also, if tgey have connections to visible nodes, those could of fourse interact with the invisible onces, in theory. But from the GUI perspective, they are simply non existent.
     
  8. KrytalityStudios

    KrytalityStudios

    Joined:
    Sep 12, 2016
    Posts:
    22
    So there is one issue right now, I have basically created a new list of groups that do what I said, but the issue is that the references to the nodes and the node canvas parent are not being saved in the group class I made, and I have marked it as serializable.

    So my question is how do you save this kind of stuff, because marking the group class as serializable is not saving the references to any SO objects.

    And so basically do I change it to a SO itself? This seems fairly logical but I will have to modify the save manager.

    I have tried to work around this by having some unique IDs for every node, but that has proven to be a pain so my options are as follows:

    Use unique IDs for every node and just work it out.
    Change the group class to an SO and modify the save manager a little (or maybe a lot idk)
    Just use the view transform to focus on a certain portion.
    Or, as I write this I thought this, do some trickery within the node class and do something similar to the dialogue editor example and just basically have some dialogue start node or quest start nodes, and all the ones connected are a part of the conversation/quest, allowing the node editor to only draw those groups of nodes in particular. Which seems like a good idea but yeah .

    Thanks again. Cheers!

    Quick edit: I have found something that may work with the unique ID solution and I am going to try it.
     
    Last edited: Jul 15, 2018
  9. brett_unity19

    brett_unity19

    Joined:
    May 1, 2018
    Posts:
    5
    I'm using the node editor to describe network diagrams for building systems and I was looking for advice on how to represent wires that branch. A pipe or a conduit will branch off and I want to represent that notation in the node editor. In current practice, this is done using a solid dot. I can cope with a very small node.

    Anyone have any advice on how to represent this type of notation? (You can see the solid dots on this image...)

     
  10. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    e
    Sorry for the late reply, am back from the trip now and working again:)
    It has to do with how the unity serializer works and how I adapted the system for working copies.
    Long story short, when saving the canvas, all scriptable objects get duplicated manually (so you are not working on the SO saved in the asset database directly) and all references to them have to be restored. So if you are referencing any SO, you have to restore the references. If you actually implement a new SO type, you additionally have to make sure it gets duplicated and recorded, too. Nodes and stuff have these two callbacks to record and restore those references incase they are referencing SOs directly.
    Your best bet would be to modify the save manager directly to replace the node references in your class with the new references. This block in the CreateWorkingCopy function is responsible for that, just use the following line:
    newRef = ReplaceSO (allSOs, clonedSOs, oldRef);

    where oldRef is the old node and newRef is the cloned node. Update every field in your class like this and it will work:)
    Unfortunately I don't think a generic, reflection based solution would be suitable here for finding and handling SOs correctly, since it would add quite an overhead compared to occasional manual additions.
     
    KrytalityStudios likes this.
  11. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Aren't multiple outputs functionally the same? You would have two wires all the way, but it would work. You can of course add these dots as an additional entity to the drawing mechanic, a simple class should work. Only problem I see is there is no notion of a connection so far, only for knobs that connect in between, so you have to figure out where to store it.

    One option would be to add a struct list on a knob which holds information like:
    list int outputsToCombine; vector2 position

    And then the drawing routine (called from here) would first go through these and draw a single connection to that position and continue the outputs from there (maybe choose line instead of bezier, or even a new form like in the above diagram).

    One problem: Currently, those connections are drawn twice, from both knobs side once. Hadn't that figured out when remaking the knob system recently, so maybe now is the point to fix that aswell. That would make it implementing easier.

    If you need help, please tell me.
    Seneral
     
  12. KrytalityStudios

    KrytalityStudios

    Joined:
    Sep 12, 2016
    Posts:
    22
    Good to hear your back :).

    So far, a unique ID system that I have implemented is working for me. Essentially I have made a ID generator that generates a unique ID for every node that is generated (I basically just added an ID field to the Node class) and I have subscribed to the NodeEditorCallbacks class so that whenever a Node is created, it generate an ID (which actually is a rather simple algorithm). Then when a Node is deleted, it unregisters its ID from the ID generator.

    I did have some trouble at first because I forgot that the static classes were not going to save their IDs when the editor is closed, so I just made a quick system for saving all the IDs to a SO and then referencing that. As far as I have tested, it works just fine. Basically it allows me to iterate over all of the nodes in the canvas, and if that nodes ID is in the active groups ID list, then that node is set to active, otherwise, inactive. I plan on keeping this system as long as it works, because having a ID for each node is pretty awesome.

    The one thing that is not quite working as well as I would like is that the group needs a reference to the parent canvas and because the problem with serialization I had before with the nodes, that hasn't been being saved. I have somehow figured out how to hack this a little(ever forgot what you did coding to get where you are now:D) so that it works, but it isn't quite there, so I will most likely use your suggestion.

    I am not sure actually about releasing my asset for a while o_O because I really want to learn my own coding skills and possibly release a game using it.

    My next step is to start filling out node classes and the canvas classes, and start filling out traversal routines so that the node editor is actually functional. I'm seriously considering some kind of outside scripting language for the quests and the condition system such as Lua, otherwise I may have to implement a very simple scripting language myself (which sounds kind of cool), but anywayo_O. Then I have to start actually implementing some stuff in gameplay such as cutscenes, Timeline support, Cinemachine support, custom editor windows, quest logs, conversations between actors. :eek:
     
  13. tiagocopelli

    tiagocopelli

    Joined:
    Apr 25, 2018
    Posts:
    1
    Hi.
    How did you create it. ID generator that generates a unique ID for every node that is generated. thanks
     
  14. ChristmasGT

    ChristmasGT

    Joined:
    Sep 21, 2011
    Posts:
    11
    Hey there! Is anyone having an issue with Unity 2018.2.5? It looks like the following code inside of the Init function is returning null

    Code (CSharp):
    1. public static void Init()
    2.         {
    3.             // Fetch rect acessors using Reflection
    4.             Assembly UnityEngine = Assembly.GetAssembly(typeof(UnityEngine.GUI));
    5.             Type GUIClipType = UnityEngine.GetType("UnityEngine.GUIClip", true);
    6.             // topmostRect looks to be returning Null after version 2018.2
    7.             PropertyInfo topmostRect = GUIClipType.GetProperty("topmostRect", BindingFlags.Static | BindingFlags.Public);
    8.             //
    9.             MethodInfo GetTopRect = GUIClipType.GetMethod("GetTopRect", BindingFlags.Static | BindingFlags.NonPublic);
    10.             MethodInfo ClipRect = GUIClipType.GetMethod("Clip", BindingFlags.Static | BindingFlags.Public, Type.DefaultBinder, new Type[] { typeof(Rect) }, new ParameterModifier[] { });
    11.             if (GUIClipType == null || topmostRect == null || GetTopRect == null || ClipRect == null)
    12.             {
    13.                 Debug.Log("GUIScaleUtility cannot run on this system! Compability mode enabled. For you that means you're not able to use the Node Editor inside more than one group:( Please PM me (Seneral @UnityForums) so I can figure out what causes this! Thanks!");
    14.                 Debug.Log((GUIClipType == null ? "GUIClipType is Null, " : "") + (topmostRect == null ? "topmostRect is Null, " : "") + (GetTopRect == null ? "GetTopRect is Null, " : "") + (ClipRect == null ? "ClipRect is Null, " : ""));
    15.                 compabilityMode = true;
    16.                 initiated = true;
    17.                 return;
    18.             }
    I'm still using the Master developmental branch, not the developmental version. Looks like Unity depreciated the GUIClip function in 2018.2?

    https://bitbucket.org/rotorz/reorde.../36/unityengineguiclip-not-accessible-anymore

    Really appreciate any assistance that you guys can provide, thanks!
     
  15. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    If I remember correctly it's already fixed in the development version, and since GUIScaleUtility is a standalone stility class you should be able to just copy it over from the dev branch:)
    Edit: Found it, from back in April:
    https://github.com/Seneral/Node_Editor_Framework/commit/d4b60843fb19ab3b46d9eb0b42e002285ab14a12
     
  16. ChristmasGT

    ChristmasGT

    Joined:
    Sep 21, 2011
    Posts:
    11
    You're the absolute best Seneral, thank you so much!
     
  17. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ah one thing I forgot, only now had the chance to check it.
    There is one change to GUIScaleUtility that you will have to account for when copying the develop version to master to fix above error.
    The BeginScale call has a new parameter, IsEditorWindow. Do make sure that, when the error fires after copying over, you insert a true as the forth parameter, DO NOT just add a false:)
    So the line should be:
     curEditorState.zoomPanAdjust = GUIScaleUtility.BeginScale (ref canvasRect, curEditorState.zoomPos, curEditorState.zoom, true, false);

    Of course, if you build it as a runtime editor, false would be the correct value. What it does is account for the native editor window toolbar of 23 pixels, else it would create an offset.
     
  18. KrytalityStudios

    KrytalityStudios

    Joined:
    Sep 12, 2016
    Posts:
    22
    :) It is a secret...

    Just kidding, it is really simple actually, since I am not the most advanced dev :D, I simply stored all of the IDs in a list and then made an algorithm to always generate a unique ID. That algorithm is quite simple, I simply loop through the IDs in the list, find the biggest one and store it in a variable. Knowing that it is the biggest ID, I just add 1 to that and it will always be unique. Not the absolute best probably but works well for now. It works especially well because I'm not looping through them every frame, just when a node is generated.
    You do need to preferably remove the node IDs when they are destroyed, but that is really simple too.
     
  19. KrytalityStudios

    KrytalityStudios

    Joined:
    Sep 12, 2016
    Posts:
    22
    @Seneral I appreciate all of your help, I just want to say my asset is coming along, and after I got the dialogue up and going, it has been going fantastic.
    One thing that I do want to address is something that I know will pop up in the future and that is creating dialogue through an XML file or the like. Would the best approach be to directly import the file into the dialogue editor, create everything needed and then I could save to my database via my custom saving system?
     
  20. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Glad to hear that! Make sure to post it here when it's out:)
    There is an Import/Export system in place, if you mean that, for custom formats, etc. There is a XML Format built-in, although I think that's not what you had in mind since it is not specifically for dialogue but for general node systems.
    The thing to keep in mind here is whether that format of yours will hold actual canvas information (position of nodes, etc.), basically anything that is needed to recreate the canvas as it was before the save, or if you translate it into a dialogue-specific format and have the rest be interpreted each time when importing (would require figuring out nodes, node connections, node layout, etc.). It kind of sounds like you want the last one, but that would obviously require alot of (unnecessary) coding.
     
    KrytalityStudios likes this.
  21. KrytalityStudios

    KrytalityStudios

    Joined:
    Sep 12, 2016
    Posts:
    22
    Yes I essentially want the user to define XML classes so like they define something like
    <PsuedoDialogueLine>
    <DialogueLineID>0</DialogueLineID>
    <Subtitle>Hello World!</Subtitle>
    <AudioClipName>HelloWorld.wav</AudioClipName>
    <ChildIDs>
    <ID>1</ID>
    <ID>2</ID>
    </ChildIDs>
    </PsuedoDialogueLine>
    without any canvas data, and then they design several classes like that, and then that is imported to the editor window, so I guess I will just have to create all of the nodes, layouts, connections, etc, from the XML data, but I don't think it will be that bad o_O.
     
  22. tossrock

    tossrock

    Joined:
    Mar 7, 2018
    Posts:
    12
    Hi Seneral, thanks for open sourcing this great framework. I'm wondering - what's the best way to make nodes tick per frame when in the Runtime version? I have a MonoBehaviour manager object which updates some knob values and calls Calculate() on a node in its Update(), but that doesn't cause anything to be redrawn. I've tried several combinations of ClearCalculation() and Calculate(), as well as attaching an RTCanvasCalculator and doing CalculateCanvas(), but I can't get it to redraw properly.

    If I detach/reattach the connection continuously, it does update to the correct value on each attach/detach, but it should be updating continuously regardless of user input. I thought maybe I should try calling NodeGUI() manually, but got a null reference on GUILayout.BeginHorizontal()

    Thanks again for the framework!
     
  23. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I hope I understood correctly -

    At runtime, the GUI is redrawn every frame, so no need to call that. If a value in a node is changed, it will display directly.

    It's rather the calculation. Depending WHAT chanes in your UI that would result in a change, you need to trigger a recalculate. As for when a node has changed parameters / UI changed, those nodes need the following line in the NodeGUI:
    Code (CSharp):
    1. if (GUI.changed)
    2. NodeEditor.curNodeCanvas.OnNodeChange (this);
    If you want to recalculate every frame, you need to call 'TraverseAll' on the node canvas. Be sure to call it on the working copy if required, although at runtime by default it does not create a working copy.

    Calling Calculate directly does not make follow-up nodes calculate, so that's probably the problem. RTCanvasCalculator should work alright...
     
    tossrock likes this.
  24. tossrock

    tossrock

    Joined:
    Mar 7, 2018
    Posts:
    12
    Thanks very much, I ended up roughly copying ContinueCalculation() from CanvasCalculator to RTCanvasCalculator and calling that in conjunction with ClearCalculation() on the node, which did what I want.

    I have another question, which is more high level. Do you think it would be possible to integrate traditional, MonoBehaviour-based UI elements into the ScriptableObject-based node system? For example, I would like to include a color-picker node based on this project: https://github.com/judah4/HSV-Color-Picker-Unity

    Possible? If so, what approach would you take?

    Thanks again for the framework, and being so responsive on here!
     
    Last edited: Oct 2, 2018
  25. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Hm that color picker seems to have been made with the new GUI, which is not usable in the editor.
    The whole unity editor is built with IMGUI and will always be, the new GUI is solely for gameplay UI.
    If you just want a color picker, there is already one you can access in the editor through a color field.
     
  26. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    19
    I made a public List<Modifier> in the Node class. I noticed that it isn't saving that List. The Modifier class is serializable, is there anything I need to do to make it save that list?
     
    Last edited: Oct 24, 2018
  27. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Is Modifier by any chance a ScriptableObject? In that case, yes, these need to be handled differently, registered so to say.
    If they are normal classes they should indeed serialize normally.
     
  28. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    19
    No it is not, here are two examples. What am I doing wrong?

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System;
    5.  
    6. namespace NodeEditorFramework
    7. {
    8.     [Serializable]
    9.     public abstract class Modifier
    10.     {
    11.         public abstract string GetID { get; }
    12.  
    13.         public abstract void Draw();
    14.         public abstract void Adjust();
    15.     }
    16. }
    17.  
    18. using System.Collections;
    19. using System.Collections.Generic;
    20. using UnityEngine;
    21. using NodeEditorFramework.Utilities;
    22. using System;
    23.  
    24. namespace NodeEditorFramework
    25. {
    26.     [Serializable]
    27.     [Modifier("Resize Modifier", typeof(IResizable))]
    28.     public class ResizeModifier : Modifier
    29.     {
    30.         private IResizable resizable;
    31.         private float scalar;
    32.  
    33.         public override string GetID { get{ return "ResizeModifier";} }
    34.  
    35.         public ResizeModifier(IResizable content)
    36.         {
    37.             resizable = content;
    38.         }
    39.  
    40.         public override void Adjust()
    41.         {
    42.             resizable.Resize(scalar);
    43.         }
    44.  
    45.         public override void Draw()
    46.         {
    47.             GUILayout.BeginHorizontal();
    48.             GUILayout.Label(GetID);
    49.             scalar = RTEditorGUI.FloatField(scalar, GUILayout.Width(100));
    50.             GUILayout.Space(350);
    51.             GUILayout.EndHorizontal();
    52.         }
    53.     }
    54. }
    55.  
     
    Last edited: Oct 24, 2018
  29. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ah, Unity does not serialize abstract classes, unfortunately... Even if it's bad for code quality, you'll either have to make it inherit UnityEngine.Object or remove the avszabst keyword.
     
  30. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    19
    Aaaaah okay, I tried doing that and it saved the list but it does not know what kind of Modifier was saved because of inheritance. I just read that polymorphism doesn't work with serialization. It is supported with scriptable objects tho. How would I go about saving those? Since you mentioned that they need to be registerd.
     
  31. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Yes, I wrote about it a while back, so I'm just leaving that post here again.
    It is not really fun to work with but that's what Unity's serialization system gives us...
    Hope that works out for you:)
     
    gangafinti likes this.
  32. Cognetic

    Cognetic

    Joined:
    Jun 15, 2017
    Posts:
    9
    I'm using .net 4.x equivalent and I'm finding the xml export does not work properly (everything works correctly when i switch back to 3.5 ) . There's a bit of stuff online talking about some breaking changes between 4.x and previous versions, but aside from suggestions of using legacy options i can't find anything that shows how to deal with these changes.
    Before I dive headlong into trying to work out whats going wrong, i was hoping someone could shed some light on how to fix this quickly and easily...
    Thanks!

    Edit: naturally i found the problem shortly after posting. when switching to 4.x there is an issue in XMLImportExport:
    Code (CSharp):
    1.     private XmlElement SerializeObjectToXML(XmlElement parent, object obj)
    2.         {
    3.             // TODO: Need to handle asset references
    4.             // Because of runtime compability, always try to embed objects
    5.             // If that fails, try to find references to assets (e.g. for textures)
    6.             try
    7.             { // Try to embed object
    8.                 XmlSerializer serializer = new XmlSerializer(obj.GetType());
    9.                 XPathNavigator navigator = parent.CreateNavigator();
    10.                 using (XmlWriter writer = navigator.AppendChild())
    11.                 {
    12.                     writer.WriteWhitespace("");
    13.  
    14.                     serializer.Serialize(writer, obj);
    15.                 }
    16.                 return (XmlElement)parent.LastChild;
    17.             }
    18.             catch (Exception e)
    19.             {
    20.                 Debug.Log(e.InnerException);
    21.                 Debug.Log("Could not serialize " + obj.ToString());
    22.                 return null;
    23.             }
    24.         }
    in this bit of code i've added:
    Code (CSharp):
    1. writer.WriteWhitespace("");
    Why does it work? no idea. solution was from here:
    https://stackoverflow.com/questions...tdocument-cannot-be-called-on-writers-created
     
    Last edited: Nov 5, 2018
    Seneral likes this.
  33. tossrock

    tossrock

    Joined:
    Mar 7, 2018
    Posts:
    12
    Hey Seneral, is there a way to declare which node types a Canvas can instantiate? I don't want the Example/ nodes to show up when I'm working on real Canvases, but I'd prefer not to delete the classes as they're useful examples, and it would make it more difficult to pull changes in the future, etc.
     
  34. Cognetic

    Cognetic

    Joined:
    Jun 15, 2017
    Posts:
    9
    @tossrock in the node examples, change the [Node (false, "etc")] to true...
    i.e.
    Code (CSharp):
    1.     [Node (true, "Example/Example Node")]
    2.     public class ExampleNode : Node
     
    tossrock likes this.
  35. toto007

    toto007

    Joined:
    Jul 18, 2014
    Posts:
    33
  36. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    I do not have access to a mac machine, only linux and windows, but it seems like you modified the gui skin of the editor, correct?
    I've looked at your fork but no change stood out to me that would be able to trigger this. The GUIStyle is only set up once in the NodeEditorGUI class, and there's no explicit differenciation between monitors, obviously...
    To be honest, I have no clue.
     
  37. toto007

    toto007

    Joined:
    Jul 18, 2014
    Posts:
    33
    Honestly I do not think I have made any particular changes to the GUISKIn of my editor. How could I verify? However I do not think this would justify the color change when I drag the window on the main monitor (mac retina)
    In any case, the issue is already present starting from the develop branch of your repository (and not from my fork)
     
  38. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Hm that's interesting, pretty sure it looks alot lighter than the standard GUISkin (both the background and the node).
    Maybe this WebGL demo works, this is what it is supposed to look like.
    That's why I thought you modified it already.
    Other than that, the GUI skin is created and set up in the NodeEditorGUI class as I said, maybe check on that. You didn't modify it, but with that setup it should make no difference between two monitors.
    So probably some involvement of some kind of Unity bug?
     
  39. toto007

    toto007

    Joined:
    Jul 18, 2014
    Posts:
    33
    I thought that it was some bug on unity but trying with another plugin Flowcanvas I expected to have the same problem and instead the color of the grid is like what you expect and don't have change color when drag window between two monitor
    Schermata 2018-11-11 alle 23.01.09.png

    However, I see that in preview unity the background.png have a different mode of rendering the color:
    Schermata 2018-11-11 alle 23.26.18.png

    Maybe this can help you understand the problem?
     
  40. toto007

    toto007

    Joined:
    Jul 18, 2014
    Posts:
    33
    For the background I solved and now you see correctly in both monitors. I saved the image with Photoshop and now it locates it in RGB format instead of ARGB. Still remains the problem of the color of the knot that is staggered Schermata 2018-11-11 alle 23.42.08.png
     
  41. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    19
    Question, I have a class that inherits from NodeCanvas. That class has a list of serialized objects called chapters. And it has a field currentChapter both of type Chapter. I store chaper[x] in currentChapter but when the canvas closes and gets reopend it is no longer a reference to chapter[x] but it is a copy aka a different object. What causes this?

    I fixed it by having a field int currentChapter to resemble the x in chapter[x] instead of a reference to the object. But I would like to know why this happens for future use.
     
    Last edited: Nov 20, 2018
  42. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Your fix is exactly right, unfortunately this is a limitation of the unity serialization system. For normal objects it will not create an object map and just copy whatever it finds, discarding the references.
    Thats why in the framework we make all objects (canvas, nodes, etc.) scriptable objects, which are stored seperately in the file system and when serialized, a proper reference to them is maintained. Since that also comes with some serious complications I would recommend you to keep your fix - if in the future you want to reference tthos chapter's from somewhere else though, eg. nodes, then going for a scriptable object solution is your best bet (the framework has some helper functions to aid in adding such scriptable objects).
    A small improvement you might want to consider adding for convenience is ISerializationCallbackReceiver to NodeCanvas, which would allow you to write two functions (before / after serialize) that takes the serialized int index field and fills in the currentChapter field. That way you have a proper API if that's of concern.
     
  43. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Sorry for not following up in that.
    It's most likely an image format incompability in Mac, as you probably guessed already. Maybe try out different formats or try to copy the settings manually from those images that do appear correct?
     
  44. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    19
    Another issue I have is that the Chapter contains a list of nodes. On selecting a certain chapter the nodes list in NodeCanvas gets replaced with the list inside that chapter. But that screws with the validation.When I add nodes and close the editor Unity crashes. And I don't really understand why. But I presume it has something to with with the serialization aswell.
     
  45. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Hm that is a change that will indeed disturb the system in many ways I'm afraid.

    One thing that comes to mind is that the save system will only consider the nodes in the current list and will NOT save the other nodes only referenced in the chapters. This could be changed by making a separate list still containing ALL nodes, and going through all references to the original list and changing the reference to the new list on a case-by-case basis (especially in the scripts around the save system). Or rework the system in such a way that only the drawing (and calculating/ etc.) parts mask out all other nodes not assigned to the current chapter.

    Also, if you reference the nodes in the chapters, those will loose reference to the actual nodes on saving, too. Remember what I said about some helper functions in the framework when dealing with ScriptableObjects (including nodes)? Nodes are copied on saving, and only through some helper functions does the framework KNOW about those references so it can restore them. In your case, since you extended NodeCanvas, you can overwrite CopyScriptableObjects in your canvas class and replace ALL nodes in your chapters with the new onces. This could look like this (pseudo code obviously):
    Code (csharp):
    1. foreach chapter c:
    2.      for int i from 0 bis c.nodes.count:
    3.           c.nodes[i] = replaceSO (c.nodes[i])
    That'll be enough to update the references.
     
  46. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    19
    Aaaaah I see since they are SO's, I totally forgot about that. Thank you so much! When I am done with my narrative tool I'll let you know and post some pics.
     
  47. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Nice! Hope that extra work pays out for you! Working with serialization in Unity can be a real pain, it's so limiting at times...
     
  48. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    19
    Hey! Sorry to bother you again. But I am trying to add some buttons underneath the nodes when they are selected.

    I put this inside the drawNode method. If I add the buttons within the area of the body they work but they obey the layout area of the body which is not what I want.

    If I place them underneath that layout area I get the buttons looking just fine hanging underneath a node. But I cannot press them because when I try to press them the canvas background gets selected and the node gets deselected. Why is this happening?

    Code (CSharp):
    1.  public override void DrawNode()
    2.         {
    3.             // Create a rect that is adjusted to the editor zoom and pixel perfect
    4.             Rect nodeRect = rect;
    5.             Vector2 pos = NodeEditor.curEditorState.zoomPanAdjust + NodeEditor.curEditorState.panOffset;
    6.             nodeRect.position = new Vector2((int)(nodeRect.x + pos.x), (int)(nodeRect.y + pos.y));
    7.             contentOffset = new Vector2(0, 35);
    8.  
    9.             // Create a headerRect out of the previous rect and draw it, marking the selected node as such by making the header bold
    10.             Rect headerRect = new Rect(nodeRect.x, nodeRect.y, nodeRect.width, contentOffset.y);
    11.             GUIStyle headerStyle = new GUIStyle();
    12.             headerStyle.normal.background = RTEditorGUI.ColorToTex(1, HeaderColor);
    13.             GUI.Box(headerRect, GUIContent.none, headerStyle);
    14.             GUI.color = new Color(0.35f, 0.35f, 0.35f);
    15.             GUI.Label(headerRect, Title, NodeEditor.curEditorState.selectedNode == this ? NodeEditorGUI.nodeLabelBoldLeft : NodeEditorGUI.nodeLabelLeft);
    16.             GUI.color = Color.white;
    17.  
    18.             // Begin the body frame around the NodeGUI
    19.             Rect bodyRect = new Rect(nodeRect.x, nodeRect.y + contentOffset.y, nodeRect.width, nodeRect.height - contentOffset.y);
    20.             GUIStyle bodyStyle = new GUIStyle();
    21.             bodyStyle.normal.background = RTEditorGUI.ColorToTex(1, Color.white);
    22.             GUI.BeginGroup(bodyRect, bodyStyle);
    23.             bodyRect.position = Vector2.zero;
    24.             GUILayout.BeginArea(bodyRect);
    25.  
    26.             // Call NodeGUI
    27.             GUI.changed = false;
    28.             NodeGUI();
    29.  
    30.             if (Event.current.type == EventType.Repaint)
    31.                 nodeGUIHeight = GUILayoutUtility.GetLastRect().max + contentOffset;
    32.  
    33.             // End NodeGUI frame
    34.             GUILayout.EndArea();
    35.             GUI.EndGroup();
    36.  
    37.  
    38.             //Buttons
    39.             int topMargin = 5;
    40.             GUILayout.BeginArea(new Rect(nodeRect.x, nodeRect.y + bodyRect.height + contentOffset.y + topMargin, nodeRect.width, 40));
    41.             GUILayout.BeginHorizontal();
    42.             if (NodeEditor.curEditorState.selectedNode == this)
    43.             {
    44.                 if (GUILayout.Toggle(NodeEditor.curNodeCanvas.rootNode == this, rootIcon, GUI.skin.button, GUILayout.Width(iconWidth), GUILayout.Height(iconHeight)))
    45.                     NodeEditor.curNodeCanvas.rootNode = this;
    46.                 if (GUILayout.Button(editIcon, GUILayout.Width(iconWidth), GUILayout.Height(iconHeight)))
    47.                     Debug.Log("edit");
    48.                 if (GUILayout.Button(removeIcon, GUILayout.Width(iconWidth), GUILayout.Height(iconHeight)))
    49.                     Delete();
    50.                 if (GUILayout.Button(duplicateIcon, GUILayout.Width(iconWidth), GUILayout.Height(iconHeight)))
    51.                     Debug.Log("duplicate");
    52.             }
    53.             GUILayout.EndHorizontal();
    54.             GUILayout.EndArea();
    55.  
    56.             // Automatically node if desired
    57.             AutoLayoutNode();
    58.  
    59.         }
     
    Last edited: Nov 21, 2018
  49. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    The click detection is manually handled for nodes, so any extras outside of the regular body won't be accounted for by default. You could modify the click detection and add a margin or even the precise boxes to the collision handling, if you really need that.

    Or maybe, just maybe, try to refactor the whole focussing system so the focussing is handled after the GUI and as such it would be able to respect the GUI controls. This is handled in the NodeEditorInputSystem, and the start would be to change the priority of the focussing and/or selecting to 100 or so. Everything above hundred gets called AFTER the GUI.
    But that won't be easy most likely, there's probably a good reason I put it before the GUI (can't remember though):)
     
  50. gangafinti

    gangafinti

    Joined:
    Jun 13, 2013
    Posts:
    19
    It looks like changing the priority works just fine! I might find out later why it doesn't work.