Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Documentation for interfacing programmatically #68

Closed
mio-moto opened this issue Apr 24, 2016 · 35 comments
Closed

Documentation for interfacing programmatically #68

mio-moto opened this issue Apr 24, 2016 · 35 comments

Comments

@mio-moto
Copy link

Currently the documentation is missing how you would plug in/out game states from the node-graph. (I've read the unity forums too, yet I have no clue how I would use this for something like game-logic.)

Any help and additions in the docs would be appreciated.

Additionally to this issue, I would highly recommend ReadTheDocs for auto-parsing documentation from documented classes, making it easy to make a comprehensive documentation for this project.

@Seneral
Copy link
Owner

Seneral commented Apr 24, 2016

You're right, the documentation is still lacking some more advanced topics than just extending it. I do try to improve it when I have time:)

Auto generated documentation from the code would also be nice, but we just decided to use mkdocs to generate the documentation (will go live soon) so it would be a pain to refactor it again to ReadTheDocs or similar. If anyone has suggestions regarding this, I'd appreciate them:)

@mio-moto
Copy link
Author

mio-moto commented Apr 24, 2016

mkdocs is the same as readthedocs, readthedocs is just a service provider with webhooks (iirc).

Is there somewhere a basic guide how to get started with accessing nodes / traversing them? If not, could you write something small down?

If any kind of documentation-service is up, I would like to contribute to it. I did simliar with my python bot projects and got some experience with the parsing and auto-doc part.

Edit: Also, wow, what a fast reply.

@Seneral
Copy link
Owner

Seneral commented Apr 24, 2016

We started discussing it in #46 and groud actually made it possible with mkdocs in his fork. It is currently documenting stuff that's only in the dev branch so it's not replacing the 'old' one, found here, yet.

@Seneral
Copy link
Owner

Seneral commented Apr 24, 2016

And if we can use readthedocs works with mkdocs, that'd be amazing:)
As long as we can still keep manually written articles, too.

@mio-moto
Copy link
Author

mio-moto commented May 7, 2016

Hey, I'm still working on interfacing with it - and some problems seem to be hard to solve.

To give you some feedback on that - which could be a seperate issue, probably:

I tried to write a random selection node. The best solution I may have found is adding a hidden first output which references the selection. Not really pretty, it would make more sense to let Calculate return what nodes it calculated or having an out / ref parameter.

Looking at certain nodes only works with something like this:

NodeCanvas x = NodeEditorFramework.NodeEditorSaveManager.LoadNodeCanvas("Assets/Plugins/Node_Editor/Resources/Saves/test_case.asset");
NodeEditor.RecalculateAll(x);

otherwise all nodes always return 0 on their inputs/outputs. I am not sure if this is intended.

@Seneral
Copy link
Owner

Seneral commented May 7, 2016

I don't quite understand what you mean:(
Maybe you can explain it in more detail, and what it has to do with the documentation (or not)?

@mio-moto
Copy link
Author

mio-moto commented May 7, 2016

Ref / Out

In my case, I would use the framework to write a dialogue system, with different branches.

Since dialogues could/should have random dialogues downwards, it would be useful to have a intermediate node to select a random path downwards. From here out on I would need to traverse downwards.

Let's say I have a dialogue root, which references 1 child dialogue which chooses a random dialogue node on runtime. I would now have a hard time getting the right child-dialogue to traverse downwards. It's probably about me not understanding some case, but this is how it looks right now:

Image

The only solution I currently can come up with is using the intermediate random node to store an object attribute "selection", writing it in the calculate, use the parent, check the connection (which then is the random node), execute calculate on the random and move on to the randomNode.outputs[randomNode.selection].

There are probably multiple other cases where having and out parameter would be useful as utility.

Recalculating Nodes

I have this piece of code sitting in the Update of a random monobehaviour of an enabled scene:

    NodeCanvas x = NodeEditorFramework.NodeEditorSaveManager.LoadNodeCanvas("Assets/Plugins/Node_Editor/Resources/Saves/test_case.asset");
    NodeEditor.RecalculateAll (x);
    Debug.Log ("I may have loaded.");
    foreach(Node n in x.nodes) {

        // n.Calculate ();
        string s = "> " + n.name;
        n.ClearCalculation ();
        foreach (NodeInput ni in n.Inputs) {
            if (ni.connection != null) {
                s += " | INPUT: " + ni.connection.GetValue<float> ();
            }
        }

        foreach (NodeOutput no in n.Outputs) {
            s += " | OUTPUT: " + no.GetValue<float> ();
        }

        Debug.Log (s);
    }

If you disable RecalculateAll all inputs/outputs return 0. Is this intended?

Thanks for all the help.

@Seneral
Copy link
Owner

Seneral commented May 7, 2016

Yes, it is intended as the calculated values are not serialized. I think that should be reasonable though, and your problem should be aproached differently...
EDIT: If you do it in update, each time the canvas would be reloaded and recalculated. If it's an option, I would cache it and the calculation state, then you would do both actions only in the start function:)

So, you really need the random child node in the parent node? Or do you 'just' want to take a random path down the tree at the random node 'junction'?
In the latter case, I'd implement a feature I remember was requested long ago which I didn't do yet (I forgot) but I could implement it in five minutes. It would enable you to 'block' the outputs of a node, so the only thing the random node would do is block all but the randomly selected output and then the calculation would only continue on that selected node. Would that help you?

@mio-moto
Copy link
Author

mio-moto commented May 7, 2016

That would improve it!

Another alternative would be some attribute on a dialoge node and do checks in its calculation. But I guess blocking would work then.

@Seneral
Copy link
Owner

Seneral commented May 7, 2016

Ok, I'll come back to you when I'm back home and I implemented it:)

