Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by LunaTrap · Nov 15, 2017 at 09:25 AM · inspectoreditor-scriptingserializationdictionaryserializable

Help with editor serialization

Hello, guys, my problem is that I have a enemies database using a scriptable object. The database is a dictionary and since unity does not serialize dictionaries, then I'm implementing ISerializationCallbackReceiver to serialize the dictionary, very simple using the exact same method the documentation about this interface tells you and it works....until I close the editor, look:

This is after changing the dictionary values which are split between 2 normal lists to use for serialization.

alt text

Here you can see that List<string> ED_Keys and List<EnemyType> ED_Valuesare being populated correctly with the dictionary contents when OnBeforeSerialize() is called. and yes they are set to [SerializeField] so they should be serialized so when`OnAfterDeserialize()` is called the dictionary will be populated with its values again.

But once I come back, then List<string> ED_Keys and List<EnemyType> ED_Values comeback to their former state and dont serialized.

Any help, please?

Source Code:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEditor;
 using UnityEngine.Serialization;
 using UnityEngine;
 using System.Linq;
 
 public class EnemiesDataBase : ScriptableObject, ISerializationCallbackReceiver
 {
     [SerializeField]
     public List<string> ED_Keys = new List<string>();
     [SerializeField]
     public List<EnemyType> ED_Values = new List<EnemyType>();
 
     private Dictionary<string, EnemyType> enemies = new Dictionary<string, EnemyType>();
     /// <summary>
     /// 
     /// </summary>
     public Dictionary<string, EnemyType> Enemies
     {
         get { return enemies; }
     }
 
     public GameObject RetrieveEnemyReference(string enemyName, int level)
     {
         var enemy = new EnemyType();
         if(enemies.TryGetValue(enemyName, out enemy))
         {
             for (int i = 0; i < enemy.Levels.Count; i++)
             {
                 if(i == level)
                 {
                     return enemy.Levels[i].gameObject;
                 }
             }
         }
         else
         {
             return null;
         }
         return null;
     }
 
     public void AddEnemyType(string enemyName)
     {
         enemies.Add(enemyName, new EnemyType());
     }
 
     public void RemoveEnemyType(string name)
     {
         enemies.Remove(name);
     }
 
     public void AddEnemyReference(string name)
     {
         enemies[name].Levels.Add(null);
     }
 
     public void AddEnemyReference(string name, GameObject enemy)
     {
         enemies[name].Levels.Add(enemy);
     }
 
     public void RemoveEnemyReference(string name, int level)
     {
         enemies[name].Levels.RemoveAt(level);
 
     }
 
     public void OnBeforeSerialize()
     {
         ED_Keys.Clear();
         ED_Values.Clear();
         foreach (var item in enemies)
         {
             ED_Keys.Add(item.Key);
             ED_Values.Add(item.Value);
         }
     }
 
     public void OnAfterDeserialize()
     {
         enemies.Clear();
         for (int i = 0; i < ED_Keys.Count; i++)
         {
             enemies.Add(ED_Keys[i], ED_Values[i]);
         }
     }
 }
 
 [System.Serializable]
 public class EnemyType
 {
     [SerializeField]
     private List<GameObject> levels = new List<GameObject>();
 
     public List<GameObject> Levels
     {
         get { return levels; }
     }
 }


