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.
Hi @ironoak - Interesting question. I have researched this same issue recently. I don't have an answer for you, as I might not use (or end up using...) scriptable object at all for various reasons (skill level being one... and also the issue of template/unique item ingame). However, regarding lifespan of ScriptableObjects, I came across this interesting thread by @lordofduct, which might help you: https://forum.unity.com/threads/scriptableobject-life-time-object-identity-and-loading.508969/
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.
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.
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?
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.
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.
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): [CreateAssetMenu] public class FloatVariable : ScriptableObject, ISerializationcallbackReceiver { public float InitialValue; [NonSerialized] public float RuntimeValue; public void OnAfterDeserialize () { RuntimeValue = InitialValue; } public void OnBeforeSerialize () { } }
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.
@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
Code (csharp): public interface IUseStats { Stats Stats { get; set; } void TakeDamage(float amount); } public class Character : MonoBehaviour, IUseStats { public Stats StatsPrefab; // asset file (preset) public Stats Stats { get; set; } // unique, for runtime public void Awake() { Stats = Instantiate(StatsPrefab); } public void TakeDamage(float amount) { Stats.Health -= amount; } } public class BadGuy : MonoBehaviour, IUseStats { public Stats StatsPrefab; public Stats Stats { get; set; } public GameObject OtherGuy; public void Awake() { Stats = Instantiate(StatsPrefab); } public void Start() { OtherGuy?.GetComponent<IUseStats>()?.TakeDamage(Stats.MyDamage); } public void TakeDamage(float amount) { Stats.Health -= amount; } } 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.
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.
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.