Edit: Sry, wrong button...

@Seneral Seneral closed this as completed May 7, 2016
@Seneral Seneral reopened this May 7, 2016
@mio-moto
Copy link
Author

mio-moto commented May 7, 2016

Thank you very much for your kindness. After that I would fork that repo and add custom types, like Bool, Int, GameObject, etc..

Also what do you think of the brighter, slightly different knobs? I could merge-request them too, if you'd like to.

@Seneral
Copy link
Owner

Seneral commented May 7, 2016

Don't know, I like them, maybe a bit too bright (considering the lines). But the style of the node editor is rather unimportant right now as it is up to the developer to customize it for his asset;)

@Seneral
Copy link
Owner

Seneral commented May 7, 2016

To come back to the OP, I'd really want to write some more articles if I had more time... So I cannot exactly say when I'll be able to provide them:/

@Seneral
Copy link
Owner

Seneral commented May 7, 2016

@DarkMio I added output blocking in this commit: a05493e

An example for the random branch node, where a random branch get's chosen which will be caluclated further and the others get blocked:

using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using NodeEditorFramework;
using NodeEditorFramework.Utilities;

namespace NodeEditorFramework.Standard
{
    [Node (false, "Example/Random Branch")]
    public class RandomBranchNode : Node 
    {
        public const string ID = "randomBranchNode";
        public override string GetID { get { return ID; } }

        public override Node Create (Vector2 pos) 
        {
            RandomBranchNode node = CreateInstance<RandomBranchNode> ();

            node.rect = new Rect (pos.x, pos.y, 100, 200);
            node.name = "RND Branch";

            node.CreateInput ("Src", "Float");

            node.CreateOutput ("Branch 1", "Float");
            node.CreateOutput ("Branch 2", "Float");
            node.CreateOutput ("Branch 3", "Float");
            node.CreateOutput ("Branch 4", "Float");
            node.CreateOutput ("Branch 5", "Float");
            node.CreateOutput ("Branch 6", "Float");
            node.CreateOutput ("Branch 7", "Float");
            node.CreateOutput ("Branch 8", "Float");

            return node;
        }

