Node Editor Question?

Hi all! I’m building a Node editor to help create missions in an open environment, and slowly I’m getting the results I want for this being the first time I extend the editor. I’m just a bit confused on making different types of nodes. In my editor window I can click the Trigger button to create a trigger node and the number of triggers I have will count from 0 and display in the title. I thought I could do the same thing with a new node Null. But it seems that the two nodes are totally dependent on each other and deleting them somehow creates a mess. Does anyone know how I can make these Rect windows independant of each other?

class MyEditor extends EditorWindow{
var triggerRect = new List.<Rect>();
var nullRect = new List.<Rect>();
    @MenuItem("Window/Editor %e")
    static function Init(){
        var ink : MyEditor = ScriptableObject.CreateInstance.<MyEditor>();
        ink.Show();
    }
    function OnGUI(){
        Menu();
        for(i = 0; i < triggerRect.Count; i++){
            GUI.color = Color.green;
            triggerRect[i] = GUI.Window(i,triggerRect[i],TriggerWindow, "Trigger " +i);
        }
        for(i = 0; i < nullRect.Count; i++){
            GUI.color = Color.magenta;
            nullRect[i] = GUI.Window(i,nullRect[i],NullWindow, "Null " +i);
        }
    }
    function Menu(){
            GUILayout.Space(30);
            if(GUILayout.Button("Trigger", GUILayout.Width(200), GUILayout.Height(20))){
                triggerRect.Add(new Rect(200,200,200,120));
            }
            if(GUILayout.Button("Null", GUILayout.Width(200), GUILayout.Height(20))){
                nullRect.Add(new Rect(200,200,120,120));
            }
    }
    function TriggerWindow(ID : int){
        GUILayout.Space(80);
        if(GUILayout.Button("X", GUILayout.Width(20))){
            triggerRect.RemoveAt(ID);
        }
        GUI.DragWindow();
    }
    function NullWindow(ID : int){
        GUILayout.Space(80);
        if(GUILayout.Button("X", GUILayout.Width(20))){
            nullRect.RemoveAt(ID);
        }
        GUI.DragWindow();
    }
}

Perhaps I need to create an editor script per node type and reference the scripts?

Possibly because of your IDs

“You can use the same function to create multiple windows. Just make sure that each window has its own ID.”
You’ve got window 0…1…etc for both types
For the null set, make the ID i+triggerRect.Count

I knew it had something to do w/ the IDs, but I thought it was the IDs in the function. I changed the nullRect to:

nullRect[i] = GUI.Window(i+triggerRect.Count,nullRect[i],NullWindow, "Null " +i);

and it worked perfectly! The only thing is, it doesn’t quite work if I want to create a third type of node. So far I have:

timerRect[i] = GUI.Window(i+nullRect.Count+triggerRect.Count ,timerRect[i],TimerWindow, "Timer " +i);

I would expect it to work just like that, assuming all other things are the same

With everything changed, it still doesn’t work for some reason. I decided to try a different approach but I may need a bit more “educating”. I’m trying to figure out if there is a way to create nodes represented by empty gameObjects. This is how my Hierarchy is set up:

+Quest <-This gameobject opens the editor that will hold and edit nodes of its children only
-Start <-This gameobject is a node that I want shown below, and etc. has “Start” script
-Obj
-End

This way, I can delete the gameObjects, and the nodes will be deleted as well. I thought I could do this with something like extends EditorWindow in the Start script, but the thing is, the Start gameObject has a trigger that’ll be activated by the player on enter to start the quest, which can’t be done in an editor script. I guess I’m asking a fool’s question, is there a way to use an editor script as both an editor and regular gameplay script? How can I make a node from my “Start” script to my “Editor” script?
Thanks for any help! :slight_smile:

