Search Unity

Scriptable objects dirtying all scenes

Discussion in 'Scripting' started by silentslack, Dec 9, 2017.

  1. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    266
    Hi,

    I have my own custom implementation of the Adventure Tutorial (outlined here)

    While this is a great solution for handling scene actions in my game I have encountered an issue with scene dirtying (scenes report a change and a save is required).

    Whenever I make a change to any scene level scriptable object (currently scene objects, not saved assets) all scenes open in the hierarchy are automatically dirtied. This wouldn't be so much of any issue but I have to have many scenes open (static geo scenes) due to another Unity bug where my editor freezes up on scene loading :mad: (see here: https://forum.unity.com/threads/editor-freeze.496876/).

    So my question, is anyone aware why changes to scriptable object dirty all scenes and is there a workaround to this? I would presume it should only dirty the scene that the scriptable object belongs? Am I missing something in my understanding?

    Many thanks any help on this appreciated, Jake
     
    Last edited: Dec 15, 2017
  2. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    266
    Any help on this one please Unity?
     
  3. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,713
    Not sure I'd ever heard of a SO dirtying the scene. Afaik, SO's don't belong to any scene, as they are assets.

    However, I've also never had multiple scenes open at once, so can't speak to that part. :)
     
  4. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    266
  5. Phobiegames

    Phobiegames

    Joined:
    Apr 3, 2017
    Posts:
    81
    What do you mean by "dirtying the scene"?
     
  6. Lysander

    Lysander

    Joined:
    Feb 24, 2013
    Posts:
    1,648
    He means that it marks the scene as dirty- that the scene has unsaved changes.

    As for using ScriptableObjects as scene assets, my only question is "why?". A ScriptableObject is only a YAML data file, it has no value beyond its usage as an asset stored physically on the drive. If you need a data object to create and use only in the scene, you can use literally any object type that doesn't derive from UnityEngine.Object and pass that around however you like. If you'd like to make a collection of said data objects and only refer to a specific item in that collection, you can do that with two MonoBehaviours- one to hold the collection for the whole scene (a singleton, no doubt), and another to put on each individual object that has only an ID number/string referencing the object in that singleton, and resolves that reference in Start. This works for preset configuration files when you want to define several for, and switch between them as needed, but still be entirely scene-limited.

    As to why this dirties the scenes, I assume it's because this is undocumented abuse of ScriptableObjects, and not something that's supposed to work at all. If anyone has seen this usage listed in any official documentation, I would be very interested to see it. The linked post makes a lot of bad assumptions about ScriptableObjects, serialization, and normal C# objects, and the post that THAT post links to makes even more. What exactly is the problem that you're trying to solve with this approach?
     
    Last edited: Jan 12, 2018
  7. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    266
    @Lysander Thank you for your input here. However, I followed this official Unity tutorial which uses this specific practice:



    I implemented their tutorial as a way to trigger scene actions as the gameplay unfolds (see Reactions below):



    It all works rather neatly, it's only the scene dirtying. But yeah, looks like there isn't a solution for this.

    EDIT: There were substantial changes I made to the Adventure Tutorial so there is a good chance I've implemented something incorrectly but from looking at the original code it looks like they instantiate ScriptableObjects in a similar way:


    Code (CSharp):
    1. public static Reaction CreateReaction (Type reactionType)
    2.     {
    3.         // Create a reaction of a given type.
    4.         return (Reaction)CreateInstance (reactionType);
    5.     }
     
    Last edited: Jan 16, 2018
  8. Lysander

    Lysander

    Joined:
    Feb 24, 2013
    Posts:
    1,648
    I get it, they're using them like blueprints, I just don't see the point. In order to save a new reaction (in your example) to persist between game sessions at runtime, you'd need to treat it like absolutely any other serializable object, writing and loading it from file as binary, or JSON, or XML, or whatever. That means that the only benefits to using ScriptableObjects in this scenario (that I can think of) are that when you instantiate one it keeps the specific values from the original, and drag and dropping references in the editor- in exchange, you're bound by all of the rules that normally apply to ScriptableObjects because of the editor/inspector serialization limitations, despite those restrictions not really applying to you since you only need the data at runtime. There are also going to be bugs, like the one you're currently experiencing, because this isn't how assets are supposed to work.

    My advice to you is still, and will always be, "don't use ScriptableObjects this way". They're assets, and despite the tutorial, you'll be better off just treating them like assets. If you need blueprints, you can generate those hundreds of different ways. for instance not using the ScriptableObject itself as your runtime data object, but rather an object type WITHIN the ScriptableObject. That way, you can use the ScriptableObject as a set of definitions (presets) for the specific setups of its inner class object- so have a few different types of ObjectA in an array that you make in the editor/inspector, then use that like a factory to produce instances of ObjectA based on the index/name of the preset stored there. You generate copies of the objects in the factory, not the ScriptableObject container- this is much more in line with the documented usage of ScriptableObjects at runtime.

    Anyways, rant over. You can choose to take that advice or not, I just wanted to get it out of the way. You can probably fix your specific problem just by using the HideAndDontSave flag when generating a new instance of the ScriptableObject. You can find more information on the HideFlags here. Be sure to only use it for those specific instances of the ScriptableObjects that are generated and used only during runtime, and it should keep those objects from dirtying the scenes I think.
     
    Last edited: Jan 16, 2018
    Suddoha likes this.
  9. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    266
    @Lysander yes asset is how I've always thought of scriptable objects, using them in the standard way of saving them to disk to hold some data. This implementation was news to me but I was happy with how its worked out so far minus the dirtying issue. I did run into the problem of trying to use this Interaction system on prefabs instantiated in different scenes but as the scriptableobject data is stored in the scene file, this doesn't work.

    I'll consider taking your advice but tbh I don't fully understand your implementation. I'm thinking of using scriptable objects in just the way I do but save as an asset and have a reference to this asset on the Monobehaviour. Obviously I will need decent asset management in creating and deleting these different interactions (scriptable objects).

    I'll first give your HideAndDontSave flag a run for its money. Thank you so much for your time & input here :)
     
  10. Lysander

    Lysander

    Joined:
    Feb 24, 2013
    Posts:
    1,648
    My idea was to use it like a blueprint system for normal objects. Essentially, you'd make a ScriptableObject (and store it in Resources or drag and drop it onto the relevant prefab/scene references) whose job is to manufacture copies of objects based on a given preset. Here's a quick super-simple example:
    Code (CSharp):
    1. [CreateAssetMenu(fileName = "ItemGenerator", menuName = "Item Generator")]
    2. public class ItemGenerator : ScriptableObject, ISerializationCallbackReceiver
    3. {
    4.     // define the presets/blueprints in the inspector here
    5.     [SerializeField]
    6.     private List<Item> _itemBlueprints;
    7.  
    8.     [System.NonSerialized]
    9.     private Dictionary<string, Item> _itemBlueprintLookup;
    10.  
    11.  
    12.     // gets called whenever the object is serialized in the editor
    13.     void ISerializationCallbackReceiver.OnBeforeSerialize() { }
    14.  
    15.     // gets called whenever the object is deserialized in the editor
    16.     void ISerializationCallbackReceiver.OnAfterDeserialize()
    17.     {
    18.         InitializeLookup();
    19.     }
    20.  
    21.     private void InitializeLookup()
    22.     {
    23.         if (_itemBlueprintLookup == null)
    24.             _itemBlueprintLookup = new Dictionary<string, Item>();
    25.         else
    26.             _itemBlueprintLookup.Clear();
    27.  
    28.         for (int i = 0; i < _itemBlueprints.Count; i++)
    29.             _itemBlueprintLookup[_itemBlueprints[i].Name] = _itemBlueprints[i];
    30.     }
    31.  
    32.  
    33.     public Item MakeItemFromBlueprint(string name)
    34.     {
    35.         // duplicate the blueprint to hand an item back to the requester
    36.         if (_itemBlueprintLookup.ContainsKey(name))
    37.             return new Item(_itemBlueprintLookup[name]);
    38.  
    39.         return null;
    40.     }
    41. }
    42.  
    43. /// <summary>
    44. /// This is the serializable object you want to be able to duplicate at will.
    45. /// </summary>
    46. [System.Serializable]
    47. public class Item
    48. {
    49.     public string Name;
    50.     public string Description;
    51.  
    52.  
    53.     public Item() { }
    54.  
    55.     public Item(string name, string description)
    56.     {
    57.         this.Name = name;
    58.         this.Description = description;
    59.     }
    60.  
    61.     // this constructor duplicates the object you pass in
    62.     public Item(Item item)
    63.     {
    64.         Name = item.Name;
    65.         Description = item.Description;
    66.     }
    67. }
    The items you add in the inspector become the "blueprints" that you can duplicate and hand out to requesters. This turns the ScriptableObject into a factory object, and makes it possible to store an infinite number of variations to an object as simple settings presets. ScriptableObjects in the editor work much the same way, but with the added annoyance of needing to exist as physical files- they're fantastic tools, but your specific use of them doesn't seem warranted IMO, because you can just make a factory like this one instead easily enough.

    Just my take on it.
     
    Last edited: Jan 17, 2018
  11. simonlvschal

    simonlvschal

    Joined:
    Nov 17, 2015
    Posts:
    263
    mhm no. first of all SO doesn't belong to a scene it belongs in the assets, or Memory even. and also SO will not Dirty a scene. it infact can't. i am using SO my self and i never have that problem. so you must be doing something very wrong.

    infact SO cannot belong anywhere but in a asset file. they are just Data files. nothing else..

    also if u change one SO it changes all things that relies on it in Runtime OR if you have a thing in a scene use it. Hence why you prob think it dirties the scene which it doesn't.
     
  12. Lysander

    Lysander

    Joined:
    Feb 24, 2013
    Posts:
    1,648
    He's using Instantiate(someScriptableObject), which generates a clone of that ScriptableObject and works at runtime (it's not a part of the UnityEditor assembly after all). By not tying the generated ScriptableObjects to asset files, they're essentially functioning exactly the same as any System.Object, but the editor is still treating them as UnityEngine.Objects (reference types, in this context). This creates a unique situation that's not exactly illegal, but also not documented or supported- it works, but it dirties the scenes because the "asset" exists only in the context of the scene, and confuses the editor.

    Why do it this way? Because it makes it easy to generate duplicate items. ScriptableObjects (since they're treated as assets) can be duplicated in a fantastically easy manner, so some people shortcut factory patterns where they'd have to have Clone / Copy To Constructors if done any other way. I offered up an easy alternative- ScriptableObjects really shouldn't be used in this manner, even if there's nothing technically stopping people from doing it.
     
  13. simonlvschal

    simonlvschal

    Joined:
    Nov 17, 2015
    Posts:
    263
    true but even creating a instance of SO wont dirty a Scene. i am quite sure. for me for example i am creating a instance of a SO just for purpose of instantiating different Viables etc.
     
  14. hedgehog90

    hedgehog90

    Joined:
    May 18, 2016
    Posts:
    27