        protected internal override void NodeGUI () 
        {
            Inputs[0].DisplayLayout ();

            foreach (NodeOutput output in Outputs)
                output.DisplayLayout ();
        }

        public override bool Calculate () 
        {
            List<NodeOutput> connectedOutputs = new List<NodeOutput> ();
            foreach (NodeOutput output in Outputs)
            { // Get all connected outputs to take into consideration
                output.calculationBlockade = true;
                if (output.connections.Count > 0)
                    connectedOutputs.Add (output);
            }

            if (connectedOutputs.Count > 0)
            { // Select a random branch from the connected ones and unblock it so it gets calculated
                int randomSelected = Random.Range (0, connectedOutputs.Count);
                NodeOutput selectedOutput = connectedOutputs[randomSelected];
                selectedOutput.calculationBlockade = false;
                // Just pass the previous value to the selected branch
                selectedOutput.SetValue (Inputs[0].GetValue ());
            }
            return true;
        }
    }
}

For the sake of completeness, here's the counterpart, where an input gets randomly selected to be the output:

using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using NodeEditorFramework;
using NodeEditorFramework.Utilities;

namespace NodeEditorFramework.Standard
{
    [Node (false, "Example/Random Selector")]
    public class RandomSelectorNode : Node 
    {
        public const string ID = "randomSelectorNode";
        public override string GetID { get { return ID; } }

        public override Node Create (Vector2 pos) 
        {
            RandomSelectorNode node = CreateInstance<RandomSelectorNode> ();

            node.rect = new Rect (pos.x, pos.y, 100, 200);
            node.name = "RND Selector";

            node.CreateInput ("Input 1", "Float");
            node.CreateInput ("Input 2", "Float");
            node.CreateInput ("Input 3", "Float");
            node.CreateInput ("Input 4", "Float");
            node.CreateInput ("Input 5", "Float");
            node.CreateInput ("Input 6", "Float");
            node.CreateInput ("Input 7", "Float");
            node.CreateInput ("Input 8", "Float");

            node.CreateOutput ("Output", "Float");

            return node;
        }

        protected internal override void NodeGUI () 
        {
            Outputs[0].DisplayLayout ();

            foreach (NodeInput input in Inputs)
                input.DisplayLayout ();
        }

        public override bool Calculate () 
        {
            List<NodeInput> connectedInputs = new List<NodeInput> ();
            foreach (NodeInput input in Inputs)
            { // Get all connected inputs to take into consideration
                if (input.connection != null)
                    connectedInputs.Add (input);
            }

            if (connectedInputs.Count > 0)
            { // Select a random input from the connected ones and pass it to the output
                int randomSelected = Random.Range (0, connectedInputs.Count);
                NodeInput selectedInput = connectedInputs[randomSelected];
                // Just pass the previous value to the selected branch
                Outputs[0].SetValue (selectedInput.GetValue ());
            }
            return true;
        }
    }
}

I hope that helps you with your problem!

@mio-moto
Copy link
Author

mio-moto commented Jun 14, 2016

Thanks for all your help. I worked the last days (busy weeks before that, sorry) with that and stumbled upon another odd problem:

I'm trying to plug some references from a scene into the node-graph and don't have a really good solution to that yet. I'm still working on a dialogue-system with your framework and have cases where it would be great to pre/post check some variables in the game manager. Without hard-coding certain variable-checks, it's (either me or really) hard to pre-define actions inside the node graph to get run later on.

I have an Abstract Class like this:

public abstract class AbstractCheckable : MonoBehaviour {
    public abstract bool VariableCheck();
}

And an implementation like this:

public class SomeCheckable : AbstractCheckable {
    public bool isTrue;
    public override bool VariableCheck() {
        return isTrue;
    }
}

And a node like this:

namespace NodeEditorFramework.Standard {
    [System.Serializable]
    [Node(false, "Dialogue/Variable Checker")]
    public class VariableChecker : Node {

