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.

EditorWindow loses reference of ScriptableObject on Play mode

Discussion in 'Immediate Mode GUI (IMGUI)' started by Veehmot, Oct 12, 2011.

  1. Veehmot

    Veehmot

    Joined:
    Oct 26, 2008
    Posts:
    27
    I'm making an EditorWindow component. Everything works like a charm, but when I press Play, and press it again to go back to Edit mode, I found that the reference to the ScriptableObject I'm working on is lost.

    Here is a fraction of the code:

    PHP:
    // C# example:
    using UnityEngine;
    using UnityEditor;
    public class 
    MyWindow EditorWindow {

        private static 
    MyWindow window null;
        private 
    LevelMap map null;

        [
    MenuItem ("Window/Map Editor")]
        private static 
    void Init () {

                
    // Get existing open window or if none, make a new one:

            
    if (window == null) {
                
    Debug.Log("MyWindow.Init");
                    
    window = (MyWindow)EditorWindow.GetWindow (typeof (MyWindow));
                
    window.onSceneGUIFunc = new SceneView.OnSceneFunc(window.OnSceneGUI);
                
    SceneView.onSceneGUIDelegate += window.onSceneGUIFunc;
                
    window.map = (LevelMapScriptableObject.CreateInstance(typeof(LevelMap));
                
    window.map.Start();
            }
        }

        private 
    void OnGUI () {
            
    exclusiveMode EditorGUILayout.Toggle ("Exclusive Mode"exclusiveMode);
            if (
    GUILayout.Button ("Load Map")) {
                
    Debug.Log("Load Map");
                
    Reset();
                
    map.LoadTileMap();
            }
            if (
    GUILayout.Button ("Clear Map")) {
                
    Debug.Log("Clear Map");
                
    Debug.Log("map = " map);
                
    map.ClearTileMap();
            }
        }
    }


    When I press Clear Map after going to Edit mode, the reference for map becomes null. But when I create my custom window through Window/Map Editor, everything works and the reference isn't lost (that is, after I go to Play and then to Edit mode).

    Any ideas? Thanks!
     
    Last edited: Oct 12, 2011
  2. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    mark them as dirty when changing them and ensure they are assigned to something. stuff not marked as dirty will not be serialized and is gone right away.
     
    equivoque, bytewav and samana1407 like this.
  3. Veehmot

    Veehmot

    Joined:
    Oct 26, 2008
    Posts:
    27
    Hi and thanks for your answer! I tried adding these lines:

    PHP:
    EditorUtility.SetDirty(window);
    EditorUtility.SetDirty(window.map);
    To the Init function, but no luck, the object is still removed.
    But when I add

    PHP:
    EditorUtility.SetDirty(map);
    To OnGUI function, I get the following error:

    But I'm NOT removing it! Is there any way to check where has been removed? Am I missing something?

    Thanks!
     
  4. Veehmot

    Veehmot

    Joined:
    Oct 26, 2008
    Posts:
    27
    I added a trace to the OnDestroy function of my ScriptableObject and yes, it is being destroyed when I press Play.

    PHP:
    void OnDestroy() {
        
    Debug.Log("Script was destroyed");
    }
    Still I don't have any idea why is this happening.
     
  5. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    is levelmap derived from scriptable object and is it assigned to a GO thats in the scene / a prefab?

    You don't need to mark the editor dirty, it will not change or help.
     
  6. Veehmot

    Veehmot

    Joined:
    Oct 26, 2008
    Posts:
    27
    Yes it derives from a ScriptableObject. No, it isn't assiged to a GameObject. From the documentation:

    So I guess I'm doing the right thing. I'll keep trying and keep this thread updated, until I run out of chances.
     
  7. Veehmot

    Veehmot

    Joined:
    Oct 26, 2008
    Posts:
    27
    I'm making some progress. Seems like although Debug.Log prints null for LevelMap, it can still be accessed (!). I set a breakpoint on the function ClearTileMap and it was hit!



    MonoDevelop debugger report that this has a null value, but it still has members. Strange as it seems, I can still work in this class, but for some reason the multidimensional array is losing the reference (i.e. all his values are set to null). Seems like there's no problem with one-dimensional arrays.

    I declare the following multidimensional array:

    PHP:
    private string[,] test2 = new string[55];
    I create a test object on this array:

    PHP:
    test2[00] = "hello";
    In edit mode, first time:



    In edit mode, after I go to play mode:



    Thanks for your help so far!
     
    Last edited: Oct 12, 2011
  8. Veehmot

    Veehmot

    Joined:
    Oct 26, 2008
    Posts:
    27
    More progress thanks to user Zerot at #Unity3D (Freenode):

    So that solves the problem with the array being set to null, but I'm still struggling with the original problem, the reference being lost on an EditorWindow.
     
    Last edited: Oct 13, 2011
  9. Veehmot

    Veehmot

    Joined:
    Oct 26, 2008
    Posts:
    27
    I tried with a static class:

    PHP:
    using UnityEngine;
    using System.Collections;

    public class 
    TestObject MonoBehaviour {
        static public 
    string test "undefined";
        
    // Use this for initialization
        
    static public void Start () {
            
    test "test ok";
            
    Debug.Log("TestObject.Start");
        }
    }
    But the value TestObject.test is being reset after quitting Play Mode. I'm calling TestObject.Start manually during Edit Mode, and at that point TestObject.test is test ok. But as I said, it is undefined after quitting Play Mode. Any ideas why?
     
  10. Veehmot

    Veehmot

    Joined:
    Oct 26, 2008
    Posts:
    27
    I gave up.

    Seems like everything is lost after quitting Play Mode. For example, if I create an EditorWindow using the common code (found in the documentation):

    PHP:
    window = (MyWindow)EditorWindow.GetWindow (typeof (MyWindow));
    That window reference is lost after quitting Play Mode. I don't know if it's a bug, or just me missing something (probably). But I tried everything and I can't find an elegant solution.


    So my workaround was to save my map grid to a file (on entering Play Mode), and when I go back to Edit Mode (at this point all references are lost), I call the static Init function of my editor, clear all the objects, load that temporal file and delete it. It's like reopening the EditorWindow again. Or maybe my own way of serializing.

    Hopefully someone will read this thread and give me a hand, I will not lose hope to understand this!
    Here is a screenshot of how it looks like right now:



    Thanks for reading!
     
    Last edited: Oct 18, 2011
    DKrayl likes this.
  11. pjcamp

    pjcamp

    Joined:
    Feb 19, 2012
    Posts:
    1
    Not sure if this helps but I just saw this today...

    I found the following blog to be helpful: http://buchhofer.com/2010/10/unity-toying-with-scriptable-objects/

    In this blog it says to basically save the scriptable object as an asset. So lets say you have a class called EditorData that derives ScriptableObject and an object of type EditorData call myData, you can do this in the Awake function of your editor script:

    myData = AssetDatabase.LoadAssetAtPath("Assets/Editor/myData.asset", EditorData);
    if(!myData){
    myData = CreateInstance(EditorData);
    AssetDatabase.CreateAsset(myData, "Assets/Editor/myData.asset");
    }


    This worked for me.

    Edit:Also in the onGUI function, make sure to call the SetDirty function on the target
     
    Last edited: Feb 19, 2012
  12. yoyo

    yoyo

    Joined:
    Apr 16, 2010
    Posts:
    112
    The basic issue is that the EditorWindow gets destroyed and re-constructed on switching in and out of play mode, just like any other custom component. This is unfortunate, because if my window has any internal state that I want to preserve out of edit mode and into play mode (or vice-versa) I'm out of luck.

    Saving to a file and reloading is a workaround, but in my case I have a list of objects of unknown type, so I can't guarantee that they can be serialized to a file. I tried DontDestroyOnLoad on my EditorWindow, but it didn't help. Pity.
     
    zwcloud and Maeslezo like this.
  13. nuverian

    nuverian

    Joined:
    Oct 3, 2011
    Posts:
    2,084
    For me it goes like this:

    I open my EditorWindow which is used to alter some variables for a Monobehaviour script (kind like an inspector use). The changes are working.
    I then enter and exit PlayMode. The EditorWindow seems like it has lost reference to the script. Any changes I make to the TextField for example, no longer alter the variable on the script.. I've been stragling for days. Any clues about that yet?
     
  14. dkozar

    dkozar

    Joined:
    Nov 30, 2009
    Posts:
    1,410
    I guess you've already read it, but in this blog post the UT engineer explains how to deal with Edit/Play mode changes and EditorWindow:

    http://blogs.unity3d.com/2012/10/25/unity-serialization/.

    However, this approach doesn't seem to work with objects in game object hierarchy. You should try the other approach.

    ps As you can see, I asked a few questions in blog comments - regarding custom inspectors - but they are still not answered.
     
  15. dkozar

    dkozar

    Joined:
    Nov 30, 2009
    Posts:
    1,410
    What I found out is that saving instance IDs, rather than object references, is the way to go when dealing with design/play mode switching.

    Since Unity re-instantiates objects, references are lost, but numbers are not changed.

    So, instead of saving references, save instance IDs (unique integers):

    Code (csharp):
    1. private int _instanceId;
    2.  
    3. _instanceId = Object.GetInstanceID();
    ... and when need to reference the same object:

    Code (csharp):
    1. var myObject = EditorUtility.InstanceIDToObject(_instanceId);
     
    vamidi16, zwcloud, Maeslezo and 2 others like this.
  16. dkozar

    dkozar

    Joined:
    Nov 30, 2009
    Posts:
    1,410
    Additionally, there is one not-much-documented feature: Unity exposes a few events in the subscribe/unsubscribe fashion when in editor mode.

    The following event is particularly interesting since it could be used to detect the design/play mode switching:

    Code (csharp):
    1. EditorApplication.playmodeStateChanged += YourPlaymodeStateChangedHandler;
    2. // ....
    3. private void YourPlaymodeStateChangedHandler() {
    4.     Debug.Log('Play mode: ' + EditorApplication.isPlaying)
    5. }
    This seems to be more elegant than constantly checking the play mode state from your Inspector methods, like I've been previously doing...
     
    zwcloud likes this.
  17. dkozar

    dkozar

    Joined:
    Nov 30, 2009
    Posts:
    1,410
    I've stuck with the following issue:

    I'm caching all the transform parent-child relationship changes done in play mode, and reapplying it after the play mode is stopped. It works great in all cases but one: I cannot reapply game objects added to a scene while in play mode.

    Unity removes all the game objects added in play mode the moment the play mode is stopped, so when looking up for added transform using EditorUtility.InstanceIDToObject() I get null.

    Is there a solution to this?

    Part of my code:
    Code (csharp):
    1. /// <summary>
    2. /// The dictionary of parent-child transform relations (parent transform ID -> child transform ID)
    3. /// each time the new game object is added to the scene, I'm caching the transform relation here
    4. /// </summary>
    5. private Dictionary<string, string> _additions = new Dictionary<string, string>();
    6.  
    7. /// <summary>
    8. /// Caches the transform
    9. /// </summary>
    10. private void CacheTransform(Transform transform)
    11. {
    12.     _additions[transform.parent.GetInstanceID()] = transform.GetInstanceID();
    13. }
    14.  
    15. /// <summary>
    16. /// Subscribing to playmodeStateChanged
    17. /// </summary>
    18. private void Initialize()
    19. {
    20.     EditorApplication.playmodeStateChanged += PlayModeStateChangedHandler;
    21. }
    22.  
    23. /// <summary>
    24. /// Checking if play mode stopped and re-applying changes made in play mode
    25. /// </summary>
    26. private void PlayModeStateChangedHandler() {
    27.     if (!EditorApplication.isPlaying) { // play mode stopped
    28.         foreach (string parentId in _additions.Keys) { // iterate through cached transforms
    29.             string childId = _additions[parentId];
    30.             Transform child = EditorUtility.InstanceIDToObject(childId) as Transform;
    31.             // -> getting null as child!!!
    32.             // .....
    33.         }
    34.     }  
    35. }
     
    Last edited: Nov 11, 2012
  18. Techdread

    Techdread

    Joined:
    Sep 10, 2010
    Posts:
    25
    There is a hack I have seen done by Antares Universe http://forum.unity3d.com/threads/89467-Antares-Universe-visual-editor-scripting

    Were he implements his own editor window were you can Play/Stop on this window, which means when the user says stop you can save out the settings and then stop the player which is the same thing as what your trying to do.

    Ask Neodrop he's a cool guy.
     
  19. dkozar

    dkozar

    Joined:
    Nov 30, 2009
    Posts:
    1,410
    Oh, thanks... :) I'll ask him, but I'm really interested in the solution of using Unity's play/stop button.

    I've been thinking about the solution to my problem; I guess I would have to cache things different ways, and recreate added objects and then add cached scripts etc.
     
  20. Neodrop

    Neodrop

    Joined:
    Oct 24, 2008
    Posts:
    1,359
    You can change
    private Dictionary<string, string> _additions

    to

    private Dictionary<Transform, Transform> _additions

    and save the links to this objects directly. All will works fine (except dynamics objects).
     
  21. studiofourway

    studiofourway

    Joined:
    Jan 19, 2015
    Posts:
    3
    Hey I was having the same problem, every time I hit play the objects get destroyed, i think is just normal behaviour, cause I created a new class called "TestI" wich had the exact same properties of my original class "BlockSet", and it called OnDestroy as soon as I pressed Play but it did Not cause a "Missing behaviour" warning and it wasn't null either, the problem with "BlockSet" was that the File Name was "Block Set" with a space.
    I know this trhead is like 5 years old but I think someone will find this usefull :)
     
  22. Sarah_Lee

    Sarah_Lee

    Joined:
    Jul 30, 2015
    Posts:
    6
    I had the same problem. It turned out I missed a small detail, setting the HideFlags to avoid GC collecting since SO does not have a root. Following example from (http://blogs.unity3d.com/2012/10/25/unity-serialization/) ...

    Code (CSharp):
    1. [Serializable]
    2. public class NestedClass : ScriptableObject
    3. {
    4.     [SerializeField]
    5.     private float m_StructFloat;
    6.  
    7.     public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; }
    8.  
    9.     public void OnGUI()
    10.     {
    11.         m_StructFloat = EditorGUILayout.FloatField("Float", m_StructFloat);
    12.     }
    13. }
     
  23. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    381
    I found I was running into a similar problem and it looked to me like that the EditorUtility.SetDirty(target) wasn't working as expected.

    I solved the problem by usng: EditorSceneManager.MarkSceneDirty(target);
     
  24. Cobo3

    Cobo3

    Joined:
    Jun 16, 2013
    Posts:
    65
    I also was running into a similar problem and figured out a solution.

    In my case, I had a ScriptableObject asset which had few other ScriptableObject references, but those weren't saved as an asset, so they were being decoupled upon entering/exiting playmode and closing the project.

    I solved it by setting the hideFlags of those references to HideAndDontSave , as this post explains somewhere near the middle.
    Hope this helps :)
     
    ilmario, Bastienre4 and aybeone like this.
  25. rivenblades

    rivenblades

    Joined:
    Aug 13, 2015
    Posts:
    1
    i have the same problem. i removed the "obj = GetComponent<(GameObject)>(); in the awake function and this solved the problem
     
  26. RaviVR

    RaviVR

    Joined:
    Feb 14, 2019
    Posts:
    1
    I was also facing the same problem. I have written all initialization of object in OnEnable() function.
    Write Init() function code in to OnEnable() function.

    void OnEnable()
    {
    //write your initialization of fields and objects.
    }

    The problem is fixed.
     
  27. Pawel_Legowski

    Pawel_Legowski

    Joined:
    Oct 28, 2015
    Posts:
    4
    I was facing the same issue for last few days and finally I found working solution.

    If you want ScriptableObject that is not saved as an Asset to persist trough project reload/play mode change you need to set

    myScriptableObject.hideFlags = HideFlags.DontSave;

    After instantiating that ScriptableObject, so the Editor knows that this asset is not going to be saved but should not be destroyed on reload
     
    Hozgen90 and DA-VANCI like this.