Search Unity

ScriptableObjects usage question

Discussion in 'Scripting' started by ironoak, Aug 1, 2018.

  1. ironoak

    ironoak

    Joined:
    Sep 22, 2016
    Posts:
    7
    Just finish watching this talk called Game Architecture with ScriptableObjects and I have a question.

    When presenting the example of using SO to store Player's HP, the first thing which come mind is what happens when you start instantiating a new Player? A new Player would require it's own SOs for maintaining it's different stats. This presumable require calling Instantiate on the SO or calling SO.CreateInstance<>. This new SO seem to me to be difficult to keep track if you have many of them. You would have to call Destroy on them explicitly?

    Other than that SOs looks to be a very exciting tool improving the game architecture with.
     
  2. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    You'd never have the current hp value of the player in an SO. That would mean that losing hitpoints changes an asset and gives you less hitpoints the next time you enter play mode. That'd be madness.

    You'd store something like the players maximum hitpoints, or the maximum hitpoints at each level, or how much an item increases your hitpoints in an SO. In essence, all data that's not tied to a particular scene.
     
  4. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,521
    ScriptableObject's are handy because you can have the Prefab/Asset original strictly as a reference and create instances of it into your class instance.

    So if you want Health as a ScriptableObject then you would create an SO with the values, set them on the Asset file, then at runtime create an instance of that in the Player class so you aren't touching the Asset anymore, but an instance that is local and safe to manipulate.

    Thats just one way to do it. You could always just use them as dumb data and pull the values out and populate other variables in your class at runtime, saving you the trouble of using SO instances in your classes.
     
    eses likes this.
  5. ironoak

    ironoak

    Joined:
    Sep 22, 2016
    Posts:
    7
    In the example mentioned in the video, the SO is used as a kind of intermediary for any system which is interested in the variable (at 23m18s). It sounds neat, but perhaps it's a bad example?
     
  6. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Okay, it's too late to watch the entire thing, but that seems like going way overboard. It's an interesting idea, but my instinct is that it's disconnected to a degree where debugging will be needlessly hard.

    If you need that kind of seperation of concerns - which isn't a given - I think solving it though using interfaces and setting up dependencies on startup should get you a much more manageable workflow.

    Could be wrong. I haven't actually tried to have the player's HP be an asset. That they need to have a "please reset this on startup" variable to hack around the fact that it's values persist smells bad, though.
     
  7. ironoak

    ironoak

    Joined:
    Sep 22, 2016
    Posts:
    7
    It just seems to me if I use SOs to keep track of per Player info, I would have to manually destroy them when I destroy the Player. It doesn't sound very practical. Perhaps it was just an example.
     
  8. barskey

    barskey

    Joined:
    Nov 19, 2017
    Posts:
    207
    In the video, he describes using Scriptable Objects as variables. One of the biggest benefits would be that you don't need references to/from the various objects that are affected by that variable. In his example of PlayerHP as a Scriptable Object variable, a GUI health bar can update itself based on the value of PlayerHP, an audio system can change it's sound volume when player health is low, enemies can react, etc. all without caring who/what/how the PlayerHP variable changes.

    He mentions that you'd want to access a runtime copy of the initial value to prevent changing your initial value saved to file.
    Code (CSharp):
    1. [CreateAssetMenu]
    2. public class FloatVariable : ScriptableObject, ISerializationcallbackReceiver
    3. {
    4.   public float InitialValue;
    5.  
    6.   [NonSerialized]
    7.   public float RuntimeValue;
    8.  
    9.   public void OnAfterDeserialize ()
    10.   {
    11.     RuntimeValue = InitialValue;
    12.   }
    13.  
    14.   public void OnBeforeSerialize () { }
    15. }
     
  9. barskey

    barskey

    Joined:
    Nov 19, 2017
    Posts:
    207
    The question I would have is when does OnAfterDeserialize get called? Everytime the player prefab that uses the PlayerHP FloatVariable asset is instantiated? If so, then you wouldn't have to do anything - PlayerHP would set itself to the initial value on its own. If not, you would want a reset method in your FloatVariable class.
     
  10. OmenX

    OmenX

    Joined:
    Apr 21, 2016
    Posts:
    6
    @LaneFox How do you go about creating an instance of Health for each character in your scene, so.CreateInstance? I am running into the issue ironoak describes in the OP, where my player and the 4 or 5 enemies in my scene are all sharing the same Health value. It makes sense that what I am doing is adjusting the one and only value of Health and I'm pointing all of the health bars to this one Health stat. It sounds like creating separate instances of this stat value may solve my problem.

    Sorry to hitchhike OP, but I felt it was a relevant question to further describe how to work around OP's question
     
  11. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,521
    Code (csharp):
    1.  
    2. public interface IUseStats
    3. {
    4.     Stats Stats { get; set; }
    5.     void TakeDamage(float amount);
    6. }
    7.  
    8. public class Character : MonoBehaviour, IUseStats
    9. {
    10.     public Stats StatsPrefab; // asset file (preset)
    11.     public Stats Stats { get; set; } // unique, for runtime
    12.  
    13.     public void Awake()
    14.     {
    15.         Stats = Instantiate(StatsPrefab);
    16.     }
    17.  
    18.     public void TakeDamage(float amount)
    19.     {
    20.         Stats.Health -= amount;
    21.     }
    22. }
    23.  
    24. public class BadGuy : MonoBehaviour, IUseStats
    25. {
    26.     public Stats StatsPrefab;
    27.     public Stats Stats { get; set; }
    28.  
    29.     public GameObject OtherGuy;
    30.  
    31.     public void Awake()
    32.     {
    33.         Stats = Instantiate(StatsPrefab);
    34.     }
    35.  
    36.     public void Start()
    37.     {
    38.         OtherGuy?.GetComponent<IUseStats>()?.TakeDamage(Stats.MyDamage);
    39.     }
    40.  
    41.     public void TakeDamage(float amount)
    42.     {
    43.         Stats.Health -= amount;
    44.     }
    45. }
    You're either addressing the asset file (original) or an instance of it. It's that simple. Make an instance, do what you want with it.

    Statinator (Free, in my sig) uses this approach.
     
    eses and karl_jones like this.
  12. OmenX

    OmenX

    Joined:
    Apr 21, 2016
    Posts:
    6
    Thanks @LaneFox, Instantiate works great, but I'm having trouble using it with my specific use case. When using ScriptableObjects for individual Stats or Attributes, what's a good way of instantiating an instance of each? Should I put them in a List and iterate through the list instantiating [ i ] or would it be best to just make my Stats into standard int/float variables?

    I like the idea of ScriptableObjects, because I'd like to pull a couple friends into this project that are not programmers. I think the Create menu and drag n drop of scriptableobjects will make sense for them.
     
  13. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,521
    If you want an example of this approach working, just download Statinator. If you're using additional SO's inside your Stat wrapper SO then the asset/instance still applies.