    public class VariableChecker : Node {
        public const string ID = "variableChecker";

        public override string GetID {
            get { return ID; }
        }

        public override bool AllowRecursion {
            get { return true; }
        }

        public Node selectedNode;

        [SerializeField]
        private AbstractCheckable _abstractCheckable;

        // Use this for initialization
        public override Node Create(Vector2 pos) {
            VariableChecker node = CreateInstance<VariableChecker>();
            node.rect = new Rect(pos.x, pos.y, 200, 80);

            node.name = "Varibale Check";
            node.CreateInput("Parent", "Void");
            node.CreateOutput("true", "Void");
            node.CreateOutput("false", "Void");
            return node;
        }

        // Update is called once per frame
        protected internal override void NodeGUI() {
            GUILayout.BeginHorizontal ();
            _abstractCheckable = (AbstractCheckable) EditorGUILayout.ObjectField(_abstractCheckable, typeof(AbstractCheckable), true);
            GUILayout.EndHorizontal();
            GUILayout.BeginHorizontal();
            GUILayout.BeginVertical ();
            Inputs[0].DisplayLayout();
            GUILayout.EndVertical();
            GUILayout.BeginVertical();
            Outputs[0].DisplayLayout();
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
            GUILayout.BeginHorizontal();
            Outputs[1].DisplayLayout();
            GUILayout.EndHorizontal();
        }

        public override bool Calculate() {
            var check = false;
            if (_abstractCheckable != null) {
                Debug.Log("Actually in there.");
                check = _abstractCheckable.VariableCheck();
                selectedNode = check ? Outputs[0].connections[0].body : Outputs[1].connections[0].body;
            }
            return check;
        }
    }
}

I have a demo on a secondary device where this worked fine until I rewrote some irrelevant parts. The inspector reports a TypeMismatch, which is expected with extensions of MonoBehaviour. I'm running out of ideas and probably need to hook up (somewhow?) a singleton + some kind of bridge - or you might have a bright idea on that?

@Seneral
Copy link
Owner

Seneral commented Jun 14, 2016

It's simply because MonoBehaviours are stored in the scene whereas the canvas is stored as an asset. So it's simply not allowed to reference scene objects:(
I developed a scene saving solution to overcome this problem, you might want to check it out: 23c875b

@mio-moto
Copy link
Author

mio-moto commented Jun 14, 2016

This is in your develop-branch, right?

I have that version right now on my instance, is this the magic "save to scene" button?

How do you use the editor for a field like this: img

Edit: Also I cannot figure out how / what / where it saves that.

@Seneral
Copy link
Owner

Seneral commented Jun 14, 2016

You mean mysterious, and crappy rather than magic? ;) It's a temporary GUI, it's the functionality that counts for now:D

I don't get what it's about, you mean referencing scene objects with the object field or adding knobs with the buttons below?

@Seneral
Copy link
Owner

Seneral commented Jun 14, 2016

Also, you should not need the calculate button (if it's not something to your design that I don't know about), just make it like this in your Node GUI:

if (GUI.changed)
    NodeEditor.RecalculateFrom (this);

@mio-moto
Copy link
Author

No, I actually mean how referencing works (seems my solution works unreliably right now) and how saving to scene works.

I'm still loading my canvas like that:

var _canvas = NodeEditorFramework.NodeEditorSaveManager.LoadNodeCanvas("Assets/Plugins/Node_Editor/Resources/Saves/Boolean_Dialogue.asset", false);

Since saving to scene should make this obsolete - how do I load that now?

The calculate button is just a glorified debug-button.

@Seneral
Copy link
Owner

Seneral commented Jun 14, 2016

Ok, so there is a new API for NodeEditorSaveManager- In order to load from/to the scene, there's now SaveSceneNodeCanvas/ LoadSceneNodeCanvas;) It should be obvious, because parameters are roughly the same. Just note that currently there's no caching for canvases saved in the scene, that means you would loose it by shutting down with unsaved changes:/

@mio-moto
Copy link
Author

Interesting. Yet I am not able to load it from scene. Another thing that is probably manageable sooner or later is, that you cannot see already stored canvas.

I'm still confused by this, but loading seems to be working fine. However, the instance-check is still problematic.

@Seneral
Copy link
Owner

Seneral commented Jun 14, 2016

There is a call to GetSceneSaves or something similar, also used by the dropdown to load canvases, which might help you:)
Also, you could modify FetchSceneSaveHolder (again, or similar) to not hide the gameobject holding the saves (the line that modifies hideflags). Make sure to reload the scene an it should show:)
It's basically a set of components differentiated by a name, and saving a canvas with existing name will overwrite that canvas - pretty generic I suppse;)

