Seneral / Node_Editor_Framework

A flexible and modular Node Editor Framework for creating node based displays and editors in Unity
https://nodeeditor.seneral.dev
MIT License
2k stars 415 forks source link

Loading canvas based on Selection.activeTransform? #202

Closed TimOgden closed 1 year ago

TimOgden commented 2 years ago

I want to extend the Editor functionality such that if I have a manager on a gameobject that contains a specific canvas (or instance of a canvas), then if I click on the gameobject, the Node Editor window should update and load it so you can see the nodes activating in real-time.

In my Manager class, I added these functions:

    public void AssureCanvas()
    {
        if (behaviorTree == null)
            throw new UnityException("No canvas specified to calculate on " + name + "!");
    }

    private void SetupTree()
    {
        AssureCanvas();
        if(Selection.activeTransform == transform)
        {
            // NodeEditor.checkInit(true);
            NodeEditor.curNodeCanvas = behaviorTree;
        } else
        {
            NodeEditor.checkInit(false);
        }

        behaviorTree.Validate();
    }

where SetupTree is being called every .2 seconds within a coroutine, and behaviorTree is the canvas given to the Manager component of my game object. Also, I commented out the NodeEditor.checkInit(true); line because it was throwing an exception since I can't call a GUI function from outside the GUI loop.

Nothing is happening here, should I extend the NodeEditor instead?

_Originally posted by @Seneral in https://github.com/Seneral/Node_Editor_Framework/issues/124#issuecomment-287489551_

Seneral commented 2 years ago

NodeEditor window uses NodeEditorUserCache to store the currently open instance, so you'd have to access that. Take control over it and you directly edit what the opened canvas is and what it's save and cache behaviour is. You should be able to get the currently open window (or open one) with GetWindow(); and access the cache as the canvasCache member. All following edits are called on this object.

So, if these canvases live in the scene, it could work nicely. Set useCache to false and call SetCanvas, and the window should switch to editing it next time it redraws (which will set the curNodeCanvas, that is really only for the time frame when it is drawn, e.g. multiple windows can draw one after another, setting their canvas as current while drawing.).

If your canvases are assets (so not instances), you do not want to edit them directly, that might at best result in inconsistent files, and at worst break completely due to how ScriptableObjects are handled. Generally when you load a canvas from assets, it copies it into an instance in memory to prevent weirdness from unity. So if they are assets, set(keep) useCache to true and call LoadNodeCanvas(string path) on it. If you have the reference, you can either get the path from it's savePath property (should be correct afaik), or you can call SetCanvas with CreateWorkingCopy(nodeCanvas) called on it before. In both cases you also need to save before switching away from it, since you'd be working on copies, not the original. You can of course just try setting it with SetCanvas without the working copy, but don't expect it to work, or if it does, that it's reliable.

TimOgden commented 2 years ago

My canvases do live in the scene so it should be a simple solution. I'm a little confused how to set useCache to false when it is a private field, I've tried setting the 'CACHE' custom directive in the player settings, but I am still getting a exception: Couldn't add object to asset file because the MonoBehaviour 'simple' is already an asset at 'Assets/MyTrees/simple.asset'!. It seems like if useCache was false, this wouldn't be an issue, but it gets set to true in the constructor and I can't modify it from my SelectionLoader script

Seneral commented 2 years ago

My bad, didn't notice that. Been a while since I wrote that code. You can set the variable nodeCanvas.livesInScene or hardcode the property nodeCanvas.allowSceneSaveOnly to at least influence WHERE it will store the cache working copy (here, in the scene, instead of as an asset). After you call SetCanvas it SHOULD update the source nodeCanvas.savePath to "SCENE/lastSession" or something, but it should otherwise directly work on the canvas. It will also now have a entry in the scene of that canvas, so it will automatically load a COPY of that canvas, stored as "SCENE/lastSession", the next time you open the editor window up. To prevent this, usually useCache should be set, but ofc the interface wasn't designed with changing that without modification in mind unfortunately. Alternatively, you can call "userCache.DeleteCache" or directly "NodeEditorSaveManager.DeleteSceneNodeCanvas ("lastSession")". Hopefully that works. Do note I work off of memory here.

TimOgden commented 1 year ago

Coming back to this issue, I've found that userCache.LoadNodeCanvas is the method I was looking for. I've included my script which will find the corresponding node canvas attached to my gameobject and set the displayed node canvas to that. Note that it's specific to my project, so a lot of it is unnecessary, but it's enough to understand I think.

Here is a pastebin link if the formatting is bad: https://pastebin.com/F1i8T1Z3

using UnityEngine;
using UnityEditor;
using NodeEditorFramework;
using NodeEditorFramework.Utilities;

namespace NodeEditorFramework.Standard
{
    [ExecuteInEditMode]
    public class SelectionLoader : MonoBehaviour
    {
        [HideInInspector]
        public Transform activeTree;
        public NodeEditorUserCache canvasCache;

        // Update is called once per frame
        void Update()
        {
            Transform trans = Selection.activeTransform;
            if (trans != null)
            {
                if (trans.TryGetComponent<BehaviorTreeManager>(out BehaviorTreeManager manager))
                {
                    if (NodeEditor.curNodeCanvas != null && manager.behaviorTree != null)
                    {
                        string loadedCanvasName = NodeEditor.curNodeCanvas.saveName;

                        int index = manager.behaviorTree.name.IndexOf("(Clone)");
                        string btName = null;
                        if (index > -1)
                            btName = manager.behaviorTree.name.Substring(0, index);
                        else
                            btName = manager.behaviorTree.name;

                        if(loadedCanvasName.Equals(btName))
                        {

                            activeTree = trans;
                            // update statuses
                            ((BehaviorTreeCanvas)NodeEditor.curNodeCanvas).rootNode.RecursivelyCopyStatus(manager.behaviorTree.rootNode);
                        } else
                        {
                            // set canvas to manager.behaviorTree
                            if (canvasCache != null)
                            {
                                canvasCache.SaveCache();
                                canvasCache.LoadNodeCanvas("Assets/MyTrees/" + btName + ".asset");
                            }
                        }
                    }

                }
            }
        }
    }
}