Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Saving instances of Scriptable Objects to prefabs

Discussion in 'Scripting' started by Extrakun, Aug 10, 2010.

  1. Extrakun

    Extrakun

    Joined:
    Apr 2, 2009
    Posts:
    69
    I am using scriptable objects to hold configurations and settings, something like this:

    Code (csharp):
    1. class GUIConfig extends ScriptableObject
    2. {
    3.   ...
    4. }
    I am storing them inside a component attached to a game-object, using ScriptableObject.CreateInstance() from an editor script.
    Code (csharp):
    1.  
    2. public var guiconfig:GUIConfig;
    3.  
    4. public function Start()
    5. {
    6.    // access guiconfig to set skin
    7. }
    However, when I try to make the game-object a prefab, the scriptable object (GUIConfig) is not saved.

    What am I doing wrong?
     
  2. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    to save anything on a prefab it must be done from an editor script, where you assign it to the array and then use the SetDirty function to tell the editor that the data has changed and requires saving
     
  3. Extrakun

    Extrakun

    Joined:
    Apr 2, 2009
    Posts:
    69
    Unfortunately,that doesn't seem to work.

    I am only using the Editor Scripts to create instances of the scriptable objects. To create the prefab, I do the usual - drag the game object into a new prefab.

    But when I do so, all the scriptable objects in the prefab are null. The game object itself has all the scriptable object instances in bold, and I couldn't apply to it the prefab.

    I've add a screenshot to illustrate:
     

    Attached Files:

  4. Alex-Chouls

    Alex-Chouls

    Joined:
    Mar 24, 2009
    Posts:
    2,652
    I'm running into this same problem with the Unity3 beta.

    I have a MonoBehaviour which contains ScriptableObjects in an array. It all works great (saving/loading etc.) until I try to make the game object a prefab - then the scriptable objects are gone (set to None)

    If I try doing it the other way around, and add the scriptable objects to the prefab, then they are gone when the prefab is instantiated (set to Missing).

    I'll try building a simpler test case, but in the meantime, was wondering if other people are seeing this, and if there is a way to preserve scriptable object references in a prefab?

    [EDIT] Forgot to mention that I instantiate the scriptable objects in an editor using ScriptableObject.CreateInstance()
     
  5. Alex-Chouls

    Alex-Chouls

    Joined:
    Mar 24, 2009
    Posts:
    2,652
    Okay, I made a super simple test case to illustrate the problem.

    MyBehaviour.cs:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class MyBehaviour : MonoBehaviour
    5. {
    6.     public TestData data;
    7. }
    TestData.cs:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [Serializable]
    5. public class TestData : ScriptableObject
    6. {
    7.     public float health;
    8. }
    Editor/MyBehaviourEditor.cs:
    Code (csharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3.  
    4. [CustomEditor(typeof(MyBehaviour))]
    5. class MyBehaviourEditor : Editor
    6. {
    7.     public override void OnInspectorGUI()
    8.     {
    9.         MyBehaviour myBehaviour = target as MyBehaviour;
    10.  
    11.         if (GUILayout.Button("Instantiate"))
    12.         {
    13.             myBehaviour.data = (TestData)ScriptableObject.CreateInstance(typeof(TestData));
    14.         }
    15.  
    16.         if (myBehaviour.data != null)
    17.         {
    18.             GUILayout.Label(myBehaviour.data.ToString());
    19.         }
    20.     }
    21. }
    Now some testing:

    1. Drag MyBehaviour onto a GameObject and instantiate TestData. It works and saves/load correctly (as expected).

    2. Drag the GameObject to a prefab and TestData is lost.

    3. Re-instantiate TestData on the prefab, and drag the prefab into the scene. TestData is intact, however, it doesn't survive save/load.
     
  6. Alex-Chouls

    Alex-Chouls

    Joined:
    Mar 24, 2009
    Posts:
    2,652
    Found this thread that sheds some light on the problem.

    Basically, it seems that nested ScriptableObjects are not automatically serialized. I tried saving them manually (AssetDatabase.CreateAsset(), EditorUtility.SetDirty(), AssetDatabase.SaveAssets()) and that does seem to work... Unity seems to take care of the details. It means a lot of extra asset files, but seems like a workable solution right now. Need to do some more testing to make sure it really works.

    Are there any plans to automatically serialize nested ScriptableObjects? Or document some of the details of Unity serialization in one place.

    For example, I also beat my head against this problem for a bit: "...This makes it impossible to use inheritance in a serialized embedded class. If you must use inheritance you have to inherit from ScriptableObject." Which led me down the road to this problem with embedded ScriptableObjects!

    Figuring out these kinds of special rules can eat up a lot of development time...

    But I don't mean to sound negative, these are details, overall Unity is really amazing and a lot of fun to work with! Keep up the great work!
     
  7. nafonso

    nafonso

    Joined:
    Aug 10, 2006
    Posts:
    377
    I also had this problem.

    Whenever I applied the changes to the prefab, the prefab would end up with arrays populated with null references.

    Basically I gave up on the ScriptableObject, I just do my classes like this and everything works fine:

    Code (csharp):
    1.  
    2. [System.Serializable]
    3. public class MyClass
    4. {
    5.     public int m_id;
    6.     ...
    7. }
    8.  
    Although this does seem to beat the purpose of the ScriptableObject class IMO...

    Regards,
    Nuno Afonso
     
  8. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
  9. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,181
    Never mind, I guess you guys are looking for something other than what I offered . . .
     
    Last edited: Mar 13, 2013
  10. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
  11. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
  12. Medding3000

    Medding3000

    Joined:
    Dec 11, 2013
    Posts:
    45
    Did anybody find a solution for this?
    Is it possible to get nested scriptableobjects serialized when creating a prefab?

    Im bumping into this problem:
    Im editing a component (script) through a customeditor, then i want to make it a prefab so i drag it into the project view. Now all my references say 'None'.

    Furthermore, when i just copy a GO it is NOT a deep copy but the references remain the same. WHY is this?! So to say, i keep a reference of the GO in the scriptableobject. Then i copy it. the reference will still point to the old GO. :( this makes me sad.
     
  13. Qiu-Jian

    Qiu-Jian

    Joined:
    May 22, 2013
    Posts:
    3
    @Medding3000

    LigihtStriker is right. You need to create an "asset" to hold the scriptableObject with AssetDatabase.CreateAsset(asset, assetPath); This should be done in "EditorMode". If you modify the scriptableObject, don't forget to call "EditorUtility.SetDirty(asset)".

    Once you have the scriptableObject saved in an asset, you can put the reference in a Monobehavior of a prefab.

    BTW. On Android, I met some problem on loading prefab referring some deep nested scriptableObjects using Resources.Load. The player crashes if I put a large number of deep nested gameObject in that prefab.

    There is no such problem if the scriptableObject is located in an scene and loaded by Application.LoadLevelAddictive.

    I have been struggling with this problem for 2 days without finding and clue.
     
  14. aresfe

    aresfe

    Joined:
    Feb 26, 2015
    Posts:
    1
    now it's 2015, and this problem still haven't been solved!
    we do need more extensible serialization in Unity for polymorphism
     
  15. nack

    nack

    Joined:
    Dec 14, 2012
    Posts:
    6
    Okay old thread that I stumbled across. But, I think you need to set the hideFlags on the scriptableObject to HideFlags.HideAndDontSave. This is needed if the root of an object is NOT in the scene, like a prefab. Here is a link to a mega thread by a unity Dev about scriptableObjects best practices.
     
  16. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    If you want to save a ScriptableObject asset then you will need to save it as a standalone asset using AssetDatabase.CreateAsset. Hiding the asset only makes sense when using AssetDatabase.AddObjectToAsset.

    If you want to keep the object instance (like if it is an asset) then you absolutely do not want to use the "DontSave" flag otherwise it'll be lost when Unity next loads.

    If it is just a temporary instance, then obviously the "DontSave" flag makes sense, but still don't forget to manually destroy the instance where applicable to avoid leakage.
     
    nack likes this.
  17. nack

    nack

    Joined:
    Dec 14, 2012
    Posts:
    6
    You're probably right, but that guides does say the following:

    HideFlags
    In the examples using ScriptableObjects you will notice that we are setting the ‘hideFlags’ on the object to HideFlags.HideAndDontSave. This is a special setup that is required when writing custom data structures that have no root in the scene. This is to get around how the Garbage Collector works in Unity.

    When the garbage collector is run it (for the most part) uses the scene as ‘the root’ and traverses the hierarchy to see what can get GC’d. Setting the HideAndDontSave flag on a ScriptableObject tells Unity to consider that object as a root object. Because of this it will not just disappear because of a GC / assembly reload. The object can still be destroyed by calling Destroy().
     
  18. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    Correct; the "DontSave" part indicates that the object will not be saved when you exit from the Unity editor. This is useful when creating temporary objects that have no root; like procedurally generated meshes for instance.

    Typically you would destroy the object inside a "OnDestroy" or "OnDisable" method in the associated MonoBehaviour class.

    In editor scripting you do not necessarily want to explicitly destroy the ScriptableObject instance; setting the "DontSave" flag is useful here since it avoids "leaked object" warnings from being logged to the Unity console. Unity will just release the native resource since this was clearly intended since the "DontSave" flag was specified.

    If you are saving a ScriptableObject to an asset file with AssetDatabase.CreateAsset or AssetDatabase.AddObjectToAsset, then you obviously DO want to save it... so do not use the "DontSave" flag since this is ambiguous and may behave oddly.

    If you have a ScriptableObject instance, and you haven't saved it to an asset file using either AssetDatabase.CreateAsset or AssetDatabase.AddObjectToAsset, then it WILL NOT be saved as a part of the prefab file. It will simply be lost when you next reload Unity.
     
    nack likes this.
  19. nack

    nack

    Joined:
    Dec 14, 2012
    Posts:
    6
    Okay I think I got'cha.

    If I want the scriptableObject to be serialized outside of the scene, I MUST add it to an asset using either AssetDatabase.CreateAsset or add it to an existing asset with AssetDatabase.AddObjectToAsset, without the hideFlags, or at least without DontSave. With AddObjectsToAsset I can use a single asset to store multiple instances of said scriptableObject or it's derivatives (not abstracts though, according to that unity dev post, it's not wise).

    Thanks for clearing this up btw, I don't think I'm the only one slightly confused :)
     
  20. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    You can save multiple ScriptableObject instances of varying class types to the same .asset file. But avoid adding ScriptableObject instances to .prefab files because that is probably asking for trouble; especially when (and if) they finally decide to add nested prefab support.

    You can't save abstract `ScriptableObject` instances to an asset file for the simple reason that you can't actually instantiate one... :p
     
    nack likes this.
  21. nack

    nack

    Joined:
    Dec 14, 2012
    Posts:
    6
    Thanks, I'll stick to .assets for this then. Heh, I of course meant the derived classes ;)
     
  22. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    You can save derived ScriptableObject class instances to .asset files.

    Unity cannot serialize derived instances of non-ScriptableObject classes that are annotated with the `System.SerializableAttribute` attribute. For these you cannot serialize by abstract or base references.
     
  23. ALKubo

    ALKubo

    Joined:
    Mar 3, 2017
    Posts:
    3
  24. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,552
    Necroing this post because I wanted to do exactly that: figure out if it's possible to save a ScriptableObject asset inside a prefab. Sounds crazy? Well, this is actually what Tilemap palettes do, and that's how I came to wonder how they did it:

    upload_2023-3-28_16-46-56.png

    Rectangular Palette is a prefab. You can drop it in the scene and it'll be just like any 2D Tilemap. But it also has this subobject "Palette Settings" which happens to be a GridPalette instance (subclass of ScriptableObject):
    upload_2023-3-28_16-47-58.png

    I wrote a little script that manually creates such a "palette" prefab that shows up correctly in the Tile Palette window. You will have to adjust the path but other than that it should work for everyone:
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.Tilemaps;
    4.  
    5. public static class TestCreateTilePaletteManually
    6. {
    7.    [MenuItem("Tools/Manually create a TilePalette prefab")]
    8.    public static void Create()
    9.    {
    10.       var grid = new GameObject("grid manually");
    11.       grid.AddComponent<Grid>();
    12.  
    13.       var layer = new GameObject("layer manually");
    14.       layer.AddComponent<Tilemap>();
    15.       layer.AddComponent<TilemapRenderer>();
    16.       layer.transform.parent = grid.transform;
    17.  
    18.       var palette = ScriptableObject.CreateInstance<GridPalette>();
    19.       palette.name = "gridpalette settings";
    20.  
    21.       var path = "Assets/Tilemap/!tests/grid palette prefab.prefab";
    22.       var prefab = PrefabUtility.SaveAsPrefabAsset(grid, path, out var success);
    23.       UnityEngine.Object.DestroyImmediate(grid);
    24.  
    25.       if (success == false)
    26.          throw new System.Exception("could not create prefab");
    27.  
    28.       AssetDatabase.AddObjectToAsset(palette, prefab);
    29.       AssetDatabase.SaveAssetIfDirty(prefab);
    30.      
    31.       palette.name += " (modified)";
    32.       AssetDatabase.SaveAssetIfDirty(prefab);
    33.  
    34.       var loaded = AssetDatabase.LoadAllAssetsAtPath(path);
    35.       foreach (var o in loaded)
    36.          Debug.Log($"loaded: {o} - {o.name} - subasset: {AssetDatabase.IsSubAsset(o)}");
    37.  
    38.       var loadedSubs = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
    39.       foreach (var o in loadedSubs)
    40.          Debug.Log($"SUBS loaded: {o} - {o.name} - subasset: {AssetDatabase.IsSubAsset(o)}");
    41.    }
    42. }
    Noteworthy:
    • AddObjectToAsset must be followed by SaveAssetIfDirty() otherwise the sub-asset is only added to the in memory representation of the prefab.
    • You can modify the scriptableobject instance but naturally you have to call SaveAssetIfDirty() to persist any changes to the scriptableobject.
    • The asset to save is the main asset, not the scriptableobject sub-asset.
    • LoadAllAssetRepresentationsAtPath() is what I would have expected to be called "Load(Sub)ObjectFromAsset". You use that to get only the sub-assets inside an asset, ie those added via AddObjectToAsset().
    • Of course you can also enumerate over all assets in a given path and find the sub-assets by calling IsSubAsset().
    • HideFlags do not affect whether the prefab subassets are shown, selectable and editable since they are for scene instances, not assets. There is a m_EditorHideFlags in the YAML but even setting 32 bits manually it did not affect how the scriptableobject appeared in the editor. And yes, I ran Reimport on the prefab asset after that change.
    This approach seems very useful since the SO provides:
    • A way for the Tile Palette window to identify prefabs that are valid palettes, and persist information inside the prefab.
    • The SO data is static, which makes sense: you wouldn't want each instantiated tile on the map to have a copy of the palette data like cell size and sort mode as you would have if GridPalette were a MonoBehaviour in the prefab.
    • The user can still drag & drop the asset into the scene, instantiating it or editing it in prefab mode. The SO and its values do not get lost in the process.
    • The user can still edit the Palette Settings on the prefab by selecting it and editing values in the Inspector.
     
    pahe, Dubitrubi and Bunny83 like this.
  25. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,612
    You can save any asset as a sub-asset of any other asset. If you open the asset in a text editor, you'll see it's all just serialised flatly into the one file.

    Not necessarily. Like most asset database changes, you need to tell Unity it's happened with
    AssetDatabase.Refresh()
    , which happens when saving anyway.
     
    Last edited: Mar 29, 2023
    Bunny83 likes this.
  26. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,923
    spiney199 likes this.
  27. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,552
    To clarify because this is a pet peeve of mine: NO change made through AssetDatabase methods has to be followed up by AssetDatabase.Refresh(). In fact, doing so is bad practice.

    You only ever need to call Refresh() when you made filesystem changes that bypass the AssetDatabase such as calling System.IO methods (CreateDirectory or WriteAllBytes) or running an external tool that modifies/creates assets in the project while the Unity Editor window remains in focus.
     
    pahe and Bunny83 like this.