@mio-moto
Copy link
Author

mio-moto commented Jun 14, 2016

I'm sorry, but there is no drop down. The Node graph I'm using is from the recent commit in the develop branch (23c875b)

Also I'm a bit confused what you mean with your second statement.

Thanks for your patience, tho.

Edit: I just checked again with the abstract-setup and it works fine, finally! There is still the missing dropdown and so on, but I'll figure that out.

I'll write down a reminder and pull-request features to delete inputs/outputs in an efficient way, which looks (for us) like this, for example: https://a.pomf.cat/hfcoqp.mp4

@Seneral
Copy link
Owner

Seneral commented Jun 14, 2016

No problem:)
The load from scene button has a bug that the dropdown immediately disappears when you click to the right of the button because the dropdown is left-aligned and automatically closes when the mouse is out of reach (which would be immediately the case when it opens out of reach obviously). That causes the dropdown to occasionally not show:/
Also, you'd obviously need atleast one save to see it, too;)

My second statement just describes a method to debug all canvases saved in the scene by showing the location where they are saved (a gameobject, hidden by default). That is if you want to debug it;)

@mio-moto
Copy link
Author

I'm still not sure where the dropdown should be. Haha:

The debugging is just to print some internal states of the hackery I'm doing with it.

@Seneral
Copy link
Owner

Seneral commented Jun 14, 2016

Ok maybe I was not too clear lol:D
It's not obvious that it's a dropdown, it's the 'Load from Scene' button which I'm referring too, it will open a dropdown containing every scene save you could load:)
Hope that helps you:) I've to quit unfortunately, where Iive it's like 23:55, so I'll answer tomorrow! :)

@mio-moto
Copy link
Author

It's 23:55 here too, you're probably German too, I guess?

Anyway, clicking the "Load from Scene" button doesn't do anything.

@Seneral
Copy link
Owner

Seneral commented Jun 14, 2016

Yep, I'm too in Germany:) I'll take a look at this tomorrow!

@Seneral
Copy link
Owner

Seneral commented Jun 15, 2016

da3d8b9 adresses this and alot of other errors from the last update:) I can post a gif about using the scene saving functionality in a few mins!

@Seneral
Copy link
Owner

Seneral commented Jun 15, 2016

Here's the scene saving functionality in a gif:
NESceneSaving
Hope it looks the same for you, as it should;)

@snarlynarwhal
Copy link

I don't have the option to Save to Scene or Load from Scene - any idea why these options might be missing for me?

capture

@Seneral
Copy link
Owner

Seneral commented Jun 27, 2016

@pi3butcher Then you're probably using the master branch.
Unfortunately due to lack of time I couldn't yet merge the develop branch with master (develop is way ahead master though and usually even more stable!):/
Sorry for that inconvenience!

Btw, please ask in the forum thread for such questions, I'll usually answer faster there and it'll also not clutter up this issue with offtopic stuff;) Not a big deal though.

@Seneral
Copy link
Owner

Seneral commented Aug 22, 2016

I suppose this has been resolved? If not, please reopen! Also, I am working on the documentation right now:)

@mio-moto
Copy link
Author

mio-moto commented Aug 22, 2016

@Seneral sure is! Very helpful all around.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants