Search Unity

Bug Scriptable objects can break in builds

Discussion in 'Editor & General Support' started by SoftwareGeezers, Oct 14, 2021.

  1. SoftwareGeezers

    SoftwareGeezers

    Joined:
    Jun 22, 2013
    Posts:
    902
    I'm trying SO's for the first time in a new side-project. At first I felt them a very capable and exciting feature, but now it seems they have some notable limits and break in actual builds and must be used cautiously.

    The attached is a debug project. I use an SO motion script as a way to assign a motion type to an agent, such as follow player or fly straight or follow a path. Running "test.scene", the bug follows a path (created with the asset BGCurve) assigned at runtime. But build for Windows or Android, the bug does nothing, and the debug log records:

    The referenced script on this Behaviour (Game Object '<null>') is missing!​

    Furthermore, I can get Unity to lose reference to the SO script so the project fails in the Editor. After building an executable, make a change to the EntityMotion_Path script such as adding a log. It runs fine in the Editor with the change. Exit the editor, reload the project, and the SO script reference is missing. This break only happens after a build; you can merrily make changes to the EntityMotion_Path code without the script reference being lost so long as you haven't build the project. Thus the user experience is:

    1) Get game running great in Editor.
    2) Build and it does nothing.
    3) Try some debugging and get nowhere.
    4) Find the game doesn't even run in the editor any more either!
    5) optional - rant a bit about wasted effort and WTF is going on and how can the game suddenly stop working?!?!

    Within my game proper that I'm trying to debug, I get the error:

    A scripted object (script unknown or not yet loaded) has a different serialization layout when loading. (Read 44 bytes but expected 72 bytes)
    Did you #ifdef UNITY_EDITOR a section of your serialized properties in any of your scripts?​

    There's a known issue with #if UNITY_EDITOR directives creating conditional [SerializeField] Inspector fields. The only code calling this in my project is BGCurve, which uses it to create the interface.

    Best I can gather, using conditional serialised fields in Scriptable Objects causes them to break. I shall proceed to replace my SO motions with an enumerated type and use a conditional code selector and see if that works.

    I don't know that this warrants an official bug report. All mention of the Serialization issue says it's by design and it's been present for years. It just seems that the way Unity works and builds projects means it cannot use SO with UNITY_EDITOR [SerializeFields] directives.
     

    Attached Files:

    Last edited: Oct 14, 2021
  2. DejaMooGames

    DejaMooGames

    Joined:
    Apr 1, 2019
    Posts:
    108
    I believe there is no bug here. The next paragraph is taken from the Unity documentation for ScriptableObjects.

    "When you use the Editor, you can save data to ScriptableObjects while editing and at run time because ScriptableObjects use the Editor namespace and Editor scripting. In a deployed build, however, you can’t use ScriptableObjects to save data, but you can use the saved data from the ScriptableObject Assets that you set up during development".

    See this stackoverflow post for more details.
     
  3. You can, but I wouldn't count on any meaningful change. It would be devastating if they had done this. Serialization is fast because they do not iterate over every piece of serialized data, they just get the data structure and load the serialized data into it. You really shouldn't make conditional serialization in unity editor. Use different data structure for different data structure.
    This has nothing to do with the "issue" at hand. It just means that in the editor your play mode changes will be saved into the assets and in the build they will not (they are immutable). This is true all assets in Unity.
     
  4. SoftwareGeezers

    SoftwareGeezers

    Joined:
    Jun 22, 2013
    Posts:
    902
    I'm not accessing the saved data from the SO in this case. Indeed, the SO instances I was using actually had zero data or serialisation which enabled me to solve the problem very easily by changing the class from a Scriptable Object to just a C# class. The problem here is the SO is referencing a script that uses Editor conditional Serialisation, and as a result you don't actually know what's causing the issue.

    It's a fringe case, but one someone else may well come across eventually.
     
  5. SoftwareGeezers

    SoftwareGeezers

    Joined:
    Jun 22, 2013
    Posts:
    902
    In my case, it's part of an asset over which I have no control. I can't tell if such conditional serialisation is bad practice or not, but the systems are in place to enable it and it works AFAICS save for this outlier when using those scripts through SOs.

    I solved my issue easily enough by turning the Scriptable Object class into a standard C# class. Then I use an Enumerated Type to specify the movement method to use on my agents, and create an instance of that Motion subclass inside the agent.

    I feel it's a bug by design. It's unwanted behaviour but one that can't be fixed. Hence it's just important to catalogue, but I'd like to see the documentation/tutorials updated with a warning because the fallout, though rare, is extremely odd behaviour that's difficult to track down.
     
  6. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    I ran into an issue with this recently, and I'm not sure if its the same issue.
    Due to additive loading and wanting to reference objects between scenes, I am using the RuntimeAnchors used in UnityOpenProjectOne "Chop Chop" developed by Unity Technologies.
    Here is the script:

    Code (CSharp):
    1. public class RuntimeAnchorBase<T> : DescriptionBaseSO
    2. {
    3.     public UnityAction OnAnchorProvided;
    4.  
    5.     [Header("Debug")]
    6.     public bool isSet = false; // Any script can check if the transform is null before using it, by just checking this bool
    7.  
    8.     [SerializeField] private T _value;
    9.     public T Value
    10.     {
    11.         get { return _value; }
    12.     }
    13.  
    14.     public virtual void Provide(T value)
    15.     {
    16.        
    17.         if (value == null)
    18.         {
    19.             Debug.LogError("A null value was provided to the " + this.name + " runtime anchor.");
    20.             return;
    21.         }
    22.        
    23.         _value = value;
    24.         isSet = true;
    25.  
    26.         if (OnAnchorProvided != null)
    27.             OnAnchorProvided.Invoke();
    28.     }
    29.  
    30.     public void Unset()
    31.     {
    32.         _value = default(T);
    33.         isSet = false;
    34.     }
    35.  
    36.     private void OnDisable()
    37.     {
    38.         Unset();
    39.     }
    40. }
    It works fine in editor.
    In build, references break. I can use logs to see that the correct object is provided... but everything that has referenced the SO or is waiting for a provide reads it as !isSet with a value==null. It almost seems like, during the build, a new instance of the SO is created for each reference.

    If Scriptable objects aren't meant to be used this way... why are they used like so in Unity's official project?

    Is there some setting somewhere I need to set to make this work?

    NOTE: setting variables static will work. But I would like to use instances if possible..
     
  7. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296
    Wow I also went with the strategy that CHOP CHOP demonstrated and what can I say... countless hours wasted due to race conditions or inconsistent scriptable object behavior on Android/Oculus builds...
     
  8. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296
    A lot of "advice" surrounding using ScriptableObjects from the CHOP CHOP demo really seem to be massive footguns when it comes to building actual games across platforms. 9/10 of my most time consuming bugs surround ScriptableObjects not working at all in android builds.
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,859
    Reading through the thread I'm just seeing the somewhat invisible issue where if you use scriptable objects in tandem with addressables or scriptable objects, you need to make sure to build the scriptable objects into your addressables groups or asset bundles.

    Otherwise they get duplicated at build time and experience the above issues. Addressables has tools to see where assets will be duplicated across your groups.
     
  10. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296
    Followup:

    - I now see that my problems with editor/build inconsistencies are actually not entirely due to Scriptable Objects. Most of these problem actually have to do with:
    • Not applying changes to nested prefabs down the entire nesting "hierarchy"
    • Not marking all my scriptable objects as addressable when using them across scenes (I still don't entirely understand what addressables are for or how they work under the hood).
     
  11. SoftwareGeezers

    SoftwareGeezers

    Joined:
    Jun 22, 2013
    Posts:
    902
    Scriptable Objects sounded very cool when I first learned about them, the fabulous feature you never knew existed. But after experiencing them, I think SO's a dumb idea. If the idea is a database, you're better off using a real database. I'm currently using BG Database for the first time and it's far and away a better, more workable, more robust solution.

    https://assetstore.unity.com/packages/tools/integration/bg-database-112262

    So my advice, use a database to store your parameters, and real Classes to implement, and you avoid all the headaches of SO's while gaining the advantages of proper data management.