Source code for the custom inspector used to add and remove enemies to the dictionary of enemies:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEditor;
 using System.Linq;
 
 [CustomEditor(typeof(EnemiesDataBase))]
 public class Inspector_EnemiesDataBase : Editor
 {
     EnemiesDataBase dataBaseScript;
     int enemiesCount;
 
     Vector2 scrollPos = new Vector2();
     List<bool> foldOuts = new List<bool>();
     string inputName;
 
     private void OnEnable()
     {
         dataBaseScript = (EnemiesDataBase)target;
         enemiesCount = dataBaseScript.Enemies.Count;
         foldOuts = new List<bool>();
     }
 
     public override void OnInspectorGUI()
     {
         serializedObject.UpdateIfRequiredOrScript();
         EditorGUILayout.BeginVertical();
         EditorGUILayout.BeginHorizontal();
         inputName = EditorGUILayout.TextField("New Type Name", inputName);
         if (GUILayout.Button("Add", GUILayout.Width(50)))
         {
             if (dataBaseScript.Enemies.ContainsKey(inputName))
             {
                 if (EditorUtility.DisplayDialog("Duplicate", "An enemy type with the name " + inputName + " already exist.", "Ok"))
                 {
 
                 }
             }
             else
             {
                 dataBaseScript.AddEnemyType(inputName);
                 enemiesCount++;
                 foldOuts.Add(true);
                 if (dataBaseScript.Enemies[inputName].Levels.Count <= 0)
                 {
                     dataBaseScript.AddEnemyReference(inputName);
                 }
             }
         }
         EditorGUILayout.EndHorizontal();
         while (foldOuts.Count < enemiesCount)
         {
             foldOuts.Add(false);
         }
         EditorGUILayout.BeginHorizontal();
         //Code kept for example on how to get a gui element to the right.
         //GUILayout.Space(EditorGUIUtility.currentViewWidth - 40);
         if (GUILayout.Button("⤴", GUILayout.Width(20)))
         {
             for (int i = 0; i < foldOuts.Count; i++)
             {
                 foldOuts[i] = false;
             }
         }
         if (GUILayout.Button("⤵", GUILayout.Width(20)))
         {
             for (int i = 0; i < foldOuts.Count; i++)
             {
                 foldOuts[i] = true;
             }
         }
         EditorGUILayout.EndHorizontal();
         scrollPos = EditorGUILayout.BeginScrollView(scrollPos, "box"/*, GUILayout.Height(200)*/);
         if (enemiesCount > 0)
         {
             int i = 0;
             foreach (var key in dataBaseScript.Enemies.Keys.ToList())
             {
                 EditorGUILayout.BeginHorizontal();
                 if (GUILayout.Button("", "OL Minus", GUILayout.Width(20)))
                 {
                     if (EditorUtility.DisplayDialog("Delete Enemy Type", "Are you sure you want to delete this enemy type?", "Delete", "Cancel"))
                     {
                         dataBaseScript.RemoveEnemyType(key);
                         enemiesCount--;
                         foldOuts.RemoveAt(i);
                         continue;
                     }
                 }
                 if (dataBaseScript.Enemies.Count > 0)
                 {
                     if (dataBaseScript.Enemies[key] != null)
                     {
                         GUILayout.Space(10);
                         foldOuts[i] = EditorGUILayout.Foldout(foldOuts[i], key, true);
                         if (GUILayout.Button("Add Level", GUILayout.Width(75)))
                         {
                             dataBaseScript.AddEnemyReference(key, null);
                         }
                     }
                 }
                 EditorGUILayout.EndHorizontal();
                 if (dataBaseScript.Enemies.Count > 0)
                 {
                     if (foldOuts[i] && dataBaseScript.Enemies[key] != null)
                     {
                         EditorGUILayout.BeginHorizontal();
                         GUILayout.Space(20);
                         EditorGUILayout.BeginVertical("box");
                         if (dataBaseScript.Enemies[key].Levels.Count < 2)
                         {
                             EditorGUI.BeginDisabledGroup(true);
                             GUILayout.Button("", "OL Minus", GUILayout.Width(20));
                             EditorGUI.EndDisabledGroup();
                         }
                         else
                         {
                             if (GUILayout.Button("", "OL Minus", GUILayout.Width(20)))
                             {
                                 dataBaseScript.RemoveEnemyReference(key, dataBaseScript.Enemies[key].Levels.Count - 1);
                             }
                         }
                         for (int a = 0; a < dataBaseScript.Enemies[key].Levels.Count; a++)
                         {
 
                             if (dataBaseScript.Enemies.Count > 0)
                             {
                                 dataBaseScript.Enemies[key].Levels[a] = (GameObject)EditorGUILayout.ObjectField("Lvl " + (a + 1).ToString(), dataBaseScript.Enemies[key].Levels[a], typeof(GameObject), false);
                             }
                         }
                         EditorGUILayout.EndVertical();
                         EditorGUILayout.EndHorizontal();
                     }
                 }
                 i++;
             }
         }
         else
         {
             EditorGUILayout.HelpBox("No enemy types have been added.", MessageType.Warning);
         }
         EditorGUILayout.EndScrollView();
         EditorGUILayout.EndVertical();
         serializedObject.ApplyModifiedProperties();
     }
 
     [MenuItem("Tools/Enemies DataBase")]
     public static void Init()
     {
         string[] lookFor = new string[] { "Assets/DataBase/Enemies" };
         string[] result = AssetDatabase.FindAssets("EnemiesDataBase", lookFor);
         if (result == null || result.Length <= 0)
         {
             ScriptableObjectUtility.CreateAsset<EnemiesDataBase>("Assets/DataBase/Enemies");
         }
         else
         {
             Selection.activeObject = AssetDatabase.LoadAssetAtPath<EnemiesDataBase>(AssetDatabase.GUIDToAssetPath(result[0]));
         }
     }
 }

