Search Unity

Bug Scriptable objects loosing their public fields when building

Discussion in 'Editor & General Support' started by BulbasaurLvl5, Jan 2, 2023.

  1. BulbasaurLvl5

    BulbasaurLvl5

    Joined:
    May 10, 2020
    Posts:
    42
    Hello,

    i am kind of lost on this one and spent now around 10h tying to get this into my head. However at this point i assume it is unintended behavior. v 2019.4.34f1

    So i do have scriptable objects
    Code (CSharp):
    1. public abstract class ModifierType : ScriptableObject
    2. {
    3.     [SerializeField] string soid;
    4.  
    5. #if UNITY_EDITOR
    6.     protected virtual void OnValidate()
    7.     {
    8.         string path = AssetDatabase.GetAssetPath(this);
    9.         soid = AssetDatabase.AssetPathToGUID(path);
    10.     }
    11. #endif
    12. }
    13.  
    So they get their guid in the editor. These scriptable objects are a field of another class

    Code (CSharp):
    1. [Serializable]
    2. public class Stat
    3. {
    4.     public ModifierType type;
    5.     [SerializeField] string _typeSOID;
    6.     public string TypeSOID
    7.     {
    8.         get { return _typeSOID; }
    9.         set
    10.         {
    11.                 _typeSOID = value;
    12.         }
    13.  
    14.     //other fields
    15.     }
    16. }
    This class is the field of another scriptable object
    Code (CSharp):
    1. public class Item : ScriptableObject
    2. {
    3.     public List<Stat> stats;
    4.  
    5.     #if UNITY_EDITOR
    6.     void OnValidate()
    7.     {
    8.         string path = AssetDatabase.GetAssetPath(this);
    9.         soid = AssetDatabase.AssetPathToGUID(path);
    10.  
    11.         foreach (var i in stats)
    12.         {
    13.             i.TypeSOID = i.type.soid;
    14.         }
    15.     }
    16.     #endif
    17. }
    So i do have a field in a scriptable object that depends on another field of another scriptable object. (this is because items get instantiated and i need this information for serialization)

    This works totally fine as long as i am in the editor. However, when i built, random "Stat" objects pass an empty string ("") as their soid to the item.

    Some random observations of mine:
    • the stats are not totally random. from the 200 available it is always a random number of the same 10 (the first 1-4 of an item and the last, for what it is worth)
    • a debug message in the item class gives way more messages then deleted strings or stats. this means OnValidate gets called way more then id expect when building
    • the number of null soids is different from build to build
    • if all stats are referenced by either a prefab or other scriptable object everything is fine
    • it happens only for the ModifierType class. if i remove the OnValidate code from Item the soids persist correctly
    • it only happens to the string field of "stats". other fields are remembered correctly. this makes me assume, that it is indeed the "ModifierType" object that loose value during the building process. however if i check them after the build, they all have their ID, they are just not correctly passed to the item
    At some point i assumed that it had something to do with script execution order. But the order can only be set for monobehaviours, if i read the documentation correctly. Even then, their should be no point in which a string field of ModifierType should be null. They have to be reset during the built or something. Other than that i am aware that string is a reference type, but at no point is a soid set to "", so this should also not be the cause.
    Id like to avoid the adressables package if possible. I made sure, that the soid of ModifierType is not set by another class (it is actually only acessible through a property, which is not shown). And just to be sure: i am not trying to change values at runtime, the information is lost when i built.

    So thanks for reading, i am glad for every hint
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Just a cross-check: you do have actual instances of each of these things sitting on disk, correct? Since it is abstract, you must have made a concrete type on disk of something derived from this, correct? And it would also have to call base.OnValidate() and do the whole protected -> child cascade.

    In other words, you're not just trying to keep them in the build by virtue of it being created and referenced in the scene, because that won't keep it. Every asset must be written to disk.

    Given that it is on disk, what do you see when you pull up the SO itself (it's just a text file)?
     
    BulbasaurLvl5 likes this.
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,927
    Agree with above; this seems to be a case of checking if values are being serialised/written to disk or not. Most likely, they just aren't being written.

    I'll note that this sort of inter-object communication is not the intended use of OnValidate. This is the kind of things that need to be handled by custom editor work.
     
    BulbasaurLvl5 likes this.
  4. BulbasaurLvl5

    BulbasaurLvl5

    Joined:
    May 10, 2020
    Posts:
    42
    Hey

    first of all thanks for checking. i am not sure if i understand all parts of the questions, but ill try my best to answer

    here is a picture



    edit: picture did not seem to integrate. here is the link: https://imgur.com/a/4f4fRNd

    From left to right.
    i) what "ModifierTypes" look like in my asset folder. so as you said i made a small script that inherits from stat, and created its scr obj. base.onvalidate is not needed, because its protected and never overridden. i can check this by trying to change the id and watching it update. that is shown in ii)
    iii) shows what an item looks like in the inspector after building with crtl+b. the empty fields were not empty before

    if i change the soid field of "stat" to this

    Code (CSharp):
    1.     public string TypeSOID
    2.     {
    3.         get { return _typeSOID; }
    4.         set
    5.         {
    6.             if (value != "")
    7.                 _typeSOID = value;
    8.  
    9.             if (value == "")
    10.                 Debug.Log("null value passed");
    11.         }
    12.     }
    my console is cluttered with plety cases where an empty string was passed
     
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,927
    We honestly weren't asking questions, but telling you the problem. Simply put, the values aren't being written to disk.

    I'll quote what Kurt already said:
    If you open one of these problem scriptable object files in notepad I think you'll find these 'soid' values have not been written to disk.

    Whenever you change the values of objects via code you need to dirty the object (by use of functions such as
    UnityEditor.EditorUtility.SetDirty()
    ), otherwise Unity doesn't know it needs to re-write its values from memory to disk. It's important to note there is a difference between what Unity shows (which exists in memory), and what is written to your drives.

    And I'll repeat this use is NOT what
    OnValidate
    is intended for. It should only be used for very basic validation within the same object. Anything beyond that you should use more robust editor scripting.
     
    BulbasaurLvl5 and Kurt-Dekker like this.
  6. BulbasaurLvl5

    BulbasaurLvl5

    Joined:
    May 10, 2020
    Posts:
    42
    ok i see. partly this answer is a little disheartening, because the main reason i did this exercise was to avoid custom editor code. the second was to have an id system sight

    anyway, i much appreciate the answers. I think i learned something, or at least i understand the problem.

    For the record you guys were right about the values not written to disk thing

    Code (CSharp):
    1. %YAML 1.1
    2. %TAG !u! tag:unity3d.com,2011:
    3. --- !u!114 &11400000
    4. MonoBehaviour:
    5.   m_ObjectHideFlags: 0
    6.   m_CorrespondingSourceObject: {fileID: 0}
    7.   m_PrefabInstance: {fileID: 0}
    8.   m_PrefabAsset: {fileID: 0}
    9.   m_GameObject: {fileID: 0}
    10.   m_Enabled: 1
    11.   m_EditorHideFlags: 0
    12.   m_Script: {fileID: 11500000, guid: e19226d5972677f449c453fc842209ac, type: 3}
    13.   m_Name: _e_damagePercAddOnProtection
    14.   m_EditorClassIdentifier:
    15.   _soid:
    16.  


    Maybe, you can give me some key words to research to tackle this problem with editor coding? Otherwise thanks again for clarification
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    I highly recommend staying away from these sorts of identifiers.

    Even if you get them working and write all the editor tooling, you STILL invite disaster as soon as one of them somehow (there's myriad ways) gets the same number as the other.

    So then you need more editor tooling to enforce that.

    And if some other object (or save game) keeps that ID, and it changes, now you have ANOTHER mysterious problem.

    Adjunct IDs like this are just bad news all around.

    Instead just use the name of the ScriptableObject itself on disk.

    This way if you always put all of the instances in a single directory then you can know they are unique.