ScriptableObjects Will Ease Your Sorrows
Before You Read
I made some discoveries while writing this blog post, so if you make code based on what you see here, it may not work. A made a follow-up video on youtube explaining why you might be having issues.
This is the video —> https://www.youtube.com/watch?v=vpjn30Nz3wg
There is also a paragraph at the bottom of this page explaining this as well.
Another Episode of Rambling ᕕ(⌐■_■)ᕗ ♪♬
Most people know ScriptableObjects in Unity Engine for their ability to easily create your own “File Types”. Where you can store some info in a nice package that you can then pass around to objects in your game. Since ScriptableObjects (Which I will abbreviate “S.O.” or “SO”) don’t need to exist inside of a scene like MonoBehaviour classes. They can effectively be used to store data or logic that persists across all scenes and can act as a “global container” that your game logic can rely on, and know that it will be there even after a scene switch.
I have mostly used S.O’s for their ability to store data to file, but they can do more than that because of how they work on the inside.
A lot of these realizations came to me when watching some Unity official videos on S.O’s:
https://www.youtube.com/watch?v=WLDgtRNK2VE
https://www.youtube.com/watch?v=PVOVIxNxxeQ
And after watching these videos, I realized…..
ScriptableObjects can do more ᕦ(ò_óˇ)ᕤ (゜ロ゜)
With anything related to the topic of file saving, there is the idea that the data can both exist in a file, but also in memory (your RAM).
A png on your computer is not stored in the same way that an image is stored in RAM when you are editing it. I mean, png is a term exclusive to the data stored as a file. Images can be stored as a file in a variety of ways, but if you want to edit that image, you need an algorithm that can read the file and translate it into a “raw” format that allows for easy editing in a program. This is the topic of serialization and deserialization; the process of converting a data structure in RAM to data stored in a file on a drive, and vice versa.
So if you are making a game, and you have created a png for the player, that png has to somehow be read by your program and converted into a raw “Texture” format that can then be understood by either OpenGL or DirectX (the thing that bridges the gap between your CPU and GPU). That format is also consequently easy to edit because it is just a list of pixel colors.
So you could also apply effects to the image on the CPU, and then that would be rendered by the GPU, but it might be slow for high res images.
Anyway, the data now exists in two places, your drive, and your RAM. In the case of Unity, a png, jpeg, bmp, etc will be automatically loaded and turned into a “Texture” object, which contains all necessary data for rendering. But the Texture class does indeed have functions that can be used to manipulate that data or do other things. You could even create a Texture without ever needing it to be associated with a file.
This is how you could start to make a cellular automata simulation if you wanted to. ಠ‿↼
But the point is, the same idea applies to ScriptableObjects. You can create them without needing a file, and they can contain functions that can be used on their data. So you could have static functions as well.
I recently discovered that the “Editor” class in Unity (which is used for custom editors) actually derives from ScriptableObject. Which I find interesting. Realizing all these different things has sort of open my mind to using S.O’s in a couple of different ways. One of which being “Scene Transcendent Systems”.
If you are building a game without an engine (from “scratch”, if you will) then you might not have this issue, because you have direct access to the code that runs the “Main Game Loop” of your program. So you can add your own high-level systems however you want. But of course, you don’t have the luxury of having an entire engine with rendering, physics, and other basic features already ironed out for you.
So for people working in unity, being able to have some logic or data that persists over all scenes is important. Because maybe it servers a greater purpose. ¯\_( ツ )_/¯
Most of the times, people will make Singletons that exist inside the scene, if they only need to exist in that scene. And if they need to exist everywhere, they can use “DontDestroyOnLoad()” to move the object into a scene that is never destroyed. But of course, the object needs to be created first, and you might not want to rely on a particular scene being loaded in order to have that Singleton exist. You want it to exist IMMEDIATELY. ୧༼ ಠ益ಠ ༽୨
This is where ScriptableObjects come into play, and where my rambling comes full circle.
MonoBehaviours depend on a scene, but ScriptableObjects don’t. So you can use them as a place to kickstart certain systems in your game no matter what scene they exist in. They are a way to “hook in” to Unity’s internal game loop and run logic before anything else happens, Quite nice! It seems like a lot of people have wanted to do this before and they couldn’t find a way. But I think I may have found a way, and that is why I am making this blog post.
This is how we got to this point…
While working on the Dwarven Kingdom for the game, I wanted to test and see if the player prefab itself would just work if I dropped him into the new scene, which doesn’t have a lot of the “Manager Level” scripts that the main Cave Scene has. And when I ran the game, everything almost worked, until the player started walking, and it tried to make footstep sounds, but it threw an error because there was a Manager Mono class that pooled a bunch of Sound Sources for me to use on-demand.
To me, this was a sign that my code needed to be improved. I had previously watched some videos talking about how S.O’s could be used to replace the typical Singletons in some cases. Along with other discoveries, this has led me to want to redesign my “Sound System” so that it is 100% scene independent using ScriptableObjects. For a moment, I thought it might not be possible to do it the way I was thinking but it appears I may have figured something out.
This is what I’ve discovered:
First of all, this needs to be said before anything else, but you will need to create a scriptable object instance in order for this to work. Now, you could probably find a way for this to work without doing this, but for my situation, this works fine. It requires a little “getting over” the fact that you have to manually create a file, but that may be better than having it be automatic.
However, I did just discover that you may be able to make it so the code automatically creates the S.O file if none was found. And it will do this check every time the code is recompiled. But even then, you still need the file.
Anyway, once you have an instance of your scriptable object, you can now store serialized data inside it. Like references to other prefabs, colors, data. It’s basically like the different sections in the “Project Settings” menu. And in version 2020.2 you can now make pop-out windows for things and dock them so you have eternal access to them! (Click on S.O file in the project view, and press Alt+P)
As long as you have this S.O file in the project directory, you will now be able to hook into Unity and have code run when the game starts.
So now, I am going to explain how all the different event functions work with ScriptableObjects. (Note, I am probably missing some information, but this is what I’ve learned through all this.)
Awake() - Executes the moment a S.O file is created, but only then.
OnEnable() - Executes when entering playmode, and when scripts recompile
OnDisable() - Executes when entering playmode, when scripts recompile, and possibly when an S.O file is deleted. (when your code recompiles or in playmode, Unity will call OnDisable() and then OnEnable() after it, possibly to reload the data)
OnDestroy() - Executes when the S.O file is destroyed, so both this and OnDisable should execute. I suggest testing this for yourself, but for my example, it’s not necessary.
With all of this information being noted, I can now show my code and how everything is being orchestrated.
But first, I did forget to mention one thing. For this to all work, we need one of these events to only execute when the game starts. OnEnable() does get called on start, but also when script reloads happen. So if you create objects inside the event, they will be in edit mode. Also, with an odd twist, if you do “Debug.Log(Application.isPlaying)” in the OnEnable() event, it will actually print out “false” even when you press the play button. So this code must be called just before the game is considered to be “playing”. Which I suppose is good if you want something to happen at the very beginning, but not good if you’re trying to distinguish when your not in play mode. At this point, I was stumped, but then I remembered: “There are attributes!!!!“. In particular, there is one called
“[RuntimeInitializeOnLoadMethod]“.
This allows for a static function to be called when the game first runs, so I tested this out, and indeed, “Application.isPlaying” is true during that point. In fact, the function only ever is called when the game runs, so I don’t even need to do an if-check. With all this being said, we can now build our
Scene-Transcendent-Singleton \(゚ ー゚\) all hail singleton
This is my new Audio System in its simplest form. When you start the game, the first thing that gets called is “OnEnable()”, and then after that my special “Initialize()“ function gets called due to the “RuntimeInitializeOnLoadMethod“ attribute. So at that point, “current” is set and you can access the serialized data. At which point I am creating 100 AudioObjects which internally call “DontDestroyOnLoad” so that they are in a persistent scene, no matter what scene is loaded first.
This way of implementing singletons might be useful when you want certain things to happen on start. Maybe you always want a certain Scene to always load first. But you want a nice inspector setting to allow you to turn that off so you can run a scene independently. Or for storing any other totally scene independent stuff.
I’m certain someone has probably already come up with a similar solution before me, but I’m impressed and happy I came up with this one.
As a final note \(・‿・ )
I did previously say It may be possible to auto-generate the S.O file when you try this pattern, so this is my attempt at it.
This does require “using UnityEditor” but that’s because you only want this to run in the editor itself. So you are also gonna have to add the ALMIGHTY PREPROCESSOR DIRECTIVES to assure this code is only compiled for the editor, otherwise, when you try to build an exe of your game, Unity will be like: “HALT! YOU VIOLATED THE LAW!!!“
There are probably more elegant ways to go about this, like creating a special class that inherits from ScriptableObject (you may call it…… ScriptableSingleton) and you can have some code that makes sure all your Singletons have 1 asset file in existence.
Also, the “InitializeOnLoadMethod” attribute is different from the other one, it only calls the function when scripts are recompiled.
As a final-final note,
I know that singletons are kinda bad in some cases. And they can cause various issues. And I’ve been calling this thing a “singleton” when it doesn’t necessarily have to be. What I’ve shown can be used to run some code at the very beginning when your game starts, and It will happen no matter what scene is loaded first. And along with that, you can also access data that is stored in the serialized ScriptableObject. In my case, I am able to create Sound Sources and play them on-demand everywhere.
And that is it! (╯▀ ͜͞ʖ▀ )╯︵ ┻━┻
Thanks for reading again!
I’ve sort of stopped doing blogs for some time because when I do them, I tend to type for a long time. And I don’t always have the time or patience to type it all out. So it’s only when I have something to talk about that I think people are really gonna be interested in. And I have a feeling this will be one of them, because of how “general” ScriptableObjects are, and useful they can be.
So if you’ve been following these blogs, thanks!
If you want to support us, but your pockets are empty, share our website with people you know! If you know someone who might be interested in Below The Stone, show them the website. If you think this blog has some useful information in it, share it with people who you think this might help. Share it on your discords!
Thanks again!
And unfortunately…….
I decided to sanity check myself, and I built the game with an empty scene to test if it would work. Unfortunately, it does not work for whatever reason in the built program. This is due to OnEnable never being executed, which is confusing, and annoying. It exists and it works in the editor, but not when it’s made into an exe. I spent a long time writing this blog so I’m not gonna delete it, but I will update this if I ever find a solution to this. I know there is probably a way, but for some reason, the S.O object isn’t being recognized. If anyone has any ideas, it will be much appreciated.