Comment
Add comment · Show 1
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image LunaTrap · Nov 15, 2017 at 09:32 AM 0
Share

Please go ahead and test the code, but you need to create "Assets/DataBase/Enemies" folder so the database scriptable object is created.

1 Reply

· Add your reply
  • Sort: 
avatar image
2
Best Answer

Answer by IgorAherne · Nov 15, 2017 at 11:17 AM

I had issues when adding things through the custom editor, where the target was not perceived as being modified. You have to explicitly tell unity "hey, i've altered this thing, you better save it soon"

For that, I had to call UnityEditor.EditorUtility.SetDirty(yourMonoBehaviorWithDictionary); after I've modified the value in dictionary, else serialization functions are not called

you can also wrap it in pre-compile tags, and include it directly into your monobehavior

 #if UNITY_EDITOR
 using UnityEditor;
 #endif
 
 void AddEnemyType(){
  #if UNITY_EDITOR 
 EditorUtility.SetDirty(this); //'this' is anything that inherits from UnityEnine.Object (scriptable obj, monobehaviour, etc)
 #endif
 }

it's quite cheap to call, because it's basically toggling a flag afaik

Comment
Add comment · Show 5 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image LunaTrap · Nov 15, 2017 at 12:13 PM 0
Share

Thanks a lot, will try it!

avatar image LunaTrap · Nov 15, 2017 at 12:31 PM 1
Share

Ok, it seems like it worked!

I changed:

     public void OnBeforeSerialize()
     {
         ED_$$anonymous$$eys.Clear();
         ED_Values.Clear();
         foreach (var item in enemies)
         {
             ED_$$anonymous$$eys.Add(item.$$anonymous$$ey);
             ED_Values.Add(item.Value);
         }
     }

To:

     public void OnBeforeSerialize()
     {
         ED_$$anonymous$$eys.Clear();
         ED_Values.Clear();
         foreach (var item in enemies)
         {
             ED_$$anonymous$$eys.Add(item.$$anonymous$$ey);
             ED_Values.Add(item.Value);
         }
         EditorUtility.SetDirty(this);
     }

But I have a complain, the Unity documentation does not mention this at all, look at it by yourself:

https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html

avatar image IgorAherne LunaTrap · Nov 15, 2017 at 12:48 PM 0
Share

if you include it directly into components, make sure to warp it in #if tags, like in my example, else unity will throw an error when compiling the actual final game

avatar image Bunny83 LunaTrap · Nov 15, 2017 at 01:58 PM 1
Share

No, no, no ^^. Do not set your object dirty inside the serialization callback. Unity does call your callback when it's going to serialize your object to disk but also in many other situations. You should mark it dirty when you change something. So this has to be done by your custom editor, not by the serialization callback.


Also read the SetDirty documentation. You should use the Undo system in this case. Just a few hours ago i've written this answer which might be helpful.


Yes, the documentation often is outdated or lacks important information. For example the Editor documentation does show the two different approaches (SerializedObject vs "target") however they are missing the Undo.RecordObject in their "target" example. This was working fine prior Unity 5.3. However now you have to use Undo.RecordObject before you do any changes to your object.

avatar image LunaTrap Bunny83 · Nov 15, 2017 at 07:09 PM 0
Share

Well, it works now but I will use your tip and refract my code :) thanks a lot.

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

82 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Error when trying to Serialize a field that is in a class 0 Answers

Public fields not showing in inspector 2 Answers

Multiple scripts reference to the same SerializeField 1 Answer

Saving dictionary from editor mode in play mode 1 Answer

Dictionary in inspector 4 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges