Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Custom Editor not saving changes

Discussion in 'Immediate Mode GUI (IMGUI)' started by afonsolfm, Aug 7, 2016.

  1. afonsolfm

    afonsolfm

    Joined:
    Jul 11, 2016
    Posts:
    11
    I have three scripts: Singleton<T>, MapManager and MapManagerEditor

    When I make changes in the *MapManager* script component, after I click to go to other scene and then come back, the values are reset!

    Here is a small example:

    Singleton
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour {
    5.  
    6.     public static T instance { get; private set; }
    7.  
    8.     protected virtual void Awake()
    9.     {
    10.         if (instance == null)
    11.             instance = this as T;
    12.         else
    13.             Destroy(gameObject);
    14.     }
    15.  
    16.     protected virtual void OnDisable()
    17.     {
    18.         instance = null;
    19.     }
    20. }
    21.  
    }

    MapManager
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [System.Serializable]
    5. public class MapManager : Singleton<MapManager> {
    6.  
    7.     [System.NonSerialized]
    8.     public int lol = 0;
    9. }
    MapManagerEditor
    Code (CSharp):
    1. using UnityEditor;
    2. using System.Collections;
    3. using UnityEditor.SceneManagement;
    4.  
    5. [CustomEditor(typeof(MapManager))]
    6. public class MapManagerEditor : Editor
    7. {
    8.  
    9.     public override void OnInspectorGUI()
    10.     {
    11.         DrawDefaultInspector();
    12.         MapManager mapManager = (MapManager)target;
    13.  
    14.         mapManager.lol = EditorGUILayout.IntField("lol", mapManager.lol);
    15. }
    16. }
    Any idea how to fix this?
     
  2. afonsolfm

    afonsolfm

    Joined:
    Jul 11, 2016
    Posts:
    11
    Fixed the problem, for anyone who has it, here is the real solution:

    Use
    Code (CSharp):
    1. [HideInInspector]
    for every variable you want to change (it also serializes it).

    Use
    Code (CSharp):
    1. [System.Serializable]
    in every class/struct that you are using with your variables.

    Finally add this code on the bottom of OnInspectorGUI

    Code (CSharp):
    1.  
    2. if (GUI.changed)
    3.         {
    4.             EditorUtility.SetDirty(castedTarget);
    5.             EditorSceneManager.MarkSceneDirty(castedTarget.gameObject.scene);
    6.         }
     
  3. MrLucid72

    MrLucid72

    Joined:
    Jan 12, 2016
    Posts:
    962
    FINALLY! This is the 10th article I've read that finally does what it says. You rock, good sir.

    @ Unity, please update your docs on this -__-
     
  4. The_Pied_Shadow

    The_Pied_Shadow

    Joined:
    Jul 25, 2018
    Posts:
    12
    I wanted to add to this. This helped me track down my problem however, the only thing of your solutions I needed to do was add the if(GUI.changed) snippet to my OnInspectorGUI method. However I also had the variables in my class set with default accesors for example
    Code (CSharp):
    1. public float boo { get; set; }
    however I needed to remove those so it was just
    Code (CSharp):
    1. public float boo;
    for some reason they hindered the process. So with those changes alone it solved my problem in case anyone tried the above and still have the issue it may be accesors.
     
    dday1987_unity likes this.
  5. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    For future generations... this is not a good way to get your editors to serialize. I struggled with custom editors for a long time too and did a bunch of hacky garbage just to get things kind of ok. But its not this much work once you know what to focus on.

    The key is to only manipulate SerializedProperties, don't ever operate on the target's data! Here's a simple example...

    Code (csharp):
    1.  
    2. public override void OnInspectorGUI()
    3. {
    4.      //do this first to make sure you have the latest version
    5.      serializedObject.Update();
    6.      
    7.      //for each property you want to draw ....
    8.      EditorGUILayout.PropertyField(serializedObject.FindProperty("boo"));
    9.  
    10.      //if you need to do something cute like use a different input type you can do this kind of thing...
    11.      SerializedProperty specialProp = serializedObject.FindProperty("myFloatThatPretendsItsAnInt");
    12.      specialProp.floatValue = EditorGUILayout.IntField(specialProp.floatValue as int) as float;
    13.  
    14.      //do this last!  it will loop over the properties on your object and apply any it needs to, no if necessary!
    15.      serializedObject.ApplyModifiedProperties();
    16. }
    17.  
    That's all you need.
     
  6. dcarnelutti

    dcarnelutti

    Joined:
    Jul 30, 2018
    Posts:
    4
    Have you tried this with a 2 dimensions array?

    I have a week looking about this and can't find anything.
     
  7. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @dcarnelutti

    Your question doesn't have much to do with topic and it is also bit of a necro post.

    I bet that because you can't serialize a 2D array in standard Inspector, you won't see it in serialized object for that class either. I think you will just see null if you try to do FindProperty("myTwoDeeArray").

    You could try to do a typical Serializable class instead that looks like two dimensional array (class with a list of arrays) and then draw that with your Custom Inspector + serializable object.
     
  8. legoblaster1234

    legoblaster1234

    Joined:
    Aug 7, 2017
    Posts:
    27

    What is "castedTarget"? You don't define it in any of your code. What should it be set to?
     
    Kronus likes this.
  9. SixofSwords

    SixofSwords

    Joined:
    Sep 28, 2017
    Posts:
    1
    This should be the first answer, i ran into this problem and this is the easiest/most elegant solution. if anyone runs into this, try this first.
     
    brownboot67 likes this.
  10. Mithrandir01

    Mithrandir01

    Joined:
    Jan 22, 2020
    Posts:
    3
    Last edited: Jan 29, 2021
    brownboot67 likes this.
  11. dginovker

    dginovker

    Joined:
    Sep 25, 2020
    Posts:
    8
    If anyone else is going insane because this isn't working, keep in mind that he uses PropertyField.

    Code (csharp):
    1. EditorGUILayout.PropertyField(serializedObject.FindProperty("boo"));
    For some reason, IntField or any others wouldn't work for me. If you want a label beside the input field, do it like:

    Code (csharp):
    1. EditorGUILayout.PropertyField(serializedObject.FindProperty("myInt"), new GUIContent("Set myInt: "));
     
  12. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    IntField and the other type specific fields return their corresponding type. So you have to do:

    Code (csharp):
    1. myIntProp.intValue = EditorGUILayout.IntField(myIntProp.intValue);
    And you are correct there are lots of optional params for labels and styles and so on, you can read the docs if you want to sort all that out.
     
  13. rcsordas

    rcsordas

    Joined:
    May 20, 2016
    Posts:
    2
    Hey guys, I'm having some difficulty with this.
    I managed to get everything working, but after writing something in the Editor Window and hitting Enter or Tab, the value I've just tiped is erased.

    My functions for loading as saving are working fine.
    I think the problem is on the OnGUI function

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor;
    5. using System.IO;
    6.  
    7. public class LocalizedTextEditor : EditorWindow
    8. {
    9.    
    10.     public LocalizationData localizationData;
    11.     Vector2 scrollPosition = Vector2.zero;
    12.  
    13.     [MenuItem("Window/Localized Text Editor")]
    14.     static void Init()
    15.     {
    16.        
    17.         EditorWindow.GetWindow(typeof(LocalizedTextEditor)).Show();
    18.     }
    19.  
    20.     private void OnGUI()
    21.     {
    22.         scrollPosition = GUILayout.BeginScrollView(scrollPosition, true, true);
    23.        
    24.        
    25.      
    26.  
    27.      
    28.         if (localizationData != null)
    29.         {
    30.  
    31.             SerializedObject serializedObject = new SerializedObject(this);
    32.             serializedObject.Update();
    33.             SerializedProperty serializedProperty = serializedObject.FindProperty("localizationData");
    34.             EditorGUILayout.PropertyField(serializedProperty);
    35.             serializedObject.ApplyModifiedProperties();
    36.  
    37.             if (GUILayout.Button("Save data"))
    38.             {
    39.                 SaveGameData();
    40.             }
    41.         }
    42.  
    43.         if (GUILayout.Button("Load data"))
    44.         {
    45.             LoadGameData();
    46.         }
    47.  
    48.         if (GUILayout.Button("Create new data"))
    49.         {
    50.             CreateNewData();
    51.         }
    52.        
    53.         GUILayout.EndScrollView();
    54.     }
    55.  
    56.     private void LoadGameData()
    57.     {
    58.         string filePath = EditorUtility.OpenFilePanel("Select localization data file", Application.streamingAssetsPath, "json");
    59.  
    60.         if (!string.IsNullOrEmpty(filePath))
    61.         {
    62.             string dataAsJson = File.ReadAllText(filePath);
    63.  
    64.             localizationData = JsonUtility.FromJson<LocalizationData>(dataAsJson);
    65.         }
    66.     }
    67.  
    68.     private void SaveGameData()
    69.     {
    70.         string filePath = EditorUtility.SaveFilePanel("Save localization data file", Application.streamingAssetsPath, "", "json");
    71.  
    72.         if (!string.IsNullOrEmpty(filePath))
    73.         {
    74.             string dataAsJson = JsonUtility.ToJson(localizationData);
    75.             File.WriteAllText(filePath, dataAsJson);
    76.         }
    77.     }
    78.  
    79.     private void CreateNewData()
    80.     {
    81.         localizationData = new LocalizationData();
    82.     }
    83.  
    84. }



    Code (CSharp):
    1. [System.Serializable]
    2. public class LocalizationData
    3. {
    4.     public LocalizationItem[] items;
    5. }
    6.  
    7. [System.Serializable]
    8. public class LocalizationItem
    9. {
    10.     public string key;
    11.     public string value;
    12. }
     
  14. rcsordas

    rcsordas

    Joined:
    May 20, 2016
    Posts:
    2
    Here are the screenshots
    I typed test for the first field
    And after clicking on the other field, or hitting Tab / Enter it is erased

    upload_2021-5-12_17-39-36.png



    upload_2021-5-12_17-39-51.png
     
  15. davidakaxellos

    davidakaxellos

    Joined:
    Jun 8, 2019
    Posts:
    2

    THANK YOU!!! I tried for hours and didn't get it to work the right way. That was my last attempt and after I changed my Code to your Soloution it worked flawlessly.

    Sorry for bad england.
     
    CSEliot likes this.
  16. GVSV

    GVSV

    Joined:
    Dec 18, 2020
    Posts:
    6
    Maybe this is a bad idea, but I managed to get this to work:
    Code (CSharp):
    1.  
    2. public static void CopyFromObject(this SerializedObject serializedObject, T target)
    3.     where T : notnull, UnityEngine.Object
    4. {
    5.     var it = new SerializedObject(target).GetIterator();
    6.     it.Next(true);
    7.     while (it.Next(false))
    8.     {
    9.         serializedObject.CopyFromSerializedProperty(it);
    10.     }
    11.  
    12.     serializedObject.ApplyModifiedProperties();
    13. }
    14.  
    The nice thing about this is that it lets me edit the
    target
    object directly, then save those changes back into the
    serializedObject
    without having to go through all the standard
    SerializedObject
    hoops.
     
    Last edited: Jul 7, 2022
  17. B33bo_Astro

    B33bo_Astro

    Joined:
    May 10, 2020
    Posts:
    4
    I think I love you <3
     
    umutcanfns likes this.
  18. lgarczyn

    lgarczyn

    Joined:
    Nov 23, 2014
    Posts:
    68
    Breaking news!

    After hours spent debugging, t appears that if your SerializedProperty goes out of scope, the change is lost too.

    In a custom editor, you should either store the serializedProperty on OnEnable, or call ApplyModifiedProperties before the property goes out of scope.

    TLDR: If you have a function in your editor that uses FindProperty and modifies said property, it should call ApplyModifiedProperties itself
     
  19. ramostim7416

    ramostim7416

    Joined:
    Nov 21, 2021
    Posts:
    1
    I ran into this problem and for my specific case I used this code to fix my problem.
    Code (CSharp):
    1. [HideInInspector]
    2. [SerializeField] GameObject partSys;
    I needed the GameObject to be Serialized and I needed it to be hidden in the inspector for organizational sake.
    It's simple but it's what worked for me.
     
  20. CSEliot

    CSEliot

    Joined:
    May 9, 2014
    Posts:
    33
    My particular issue is that i was maintaining a reference for 'baseClassSerializedObject' and calling
    serializedObject.Update()
    DrawPropertiesExcluding('baseClassSerializedObject', ...)
    serializedObject.ApplyModifiedProperties()

    I should've either been passed serializedObjectr into the draw method OR called update and apply on the baseclassSO.

    Hope this helps!
     
  21. Deadshot420

    Deadshot420

    Joined:
    Apr 27, 2021
    Posts:
    1
    Just in case someone is still reading this post and does not find a proper solution. I tried out alot of the suggested solutions but none of them worked in my case.

    Just doing serializedObject.Update() at the start of OnInspectorGui() and serializedObject.ApplyModifiedProperties() at the end did not work in my case, tried out alot. Finally the easiest way for me was to mark the target as dirty at the end of OnInspectorGui().

    Here is an example from my code:

    Code (CSharp):
    1. [CustomEditor(typeof(BattleUnitPreset))]
    2. public class BattleUnitPresetEditor : Editor
    3. {
    4.     private BattleUnitPreset battleUnitPreset { get { return target as BattleUnitPreset; } }
    5.  
    6.     public override void OnInspectorGUI()
    7.     {
    8.         serializedObject.Update();
    9.  
    10.         // All Custom Editor Stuff    
    11.  
    12.         serializedObject.ApplyModifiedProperties();
    13.  
    14.         EditorUtility.SetDirty(battleUnitPreset);
    15.     }
    16. }
    I did not try out if removing serializedObject.Update(); and serializedObject.ApplyModifiedProperties(); would change anything, but i would rather keep it for safety.