Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Feedback Scriptable Objects and dynamic data. Yes or No?

Discussion in 'Scripting' started by stephenq80, Jun 25, 2021.

  1. stephenq80

    stephenq80

    Joined:
    Jun 20, 2018
    Posts:
    34
    Hello and thanks for readying my post.

    I have a question that I hope someone could help me with.

    Should scriptable objects be used to handle dynamic data? I am confused about this question because of the following reason:

    On Unity's website here: https://unity.com/how-to/architect-game-code-scriptable-objects

    they use an example of using a scriptable object as a float variable for the player's health. Now, I would imagine that in any game, the player's health would change a lot. It would change during combat, it would change when the player levels up, etc. So, I would consider that a dynamic piece of data.

    However, I have read numerous times, both on these forums and other places, that scriptable objects should be used as "data containers" that hold data that WONT change.

    So, does anyone know what the answer really is? Or, has anyone actually deployed a game that has used scriptable objects to handle dynamic data and can speak to their experience?

    Thank you!
     
  2. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    Yes! But only sometimes. It's complicated. I actually asked a question similar to it a few days ago in my thread

    https://forum.unity.com/threads/scriptableobject-data-sharing-sanity-check.1130794/#post-7270675

    One thing about ScriptableObjects is that when you change the data in your game, the changes will persist in between plays, so it's something you have to be careful about. However, you can use that feature for things like transferring data between scenes and the like. In the health example, the idea is that the PlayerHealth is set by some Player script, and allows other "dependent" scripts to not actually need the Player script, only the ScriptableObject.
     
  3. stephenq80

    stephenq80

    Joined:
    Jun 20, 2018
    Posts:
    34
    Thanks for the response and the link. I read your post. Good information. I THINK I might be ok. I don't want to use them for anything crazy. Here are my thoughts:

    I want to use them similar to the float variable example for my player's health, stamina, and mana. I want other systems to be able to access these values without needed a hard reference.

    However, I also want to do the same thing with my players inventory (a List) and equipment (an array). Again, I want other systems to be able to access these without a hard reference.

    In these instances, the fact that the values can persist between plays kind of seem like a side benefit.

    Any other thoughts regarding pros and cons would be appreciated
     
  4. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    Sounds good! In general, I've found that almost always whenever you would want to use a Singleton, you can use a ScriptableObject instead.

    What you have to be careful with is that any script that has a reference can edit the values if they're public, so if you made it public so that your Player script can change its health, then any other script can do that too. Nothing to worry about usually, but it's good to keep in mind.
     
  5. stephenq80

    stephenq80

    Joined:
    Jun 20, 2018
    Posts:
    34
    Thanks a ton. Good tips to keep in mind. And actually both my inventory and equipment classes used singletons before I understood scriptable objects. I refactored the code so that the core containers (the list and array) were scriptable objects. Doing that, along with using scriptable object global variables allowed me to get rid of pretty much every hard reference in my project thus far. Then I got worried due to some things I had read. But, I think I may be ok. Thanks again.
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Using it as dynamic data is perfectly ok, it's just you need to consider what's actually going on in memory.

    1) When you create an instance of your ScriptableObject as an asset in your assets folder. That specific asset behaves as a single instance. Any and all references to it are the same object. So if 2 scripts ref the same asset, any changes one does can be read by the other.

    2) ScriptableObjects don't play by the same rules as scenes when it comes time to load a new scene. This means the data in the SO will carry over between scenes. This means if in Scene A you change the player's health, load a new scene, the health made it over. This can be good if you know that's what is happening and it's what you want. For example if you have a series of rooms as different scenes and you want the Player's health to travel between them even though a new instance of the player's avatar/gameobject is spawned in each room. But it may be a hinderance if you are designing a game where you expect reloading a scene resets the health (some people do the scene reload technique to reset the player to the beginning and try again).

    3) 1 & 2 effect how things behave when you're in the editor. Because the asset is independent of the scene, then any changes you do while playing the game are actually effecting the asset. So when you stop playing, the asset reflects the most recent state.

    A way you can deal with 3 is that the values that are serialized are NOT dynamic, and the values that change aren't serialized.

    Something like this:
    Code (csharp):
    1. public class HealthContainer : ScriptableObject
    2. {
    3.     public float MaxHealth; //this is serialized
    4.     [System.NonSerialized]
    5.     public float CurrentHealth; //this is not serialized
    6. }
    Then in your player script when a player is spawned for the first time you might do:
    Code (csharp):
    1. public class Player : MonoBehaviour
    2. {
    3.     public HealthContainer Health;
    4.  
    5.     public void OnPlayerSpawned()
    6.     {
    7.         this.Health.CurrentHealth = this.Health.MaxHealth;
    8.         //do other things that pertain to the first time the player spawns
    9.     }
    10. }
    This OnPlayerSpawned method is called when your player is supposed to register as coming alive. Like at the beginning of a level, or when they die and get teleported back to a checkpoint and reset... the whens/hows is up to the sort of game you're making. You could have easily done it in the 'Start' message if that was when you wanted to do it.

    ...

    The general idea here though is that MaxHealth is the static data that is configured on the asset in your project folder. The CurrentHealth is the dynamic data that is modified during gameplay and can be accessed from anywhere that has a reference to the asset.

    Added benefit here also is that you always have cached what the MaxHealth would be, which is common for health scripts. You know... so you can heal up to max.
     
  7. stephenq80

    stephenq80

    Joined:
    Jun 20, 2018
    Posts:
    34
    @lordofduct thanks very much for the insight. To be honest, the things you laid out in 1 & 2 are actually some of the things I saw as benefits that drew me to scriptable objects and to refactor my code base. Much appreciated.