Search Unity

  1. We are looking for feedback on the experimental Unity Safe Mode which is aiming to help you resolve compilation errors faster during project startup.
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Question about the data storage of ScriptableObject at runtime

Discussion in 'Scripting' started by quiet00903, Jun 16, 2017.

  1. quiet00903

    quiet00903

    Joined:
    May 18, 2017
    Posts:
    15
    I am new to Unity. Recently I have learned about ScritptableObject for several days. The circumstance is that I create an asset of ScriptableObject and I create a GameObject in one Scene just called Scene1 that hold the reference to the asset. The ScriptableObject hold only a public int variable and a method that implement the functionality of int++. And I create an other scene called Scene2 which can be translate between Scene1. And I also create a scene called PersistentScene which will used later. The buildsetting order is from PersistentScene which is 0 to Scene2 which is 2. The code is very simple like below:
    Code (CSharp):
    1. //This is the ScriptableObject Class
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. [CreateAssetMenu(fileName = "Num")]
    6. public class NumCountScriptable : ScriptableObject {
    7.  
    8.     public int num;
    9.  
    10.     public void Plus()
    11.     {
    12.         num++;
    13.        // EditorUtility.SetDirty(this);
    14.     }
    15. }
    16.  
    17.  
    18.  
    19. //This is the class of Translate Scene by click button
    20. using System.Collections;
    21. using System.Collections.Generic;
    22. using UnityEngine;
    23. using UnityEngine.SceneManagement;
    24. using UnityEditor;
    25.  
    26. public class Loader : MonoBehaviour {
    27.  
    28.     private void Start()
    29.     {
    30.         if(SceneManager.GetActiveScene().buildIndex==0)
    31.         SceneManager.LoadScene(1, LoadSceneMode.Additive);
    32.     }
    33.  
    34.     public void LoadScene(int index)
    35.     {
    36.         //Loader persist = GameObject.Find("Loader").GetComponent<Loader>();
    37.         //persist.LoadSceneAdd(index);
    38.  
    39.         SceneManager.LoadScene(index);
    40.     }
    41.  
    42.     //public void LoadSceneAdd(int index)
    43.     //{
    44.     //    if(index==1)
    45.     //    {
    46.     //        SceneManager.UnloadSceneAsync(2);
    47.     //    }
    48.     //    else if(index==2)
    49.     //    {
    50.     //        SceneManager.UnloadSceneAsync(1);
    51.     //    }
    52.     //    SceneManager.LoadScene(index, LoadSceneMode.Additive);
    53.     //}
    54. }
    55.  
    56.  
    57. //This is the class on the GameObject to plus int
    58. using System.Collections;
    59. using System.Collections.Generic;
    60. using UnityEngine;
    61. using UnityEngine.UI;
    62.  
    63. public class TestNum : MonoBehaviour {
    64.  
    65.     public NumCountScriptable numScirpt;
    66.     public Text text;
    67.  
    68.     // Update is called once per frame
    69.     void Update () {
    70.         if(Input.GetKeyDown(KeyCode.Space))
    71.         {
    72.             numScirpt.Plus();
    73.             text.text = "Num: " + numScirpt.num;
    74.             //NumCountStatic.Plus();
    75.             //text.text = "Num: " + NumCountStatic.num;
    76.         }
    77.     }
    78. }
    P.S.: The Loader Class should be unenabled in Scene1 and Scene2 because its Start function should be only used by PersistentScene and the LoadScene function is just for Onclick.
    I create the asset and set its int to 0, and in Scene1 whenever I press space and the int++ and there is a Text to show the int. And there is a button for the Scene change. So there are many different situations I have met:
    Situation 1: Part one. I enter play mode and press space, the int++ like this(the red characters are what I write manually):
    upload_2017-6-16_12-34-23.png
    Part Two. When I exit the play mode without change scene and play again without select into other scenes, the number seems to store the data I have changed :
    upload_2017-6-16_12-38-44.png

    Situation 2: Part One. The same Part One like Situation 1.
    Part Two. I shut down the editor or I select into other scenes, the number reset to 0 as if it lost the data changed by the GameObject.
    upload_2017-6-16_12-42-48.png

    Situation 3: Part One. The same Part One like Situation 1.
    Part Two. I click the button to Scene2 and click the button in Scene2 to come back to Scene1, and the data seems to be lost again.
    upload_2017-6-16_12-46-15.png
    upload_2017-6-16_12-47-29.png

    Situation 4. I use the PersistentScene to load and unload between Scene1 and Scene2 just similar to the official tutorial of Adventure Game using the code about LoadSceneMode.Addtive which I have annotated and the data seems to be stored at runtime. But when I exit and play it again, the data just reset again.(The picture is to limit, I have to use text only... Pardon me for my first submit )

    Situation 5. Without the PersistentScene, just like the first three situation. I add a line code of EditorUtility.SetDirty in my ScriptableObject. And it turns out to be that the data changed by GameObject becomes persistent even when I shut down the editor and I come back to find the asset data has surely been changed by the GameObject.

    Situation 6. I try to remove the SetDirty function but the data become still persistent until I delete the asset and create a new asset to be referenced by the GameObject and it becomes resettable again.

    Situation 7. This is a weird situation that when I change the num by inspector at Play Mode, for example I changed the num to 3, it comes to be two situation:
    Situation 7.1. I change the num by Inspector and exit without press space (I can press space before I change the num by Inspector). It turns out to be that the num has reset to 3, and works normally as same as the data which I set to 0 at first.
    Situation 7.2. I change the num by Inspector and then press space to change the data by GameObject, and exit. It turns out to be that the num works persistently as same as the SetDirty function does.

    All that situations above really confuse me a lot maybe it's because I'm not the computer science major. Whatever, I have seen several videos and discussion about the ScritptableObject and Serialization. Someone said we should just use the SO at editor time and should not to change the data at runtime. But the official tutorial of Adventure Game use SO to save data temporarily between scene changes. Due to the strange behavior for SO at runtime, I wonder if there is someone can explain the situations I met above so that I can use SO more confident. Appreciate previously and thanks for giving suggestions about everything, for example , improving my English to let others read my issue easier, hah hah.
     
  2. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,020
    @quiet00903

    Hi there,

    I'm in the same boat as you - I pretty much did the same observations, I've used Unity some time now, but so far I've figured that:

    ScriptableObject is only good for such use that you store values and data to it, sort of like keep it as a storage location for your preset values.

    Also, as it is asset, it can be shared with many objects, doesn't have to be attached to GameObjects.

    It seems to be working only in editor, not run-time, so saving values to it seems to be not a good idea.

    I've gathered, it's better use Unity's JsonUtility, BinaryFormatter or PlayerPrefs to save and load your changed values.

    https://unity3d.com/learn/tutorials/topics/scripting/persistence-saving-and-loading-data

    If you are in need to get your "default values" for example for your sword, character or whatever, then get them from ScriptableObject.

    Also, in many Unity tutorials they give lengthy examples of using ScriptableObject, but for simpleton like me it feels like incomplete explanation about it;

    https://unity3d.com/learn/tutorials/modules/beginner/live-training-archive/scriptable-objects

    Anyway... most likely there is someone who can give a better answer... I've forgotten probably many things and got somethings related to ScriptableObjects wrong to begin with... IMO it's not well explained in the manual or tutorials I've watched.

    That is why I've just personally used text / binary files, values in prefab or where ever, then saved values to and load values from these files - adding ScriptableObjects in this configuration didn't seem that useful in these simple cases.

    Hope this helps.
     
    quiet00903 likes this.
  3. quiet00903

    quiet00903

    Joined:
    May 18, 2017
    Posts:
    15
    Thanks very much for your replying. You give me an idea that maybe I'm trying to abuse the ScriptableObject to do what it is not supposed to do. Just use SO as a storage of preset value seems to be more distinct. And if it is so, I think I should find a way to save the data temporarily at run-time. Because as far as I know (well, in fact it's just my understanding without deeply research) I should save the data to the binary file when I come to just like the savepoint in the game ,it seems to be inefficient if I use the binary file frequently. The ways I've seen to save data temporarily at run-time is the ScriptableObject in the official tutorial, the static variable of C# class and the GameObject that persist in the game like using DontDestroyOnLoad, I wonder what's the proper way to put the changed data in the game to the memory and put them to the persistent file when I comes to the savepoint. Now is 9:50 am and I'm so happy I get an advisable answer when I wake up, thanks again my "water friend" (it means someone who likes posing replies and themes and so on in Chinese) and I'll move on to learn the Unity, wishing I could find a job as a game developer someday : )
     
  4. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    833
    A ScriptableObject's primary benefit is the ability to drag references around in the inspector. Being able to work with it in the Inspector it makes it easy to apply polymorphic principals and configure settings without the need of recompiling or performing basic serialization.

    the values that you preset in it are set the first time to try to reference and will persist for as long as you keep a reference to it, as soon as you stop referencing a ScriptableObject Instance the data it had is reset to the default values on the next time Resources.UnloadUnusedAssets() is called, which happens when you enter/exit playmode, or load scenes, and a random times during play. The Adventure Tutorial likely kept using the reference while loading scenes which kept their data from getting caught in the UnloadUnusedAssets call. If you want a ScriptableObject Asset to persist its data across scenes you can either hold the reference while loading, or change its HideFlags to include HideFlags.DontUnloadUnusedAsset.

    changing values via the inspector, even during runtime, will save those values to file even when returning to editor mode. this is because when the inspector detects changes by the user, it saves the modified properties directly to the asset's file.

    If you want to save data stored inside a ScriptableObject between play sessions, then you'll need to serialize the data to a file. Which is a different tutorial entirely.

    What ScriptableObjects can't do. unlike Serializable Custom classes. ScriptableObjects generated internally in a Component won't save to prefab. All UnityEngine.Object classes are converted to guid references inside YAML, ScriptableObjects included. furthermore ScriptableObject Assets can't render scene references in the inspector (they'll still work fine if set via code, but the inspector will print out type mismatch for such fields)
     
    zhool, bguild, xiugong and 3 others like this.
  5. quiet00903

    quiet00903

    Joined:
    May 18, 2017
    Posts:
    15
    Thanks for your replying! Your explanation clarify most of the situation I met above and make me notice the Resources.UnloadUnusedAssets() which I didn't know before. If you can explain the SetDirty and the Situation 7.2 that will be perfect. The last paragraph seems to be a little too professional and I think I should review it after months. The tutorial you give me seems to be using the .NET Serializaton to serialize the System.Serializalbe Class data to the binary file (I watched that a month before and maybe I got a wrong memory), but I got an idea at http://answers.unity3d.com/questions/1315995/save-multiple-scriptableobjects-in-just-one-binary.html that I should not use .NET serialization to serialize the ScritableObject form Bunny83 who I believe is familiar with SO, So I expect your opinion on it. Whatever, you have given enough advice to broaden my view and I'm very appreciated, thanks again my "water friend" : )
     
    bowserscastle likes this.
  6. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    833
    SetDirty basically tells Unity that this Object state has changed and so should be saved to disk. By default setting fields in the Inspector internally calls SetDirty, hence the similar behaviour you're seeing. when working with a ScriptableObject you're basically accessing local floating memory. when its flagged dirty that floating memory is saved to disk, thats when it becomes persistent. Typically you should never be calling SetDirty directly, it was encouraged for some functionality in earlier versions of Unity, but in the current versions you're asked to shy away from it unless you have a very specific need that only it fills.

    Bunny is correct you shouldn't serialize/deserialize Unity Objects directly, they are tightly connected with Unity's managed side, and trying to serialize them directly simply won't work. However we don't need to serialize them directly, in fact even in the tutorial, they pushed all the data into a custom class and then serialized/deserialized that class.

    I typically like to create Bucket-like ScriptableObject. all it does is hold some data. say for example you could have a gamesave Scriptable object that hold the current progress of the game. then hit save, a utility function comes in and pulls that data into a cutsom class, packs it, and writes it to disk. then you choose to load a different gamesave. the same custom class reads the data, unpacks it and then passes it back to the same Gamesave scriptableObject. This "Bucket" is the same one that you used for the last save, just that now its holding different data.

    This GameSave asset is nice in that you can simply drag its reference everywhere and drop it into fields on certain components, because its a UnityObject (you can't drag custom .Net classes into fields). You get both a ScriptableObject and a custom class to work together to really help your workflow.
     
    Reahreic, chantey, irenya and 3 others like this.
  7. quiet00903

    quiet00903

    Joined:
    May 18, 2017
    Posts:
    15
    You really help me level up my understanding about SO. Thank yoooooooou a looooooooot! I'm so glad that I can engage myself to the game development which is my favorite with what a lot good guys like you instead of researching thermal-hydraulic effect at lab. I believe my friends at college will be surprised when they play the game from me someday. Hah hah. Thanks again my water friend!
     
    Rusted_Games likes this.
unityunity