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.
  2. Dismiss Notice

Question Editor Data removes on Awake

Discussion in 'Scripting' started by RoeeHerzovich, Oct 9, 2020.

  1. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    I know this question and answers to it is all over the place, but in that case, it's al little different.

    All of my objects are serializable, everything is properly serialized.

    The object that I am trying to serialize works perfectly until the Awake at which it resets.
    It's a list containing a serializable, non-generic, tree-structured class.

    I tried setting the target as dirty as well as changing the scene to dirty but it just doesn't work... the target is a reader that takes that list and reads data from it. The reading code works perfectly well (as it was tested before without the custom serialization)

    The only thing that is not working, as usual, is that the data from the list is removed on Awake, the only thing that's left is a new empty list. Which makes me suspect because I assumed it'd be null...

    Any ideas?
     
  2. Meishin

    Meishin

    Joined:
    Apr 12, 2019
    Posts:
    26
    Hi ! Bit hard to tell without knowing anything about your code, maybe you can post your class here ?

    First things i'd checked ; Are you using a Custom inspector ? Are you implementing ISerialization Interface ? Did you tag [System.Serializable] on your objects' class ? Does the tree structure has more than 7 depth or does objects references both ways ?
     
  3. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    Are you using a custom inspector? Yes.
    Are you implementing ISerialization Interface? No, I've never used it but I'll check.
    Did you tag [System.Serializable] on your objects' class? Yes.
    Does the tree structure has more than 7 depth or do objects reference both ways? Specifically with my examples no, but it could get there, and wdym reference both ways?

    Information about objects:

    Route -
    string name
    string displayName
    List<KeyValuePair<Character, string>> lines
    List<KeyValuePair<Choice, int>> choices


    Choice -
    Route origin
    Route[] subRoutes


    Character -
    string name
    string hexColor
    Sprite image



    The way it is made in a tree-structured way is that a route has a list of choices that each have a list of routes and so on.


    If you need more information about the code let me know
     
  4. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    Not sure at all what you are doing, but these ones are not going to be serializable by Unity, so they won't show in the Inspector either:
    Code (CSharp):
    1. List<KeyValuePair<Character, string>> lines
    2. List<KeyValuePair<Choice, int>> choices
     
  5. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103

    The way I serialized them is:

    for Lines:
    making a toggle foldout (to resemble a list)
    containing two textFields and a label
    textField one for character name, label for a " : " (separation, meant just for style), and another textField for the string which is the line, it does store the data

    for Choices:
    making a toggle foldout (to resemble a list)
    using a helpBox verticle
    making a function to generate a Route, made out of simple fields, and doing the same for Choice, as Choice is basically a list of Routes.

    Edit:
    the data is being stored perfectly because when I run methods on them before awaking, it worls
    I just don't know how to pass Unity's serilizing

    And more than that, even if those two lists will not be serialized, it doesn't explain why NONE of the information was serialized, it should have keep a list of the Routes, with their names, display names, and only removing those two lists from each. Yet, it removed EVERYTHING
     
    Last edited: Oct 9, 2020
  6. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    I think this is a pretty fine look at what I got

    first image is when all lists are closed
    second is seeing the list of lines
    third is list of choices (you can see one and a half choices... the second one couldn't get into the frame fully)

    Edit
    I already made a working version supporting text files(last image)

    the <Ignore> things is just silencing my exception system so ignore it... lol ignore the ignore

    There are more stuff to it, like in-text commands, a wide exception system, character customization etc... but I just showed the very basic that actually relates to the problem(The 4th one doesn't have to do with the problem I just showed it so you can get a picture of what that graph in the inspector creates)
     

    Attached Files:

    Last edited: Oct 9, 2020
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    The way you serialize it? I think you're confusing some things here. A custom inspector has no (absolute zero) influence on how and if the data is serialized. A custom inspector is only responsible for how the data is presented. Like @eses said your data structures are not supported by Unity's serialization system. Also your classes have circular references which is generally not supported, no matter if you use them or not. You can not store hierarchical data in custom serializable classes this way. A common solution is to "unroll" your hierarchy into a flat array and reconstruct the actual hierarchy from IDs / indices of that array.
     
  8. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    Whoops-
    yeah, Ik it only affects the data presentation, but I was wrong, my bad.
    umm.. yeah when I said tree-structured I meant hierarchical. I'll try to unroll it (which means I'll need to ID them... I'll look into it, thanks for your reply)
     
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    You can actually use the ISerializationCallbackReceiver to kind of implement an auto unroll feature. I've quickly hacked this example together based on the bits we know about your case:

    Code (CSharp):
    1.     [System.Serializable]
    2.     public class Data : ISerializationCallbackReceiver
    3.     {
    4.         [System.NonSerialized]
    5.         public List<Route> routes;
    6.         [SerializeField]
    7.         private List<int> m_RouteIDs;
    8.         [SerializeField]
    9.         private List<Route> m_AllRoutes;
    10.  
    11.         public void OnAfterDeserialize()
    12.         {
    13.             if (routes == null)
    14.                 routes = new List<Route>();
    15.             foreach (var route in m_AllRoutes)
    16.             {
    17.                 foreach(var choice in route.choices)
    18.                 {
    19.                     choice.origin = m_AllRoutes[choice.m_OriginID];
    20.                     if (choice.subRoutes == null)
    21.                         choice.subRoutes = new List<Route>();
    22.                     choice.subRoutes.Clear();
    23.                     foreach (int routeID in choice.m_SubRouteIDs)
    24.                         choice.subRoutes.Add(m_AllRoutes[routeID]);
    25.                 }
    26.             }
    27.             foreach (var routeID in m_RouteIDs)
    28.                 routes.Add(m_AllRoutes[routeID]);
    29.         }
    30.  
    31.         public void OnBeforeSerialize()
    32.         {
    33.             if (m_AllRoutes == null || m_RouteIDs == null)
    34.             {
    35.                 m_AllRoutes = new List<Route>();
    36.                 m_RouteIDs = new List<int>();
    37.             }
    38.             m_AllRoutes.Clear();
    39.             m_RouteIDs.Clear();
    40.             foreach (var route in routes)
    41.             {
    42.                 AddRoute(route);
    43.                 m_RouteIDs.Add(route.m_ID);
    44.             }
    45.         }
    46.         private void AddRoute(Route aRoute)
    47.         {
    48.             aRoute.m_ID = m_AllRoutes.Count;
    49.             m_AllRoutes.Add(aRoute);
    50.             foreach(var choice in aRoute.choices)
    51.             {
    52.                 choice.m_OriginID = aRoute.m_ID;
    53.                 if (choice.m_SubRouteIDs == null)
    54.                     choice.m_SubRouteIDs = new List<int>();
    55.                 choice.m_SubRouteIDs.Clear();
    56.                 foreach (var route in choice.subRoutes)
    57.                 {
    58.                     AddRoute(route);
    59.                     choice.m_SubRouteIDs.Add(route.m_ID);
    60.                 }
    61.             }
    62.         }
    63.     }
    64.  
    65.     [System.Serializable]
    66.     public class Route
    67.     {
    68.         public string name;
    69.         public string displayName;
    70.         public List<Line> lines;
    71.         public List<Choice> choices;
    72.         [System.NonSerialized]
    73.         public int m_ID;
    74.     }
    75.  
    76.     [System.Serializable]
    77.     public class Line
    78.     {
    79.         public Character character;
    80.         public string text;
    81.     }
    82.     [System.Serializable]
    83.     public class Choice
    84.     {
    85.         public int ID;
    86.         [System.NonSerialized]
    87.         public Route origin;
    88.         [System.NonSerialized]
    89.         public List<Route> subRoutes;
    90.  
    91.         public int m_OriginID;
    92.         public List<int> m_SubRouteIDs;
    93.     }
    94.     [System.Serializable]
    95.     public class Character
    96.     {
    97.         public string name;
    98.         public string hexColor;
    99.         public Sprite image;
    100.     }
    101.  

    Note that I haven't tested the code. Also note that in order to work with those classes you don't want to use Unity's SerializedObject / SerializedProperty in your custom inspector but instead use the old "target" reference. The "Data" container will essentially take care of everything. You just create your Route, Choice, Line, Character objects just as you like and don't touch any of the m_ fields manually. The serialization callback interface will take care of converting your hierarchical structure into a flat array and assigns the indices as IDs. It also automatically reconstructs your hierarchy after deserialization.

    Note that this is just meant as an example. Also note that if you want to reference the same "Character" in multiple Lines, you may want to use IDs for those as well because after deserialization your character objects would be duplicated.

    For more information see Script Serialization.
     
  10. Meishin

    Meishin

    Joined:
    Apr 12, 2019
    Posts:
    26
    Hiho, yeah @Bunny83 solution is the way to go. Just be careful that OnBeforeSerialize & OnAfterSerialize are usually called .. A LOT when inspector is opened (especially OnBeforeSerialize). So if you don't want your inspector to be a burden, try adding some basic checks that will allow minimum calculations in case your inspector lists are not updated.

    Also since you have a custom editor, the above should in theory not be necessary, though you have to keep in mind (you probably already know, just in case) :
    1°) In a custom inspector, you should not assign anything directly to a variable, but to its serialized representation
    For example
    Code (CSharp):
    1. private MyObjectEditorClass m_Object;
    2. private List<CustomClass> m_CustomList;
    3. SerializedProperty m_CustomListProperty;
    4. SerializedObject GetTarget;
    5.  
    6. void OnEnable()
    7. {
    8.     m_Object = (MyObject)target;
    9.     GetTarget = new SerializedObject(m_Object);
    10.  
    11.     m_CustomList = m_Object.skillsTree;
    12.     m_CustomListProperty = GetTarget.FindProperty("CustomListName"); // This is what you'll be modifying in the inspector
    13. }
    14. // Example
    15. public override void OnInspectorGUI()
    16. {
    17.     // Initialize
    18.     GetTarget.Update();
    19.  
    20.     base.OnInspectorGUI();
    21.    
    22.     // Custom Display
    23.     // Show(m_CustomListProperty);
    24.     // Adding List Element
    25.     // m_CustomListProperty.InsertArrayElementAtIndex(m_CustomListProperty.arraySize);
    26.    
    27.     GetTarget.ApplyModifiedProperties();
    28. }
    2) Since keyvaluepairs are not serialized by default, you have to define your own serialization (or find some custom package) to be able to use those FindProperty etc.. (task is basically defining your own KeyValuePair
    as an XML serializable collection of keys and values, and implement methods to read/write that XML).
    Note that there is a reason why KeyValuePair is not serialized in the first place (forgot but you'll prob find it easily if you need to).

    Final note ; I'd advice you not to work with keyValue pairs. If you need a serialized dictionnary, use a custom one you can find pretty much anywhere (i'm using OdinInspector for so much more reasons), else i'd suggest you to keep working with lists of custom class, you can do pretty much anything with those and if you don't intend to put hundreds/thousands of entries there is no performance drop between accessing a List compared to a dictionnary.