Hello, I’ve recently started playing around Unity and it’s ScriptableObject class, I’ve seen in many videos how people use them for almost everything, I’ve seen some tutorials on how to create an ability system with scriptable objects and I’ve tried some of my own variations to see how they work but I’ve encountered some weird things along the way and I was hoping someone was willing to enlighten me regarding this issues :
ScriptableObjects are assets but in “Unite Austin 2017 - Game Architecture with Scriptable Objects” they used it as a Health reference (FloatReference more precisely) and If I understood correctly they won’t get “reset” on scene change, that’s good and all but when for example the health reaches 0, it saves to the disk, so next time you start the game your HP will still be 0. How do you handle this cases usually ? It’s a bit weird for me to store a variable such as HP, that needs to get reseted every playing session in the assets.
I’ve made an AbilitySystem that derives from MonoBehavior, which stores abilities (scriptable objects) in a List, tried making 2 players and assign them different abilities but the abilities were generated only from the first player.Although I had an Initialize(GameObject holder) method, which seemed to make sense to me, only the first player actually shot the abilities and the weird thing is that the first player generated both player 1 & 2 abilities. Is my approach of the ability system wrong ? I wish to have the freedom to assign abilities to whatever game object I want.
Taking into consideration the fact that scriptable objects gets stored on the disk, how does one handle temporary changes to a ScriptableObject ? Given the next example : I got a PlayerData scriptable object, a Player MonoBehavior that references a PlayerData object. My player “levels up”, it gets saved to the disk. Then the next game its level will be the previous one. Again, how does one handle this kind of cases ?
I believe the same talk (or a related one) briefly goes into that issue. You need to add event handlers for things like scene load or enter playmode to reset the original value. I think that is why their system has both a design and runtime value.
ScriptableObjects are global. Unless you call CreateInstance in script the SO for “Ability A” will be the same for both players, and if you do not consider that in code then either player running the ability will also run it for the other player. Perhaps that is the issue here?
See 1. Note that this only happens in the editor. SO values are reset in builds every time you run the build because they are read-only in builds.
Thanks for the quick reply, that makes a lot of sense now (regarding event handlers) but I did not see in the video design & runtime value tho , did I miss it somehow ? Also my bad I forgot to add that I did try with CreateInstance and the same thing happened. Thanks a lot, at least now I’m having some ideas of what to do.
Hmmmm maybe I remember our own system from a couple years back. Now that I think of it … I implemented that because of this exact reason. Upon entering playmode, the design-time value was copied to the runtime value. In the Inspector you saw and edited the design-time value but scripts always accessed the runtime value via property.
That t alk has been discussed here before, basically that approach is not that good. https://discussions.unity.com/t/892334/43
You lose a lot of safety by doing something like that.
Yes.
It does not. Initial state of scriptable object is baked into your build, and the game cannot overwrite it IN THE BUILD.
You can overwrite (and save) it accidentally in editor, though.
So it is a global object. And when you save/load you just access it as you would a global object.
On 1.)
You are not the first in the forum to get a bit confused by that talk. Honestly personally I’d rather ignore that approach or maybe learn from it but apply way differently. What the developer there was trying to do was efectively (mis)use SOs as a way to do full on visual scripting to a degree.
That was a great demonstration of SO’s features (which were fairly new at that point), but it remains that: A demonstration. Actually developing a medium sized project that way brings a lot of hassle and seems not worth it.
It’s important to not fall for “I got a hammer, now everything looks like a nail”
2.)
Character abilities on the other hand are a perfect example for what SOs have been established themselves for now.
You might use them slightly wrong though. SOs are best as configuration objects designed by the developer. They shall not really be actually modified during the game. At least I don’t see why that would be necessary for an ability system. At runtime you have gamenobjects at your disposal after all!
So use SOs to just store info about the abilities (one SO instance per ability variant. You can also use inheritance), like what damage they do, which Prefab to instantiate for the damage animation, what cooldown it shall have etc.
Then you need a monobehavior which gets this SO as a variable and reads the data when an ability shall be used. That monobehavior shall not write anything in the SO though, because since there is only one SO, that would be global and thus cannot be different per character etc.
3.)
Do not use SOs for that usecase. The easiest approach are PlayerPrefs.
@neginfinity I see, that makes sense now. Thank you.
@DragonCoder Regarding the character abilities, I liked the approach a lot but I’ve had couple of issues with it :
I get that SOs are data containers (configurations) but what I’m trying to achieve is simple : have a base configuration of a given skill :
ThrowShuriken for example, it derives from ProjectileAbilitySO (which has things like angle offset, projectile count, speed etc) but then it rained on me : how do I target a specific GameObject , so that the shuriken will fly in the target’s direction instead of a random/preset angle ? I’ll just make a target property inside the ProjectileAbilitySO, if it’s null - all good, throw in the given angle offset, if it’s not then just calculate the angle required between the target & the holder. All this is a logic contained in the Trigger method which is an abstract method defined in the AbilitySO base class. I’ve got the logic down then comes the next step => actually setting the target. There’s also the problem that on every level up my character gets I need those properties in the SO to actually increase (level 2 gets +1 more projectiles, leve 3 gets +2 and so on) and I’m a bit confused on how to achieve all this
I’m living under the impression that I’m mis(using) like you said the entire concept of SOs but I’ve seen many examples where logic is actually implemented inside a SO when everyone calls it a data container, now that’s kind of confusing.
Would you mind sharing a few tips regarding the efficient way to achieve what I want ?
P.S Thanks a lot to the both of you, I’ve started learning few days ago , reading/watching everything I could regarding SOs, unity events etc but It’s kinda overwhelming (for now at least)
You can get rid of inheritance completely and create ProjectileConfiguration scriptable object that will describe ALL possible weapons in your game through parameters.
Said object can have a bool/checkbox saying “homing”. Your launcher script would check that, and if it is set, aim wherever it is supposed to go.
You got distracted by inheritance. You do not appear to need inheritance in this case.
Sorry for misleading , ThrowShuriken is an asset (it’s actually A ProjectileAbilitySO).
Regarding the Launcher, it’s supposed to be a monobehavior that has a reference to the ability that it’s using ? Seen that example but couldn’t figure out why it’s needed since I can just have a Trigger which can instantiate the object, seems like I was wrong again
For the first issue, store the target in your shooter script attached to the player. Not in the SO.
Also don’t store an aim-angle but just something like “max_angle_inaccuracy”. An angle that’s mumtiplied with a random value between -1 and 1 on every shoot so the shuriken fly more randomly. You add this value to the perfect aim_angle which you calculate the moment the player shoots.
For making values in the SO dependent on a player state, how about factors?
If a higher level player should shoot faster shuriken, then have a “level0_speed” and “speed_per_level” setting in the SO.
Then when you need the currently appropriate speed, use:
shuriken_speed = so.level0_speed + player.level * so.speed_per_level;
People on the web and in reality just are not perfect xP There are still way too many professionals who totally overuse pointers when programming Cpp for example.
In the end, people have formed their own ideas for what works for them. The thing with SOs is that you need somewhat in depth knowledge before you can meaningfully use them for anything but read-only containers.
I’m probably the greenest guy in the room, and recently ran into the old SO’s dont save between sessions issue. However, what i did in the end, rather then change all my SO’s, and rewrite everything, is simply added some routines to the SO’s that save, fetch, load their own data @ runtime. Meh, maybe i will pay for it later, but for now , it seems to be working out really well. If anyone has any pointers to caveats i should watch for, i am always interested.
Yeah, it is really easy to make that assumption because that’s how they behave in the editor, and when you develop for desktop, you just do not do a build early on.
I actually wish Unity had “stiffended” SOs a bit and provided a toggle in the inspector like “editable_at_runtime” which, if not selected, automatically makes all members read-only (or at least reset the values when ending playmode). That would bring the message across and also prevent beginners from falling into traps.
What you have done there pretty much is “serialization”. Have you written manual methods to write down every value into a file, like what one would do with PlayerPrefs?
Since Unity has built in serialization: https://docs.unity3d.com/Manual/script-Serialization.html
That allows you to transform an object into a JSON string which you can then write to file. The nice thing about that mechanism is that it works equally with SOs as with any other C# class like Monobheaviors and even Structs (with a few limitations, but they apply to all the same).
If i am following correctly, yes i think i have. Everything in my SO’s is loaded / saved etc ,. and read / written from within the SO itself. At certain intervals in game play, and on load ofc.
Just in case you haven’t read through the thread linked earlier the system that is showcased in that talk is just a proof of concept not a system that should be used out of the box as it’s missing major functionality (discussed briefly in his blog post also linked in that thread).
If you still want to try out a system like that I recommend checking out this one as its a more complete one with its own Discord community.
A lot of the Scriptable Object talks cover workflows that end up being a poor replacement for proper Visual Scripting in Unity. Stuff like designer accessible workflows, quick iteration, no compile waiting time, globally accessible variables, etc… The problem is, Unity still doesn’t have proper Visual Scripting either.
Value is only retained while it’s in memory. On target platform, if you load a scene that doesn’t reference the same SO, it’ll be automatically unloaded and reset if you don’t enforce it to be retained via other means. The value will also reset between play sessions - quitting the game will reset the values.
Established Scriptable Object frameworks like Unity Atoms use VariableResetter component, where you manually plug in variables that need to be reset between two scenes that do reference the same SO asset. There are also variations of default value and conditions on when the variable is reset to its default value.
In my opinion, this type of approach to variables is more pain than gain. The asset spam becomes a big problem for any project that’s not a small mobile game. And subasset workflows in Unity are poorly documented and poorly supported.
You can have ability List with [SerializeReference] abstract class. SO workflow talks came a long time before [SerializeReference] was available. I find working with [SerializeReference] to be a much better experience. Manually instancing SOs is just redundant and needless extra complexity.
The value is only retained as long as it lives in the memory. It’s not saved between Play sessions on target platforms. That’s one of the problems with SOs - they behave differently between the editor and the final build. Multiple play sessions in the editor is not the same thing as multiple play sessions of the final build. Test on platform often, doubly so when employing scriptable object workflows from the famed talks.
Personally, I’ve tried rolling my own SO architecture implementation and tried Unity Atoms several times and I always ended up working on tools more than the game. This approach has many problems that require a lot of over-engineered solutions to keep it sustainable long term. And many a problem are not really solvable like the asset spam.
The way to be able to effectively connect variables in the editor, like Atoms seems to provide, is something that I actually wish to be able to do. Normally am everything but a fan of visual scripting, but it happened often that I had to add a whole script reference just to access one single value…
In fact I remember that I expected such a feature when I tried Unity for the very first time. It seemed so logical to be able to just drag two variable fields from two different game objects together and connect them.
Of course that can very quickly be overused. I would limit myself to no more than two or three connections to the same script before doing it more solid via a reference.
Gotta see whether Atoms can just be dropped in for such a feature without having to use it as the main project architecture.