Regarding your previous post, I tried adding a third set called timer myself, and it worked fine:
(In c#)

//Editor/NodeTest.cs
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;

public class NodeTest : EditorWindow {
    List<Rect> triggerRect;
    List<Rect> nullRect;
    List<Rect> timerRect;

    [MenuItem( "EDITORS/Node Test" )]
    static void Init () {
        NodeTest ink = ScriptableObject.CreateInstance<NodeTest>();
        ink.Show();
    }

    void OnEnable () {
        triggerRect = new List<Rect>();
        nullRect = new List<Rect>();
        timerRect = new List<Rect>();
    }

    void OnGUI () {
        Menu();
        BeginWindows();
        for ( int i = 0; i < triggerRect.Count; i++ ) {
            GUI.color = Color.green;
            triggerRect[i] = GUI.Window( i, triggerRect[i], TriggerWindow, "Trigger " + i );
        }
        for ( int i = 0; i < nullRect.Count; i++ ) {
            GUI.color = Color.magenta;
            nullRect[i] = GUI.Window( i + triggerRect.Count, nullRect[i], NullWindow, "Null " + i );
        }

        for ( int i = 0; i < timerRect.Count; i++ ) {
            GUI.color = Color.blue;
            timerRect[i] = GUI.Window( i + nullRect.Count + triggerRect.Count, timerRect[i], TimerWindow, "Timer " + i );
        }
        EndWindows();
    }

    void Menu () {
        GUILayout.Space( 30 );
        if ( GUILayout.Button( "Trigger", GUILayout.Width( 200 ), GUILayout.Height( 20 ) ) ) {
            triggerRect.Add( new Rect( 200, 200, 200, 120 ) );
        }
        if ( GUILayout.Button( "Null", GUILayout.Width( 200 ), GUILayout.Height( 20 ) ) ) {
            nullRect.Add( new Rect( 200, 200, 120, 120 ) );
        }
        if ( GUILayout.Button( "Timer", GUILayout.Width( 200 ), GUILayout.Height( 20 ) ) ) {
            timerRect.Add( new Rect( 200, 200, 60, 120 ) );
        }
    }

    void TriggerWindow ( int ID ) {
        GUILayout.Space( 80 );
        if ( GUILayout.Button( "X", GUILayout.Width( 20 ) ) ) {
            triggerRect.RemoveAt( ID );
        }
        GUI.DragWindow();
    }

    void NullWindow ( int ID ) {
        GUILayout.Space( 80 );
        if ( GUILayout.Button( "X", GUILayout.Width( 20 ) ) ) {
            nullRect.RemoveAt( ID - triggerRect.Count );
        }
        GUI.DragWindow();
    }

    void TimerWindow ( int ID ) {
        GUILayout.Space( 80 );
        if ( GUILayout.Button( "X", GUILayout.Width( 20 ) ) ) {
            timerRect.RemoveAt( ID - triggerRect.Count - nullRect.Count );
        }
        GUI.DragWindow();
    }
}

While this is fine, it would be simpler if, instead of having seperate lists for each type, you made a class with a selectable type, and just maintain a single list, something like:

//Editor/NodeTest.cs
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;

public class NodeTest : EditorWindow {

    List<MyNode> nodeList;

    NodeType selectedType;

    enum NodeType { trigger, nul, timer }; //'null' is not allowed

    class MyNode {
        public Rect rect;
        public NodeType nodeType;
        public MyNode ( Rect r, NodeType n ) {
            this.rect = r;
            this.nodeType = n;
        }
    }

    [MenuItem( "EDITORS/Node Test" )]
    static void Init () {
        NodeTest ink = ScriptableObject.CreateInstance<NodeTest>();
        ink.Show();
    }

    void OnEnable () {
        nodeList = new List<MyNode>();
    }

    void OnGUI () {
        Menu();
        BeginWindows();
        for ( int i = 0; i < nodeList.Count; i++ ) {
            switch ( nodeList[i].nodeType ) {
                case NodeType.trigger:
                    GUI.color = Color.green;
                    break;
                case NodeType.nul:
                    GUI.color = Color.magenta;
                    break;
                case NodeType.timer:
                    GUI.color = Color.blue;
                    break;
            }
            nodeList[i].rect = GUI.Window( i, nodeList[i].rect, NodeWindow, nodeList[i].nodeType + " " + i );
        }
        EndWindows();
    }

    void Menu () {
        GUILayout.Space( 30 );
        selectedType = (NodeType) EditorGUILayout.EnumPopup( selectedType, GUILayout.Width( 200 ), GUILayout.Height( 20 ) );
        if ( GUILayout.Button( "Add node", GUILayout.Width( 200 ), GUILayout.Height( 20 ) ) ) {
            nodeList.Add( new MyNode( new Rect( 200, 200, 200, 120 ), selectedType ) );
        }
    }

    void NodeWindow ( int ID ) {
        switch ( nodeList[ID].nodeType ) {
            case NodeType.trigger:
                GUILayout.Space( 80 );
                if ( GUILayout.Button( "X", GUILayout.Width( 20 ) ) ) {
                    nodeList.RemoveAt( ID );
                }
                break;
            case NodeType.nul:
                GUILayout.Space( 80 );
                if ( GUILayout.Button( "X", GUILayout.Width( 20 ) ) ) {
                    nodeList.RemoveAt( ID );
                }
                break;
            case NodeType.timer:
                GUILayout.Space( 80 );
                if ( GUILayout.Button( "X", GUILayout.Width( 20 ) ) ) {
                    nodeList.RemoveAt( ID );
                }
                break;
        }
        GUI.DragWindow();
    }
}

Now you don’t have to worry about offsetting the index of each window by the size of each previous collection.

Regarding you last post, editor scripts cannot function as game components.
What you can do is write a custom inspector for your trigger component, that adds a button to the inspector, which opens the node editor. When the button is clicked, it checks the objects hierarchy, and creates nodes from it.

Wow, this is great! I’ll be sure to build on this all day and update progress. Thank you so much! :slight_smile:

There was a great example of making node windows here in the forums like a year ago.
Take a look HERE

1 Like

lol. Thnx! I’ve been referencing this this for like two weeks now. :stuck_out_tongue:

Ok. I see how you’ve done it. It’s a great idea to not have to worry about offsetting because it’s a pain, but you did it depending on the type selected. I did some “menu rewiring” and decided to use a Generic Menu. Is there a way I can still use switch cases in this instance?
Editor.js

function OnGUI(){
     if(Event.current.button == 1 && Event.current.type == EventType.ContextClick){
          ClassMenu();
     }
}
function ClassMenu(){//The Generic Menu setup
        var menu : GenericMenu = new GenericMenu();
        menu.AddItem(new GUIContent("Setups.../Add Null Set"), false, DoSomething, "null");
        menu.AddItem(new GUIContent("Setups.../Add Trigger Set"), false, DoSomething, "trigger");
      
        menu.AddItem(new GUIContent("Events.../Add Animation Event"), false, DoSomething, "animation");
        menu.AddItem(new GUIContent("Events.../Add Timer Event"), false, DoSomething, "timer");
      
        menu.AddItem(new GUIContent("States/End State"), false, DoSomething, "end");
        menu.AddItem(new GUIContent("States/Start State"), false, DoSomething, "start");
            menu.AddSeparator("");
        menu.AddItem(new GUIContent("Create Embedded Mission"), false, DoSomething, "embed");
            menu.AddSeparator("");
        menu.AddItem(new GUIContent("Exit"),false,Exit, "closed");
            menu.ShowAsContext();
    }
    function DoSomething (obj:Object){
        Debug.Log ("Selected: " + obj);//Tells which menu item has been selected
    }
  
    function Exit(){
        this.Close();
    }

Inside DoSomething you can switch obj, once casted to string, it will contain the string you passed from the menu item (“null”, “trigger”, etc)

I think I might have the right idea, but it doesn’t Debug null with a captial “N”. Am I missing something?

function ClassMenu(){
var menu : GenericMenu = new GenericMenu();
        menu.AddItem(new GUIContent("Setups.../Add Null Set"), false, MakeNodes, nodes.nul);
}
function MakeNodes (o : Object){
        Debug.Log("Selected: " + o);
            switch(o){
                case "null":
                    Debug.Log("Null");
                    break;
            }
    }

Now youve changed the passed object to ‘nodes.nul’ which im guessing is an enum
so your case should also be ‘nodes.nul’

Yeah, I still didn’t get it, but I guess offsetting it wouldn’t be too bad, just a lot of debugging when the list gets long. I did switch the windows, so I won’t have to create a new function just to add a window and the window’s contents. The switch will just add a window itself, and window contents are taken care of:

function ClassMenu(){
var menu : GenericMenu = new GenericMenu();
        menu.AddItem(new GUIContent("Setups.../Add Null Set"), false, Nodes, nodes.nul);
        menu.AddItem(new GUIContent("Setups.../Add Trigger Set"), false, Nodes, nodes.trigger);
}
function Nodes(i : int){
        //Debug.Log("Selected: " + i);
        switch(i){
            case nodes.trigger:
            triggerRect.Add(new Rect(200,200,160,120));
            break;
          
            case nodes.nul:
            nullRect.Add(new Rect(200,200,160,120));
            break;
}

Now, all I have to do is figure out how to connect them and make them work with actual gameObjects. Thank you for all your help! :slight_smile: