ScriptableObjects, Explained | Unity Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what the heck are scriptable objects anyway in this video we're going to take a look at what they are how they work and some real-life use cases that i've personally used in my own games hey chris here from mom academy here to help you yes you make your game dab dreams become a reality we're going to jump right into it today what are scriptable objects and what is the problem that they're trying to solve a scriptable object the best way to think about it is a data container with some extra functionality we can store things like primitive types class instances prefab references basically any type of data that we need for our game if you take a look at the unity documentation the number one thing that they say that you would use a scriptable object for is housing data to reduce your memory footprint of your game and while this is a really good use case for it there are more use cases but let's focus on that one first the critical thing is that we're trying to house non-changing data because whenever we use a scriptable object it's effectively like a static class there's a few subtle differences between a static class and a scriptable object one of which is the scriptable object we have an instance of and remember static classes we cannot create instances of but whenever we're referencing that one scriptable object we're reusing that across probably multiple different components or game objects different scripts and as a result as we change the one value on the one scriptable object that's going to impact every object that references that scriptable object so i'm saying it's kind of like it's static because we have a lot of stuff referencing that one static instance since we need to create an instance for that scriptable object how do we do that creating scriptable objects isn't immediately clear how do you do that because whenever you create a c-sharp script it gives you a mono behavior we're going to follow that same process to create a scriptable object because we need to define the instance type of that scriptable object that we want to use before we use it so remember we want to create it like we have a prefab we want to create an asset scriptable object that we're going to use in our project we're generally not going to create new ones at runtime that we're going to store and try to reference later that's something that we generally don't do so let's walk through a real use case of how do we create a scriptable object first we're going to right click in the project panel create c-sharp script and call it whatever we want in this case i'm going to use enemy and then whenever i'm making a scriptable object i always suffix it with scriptable object i do that just because i'm probably also going to have an enemy c-sharp mono behavior class and if i have an enemyscriptal object i can't name them both enemy without making them in different namespaces it just gets really confusing so i always just label it enemyscriptal object or whatever scriptable object if it's going to be a scriptable object once we've created that c-sharp script let's open that up in visual studio and a really common mistake whenever people are just getting into using scriptable objects is they forget to change this last piece of mono behavior to scriptable object so we're going to extend the scriptable object class instead of the mono behavior class if we don't do this the next step is not going to work correctly step two is we need to add create asset menu attribute to our script and that's on the script level at the top level right above the class the create asset menu attribute accepts three parameters the first one is the file name and that's the default file name that we want to provide whenever we're going to create that scriptable object i usually just use the class name so in this case i just use enemy not the scriptable object piece just enemy number two is the menu name this tells the unity editor where to place this creation item scriptable objects are always under the asset create menu and then from there wherever we define in this menu name is where it's gonna go so if we provide something like enemies forward slash enemy then we're gonna get a new menu that says enemies with an enemy element underneath it that way we can group common things together like maybe we'll have enemy attack configuration some other enemy configuration type and we can put those all together underneath the enemies submenu if we just provide enemy then it's going to show up in that big list of create and that ends up getting kind of annoying if you have like hundreds of items there that's why we can create these sub menus to make it a little bit more clean and consistent the third one is the order it specifies where on this asset menu should it show up the lower numbers are going to show up higher on the menu the higher number is going to show up lower and if you leave a gap of more than 10 you'll get a line in between them so that way you can kind of logically separate okay these things are grouped together these things are grouped together and these things are grouped together just leave a gap of more than 10 and you can do that if you leave them at just straight numbers 1 2 3 four five six seven eight nine then you'll get all those in that order that you specified with no separator between them now we've talked about how do we create a scriptable object let's look at what kind of data do we put into a scriptable object whenever we're doing that we need to remember that whatever we put in here will be bundled into our build we will only retain changes through runs that are made via the unity editor including in play mode and it will be effectively static for anything that references this particular instance so whenever we're trying to think about what kind of data do we want to put into a scriptable object think about is this data that's not going to change or that if it does change it should apply to all of my enemies let's use this exact use case here so something like move speed maybe that makes sense to put it onto the scriptable object because i want all my enemies of this type to move at the same speed i could put in base health because i want all my enemies to start with this amount of health but i'll need to track the health of each individual enemy on the enemy script because if i subtract from this then it's going to affect every single enemy not just that particular one that got hit we could also put things like the height of the enemy and let's put something to an attack configuration so i'll make a second scriptable object here just to make it kind of make a little bit more sense and show the power that we can have reusable configurations that are applied to multiple different triple objects so let's let's take this example because i'm getting a little ahead of myself here if we're going to take an attack configuration i'll define that maybe i have two kinds of enemies that i want to attack the same kind of way like they're gonna they're gonna do the same thing they're not really meaningfully differentiated from one another what i can do in that case is create one single attack configuration and put in things like the attack delay the attack damage attack range this kind of configuration and only create one of those so that way if i want to change the behavior of how does this group of enemies attack i just change that one scriptable object base enemy attack configuration if i change that one then anybody who's using that will also be affected so that's a really cool way that i can reduce the amount of changes i have to make to update how my enemies are behaving this makes it very easy to test different configurations and determine does it actually make sense for these two to share configuration if i don't like how one of them is configured what i can do is simply create a new scriptable object adjust the values and link it to the previous enemy that i want to now attack a different way this is why a lot of the time i'll say that using scriptable objects is making a configuration driven game instead of more like a hard-coded one where you're maybe setting values directly on a static class or just on the script itself or through prefabs where that kind of stuff can get lost very easily or it becomes really cumbersome to manage that kind of configuration this way we've grouped all of our configuration for that enemy in one place all the configurations are an attack configuration in one place we can structure them where we have them all in the same folder or maybe if we have too many configurations we can start having a folder structure to make sure that it's organized and easy for us to maintain because even in my game that only has like four different base enemy types and then some different boss configurations then i have new configurations for spawns that some of the bosses can do and just ended up creating a lot of different scriptable objects so i needed to create some folder structure to keep it organized because it quickly grows out of control whenever you have many different types of things that need to be managed through a configuration close the loop on the basics here everything we defined in the scriptable object we do not need to define on the base class mono behavior that we're going to use so like the enemy doesn't need to have a move speed or a height or anything like that it just needs to reference the scriptable object enemy scriptable object that defines the configuration for that enemy type now we've reduced let's say we have 100 enemies instead of having 200 extra floats we only have two floats because we don't need to copy those values on every enemy and most likely your enemies have more than just two things there's probably a lot of configuration that goes into them this ends up really reducing the memory footprint of your game which is what unity was saying in the documentation is one of the big advantages of using a scriptable object so how do we add that scriptable object reference to a live mono behavior we'll make a new c-sharp class called enemy that means it's going to be attached to a game object and we will have a discrete instance of this enemy running around somewhere in our game what i'll do is add a enemy scriptable object configuration in a private in health our enemy probably has more configuration than this but we'll just start with this to showcase what we're talking about today since each enemy needs their own health remember that this is really important we keep the health on the enemy itself and we do not modify the scriptable object health because that's going to override it for every enemy on start what we'll do is assign the health to be the base health from the enemy scriptable object that way at the very start after this object is created we'll make sure that we start out with the correct base health and our enemy will be able to have that life subtracted from them without it impacting the configuration that we've set up on the scriptable object now all we need to do is create a prefab for our enemy with the model and whatever other behaviors it needs attach that enemy script and drag in the inspector for that prefab the enemy scriptable objects for that particular instance to that enemy circle object configuration that we added the enemy script based on how we set this up you can create those enemy scriptable objects by going to asset create enemies enemy that will create a new scriptural object called enemy we can rename it whatever we want it to be set up the values and then attach it to each game object prefab that we've created for the enemy the same goes for the attack configuration we go to assets create enemies attack configuration get a new attack configuration hook that up to whichever enemy instances we want it to be associated with and that's it really but chris i have components in my game that cannot read the scriptable object value and they have their own settings for each component like the navmesh agent can't read your base enemy speed can i still use the scriptable object approach of course you can you don't have to do it this way this is just reducing the memory footprint for components that you control for out of the box components or maybe components that you got from some asset maybe don't work very well with the scriptable object approach so you will lose out on that memory benefit on whatever components can't read those same values but you can still define all of this in a scriptable object so that way all of your configurations stay in one place let's use the navmesh agent as an example you know i spend a lot of time working with the unity navmesh system so we can't modify the nav mesh agent to pull all of this configuration from the scriptable object instead what we can do and what i personally do is apply the configuration from that scriptable object to the nav mesh agent and to whatever other components need to be set up on start just like we did the health i generally like this approach because then i can define all of that configuration in one place i don't need to bounce between prefabs i don't need to worry about is this set from the configuration from the scriptable object or is it set up on the prefab like there's too many variables to worry about and where they come from so i like to just put it all in the scriptable object say this is how it's defined and have that scriptable object know how to set up that enemy and that's where it gets really cool because the scriptable object is not just a data container it's also a c-sharp class so we can put logic into the scriptable object manage whatever we need it to do so i'll do is create a public void set up nav mesh agent that accepts the nav mesh agent agent and what we're going to do there is apply the move speed and the height parameters from the navmesh agent configuration we can call this from the enemy start function passing in git component mesh agent or if for some reason unawake we have already grabbed a reference to that agent we can just pass in that cached reference now before you go get up in arms about how this is bad design because we now made the scribble object and the enemy coupled with having a navmesh agent i know that's what we did i get it why i think this is not a huge deal is you have at the beginning of your game most likely already chosen we're going to use this system or that system whatever for how the enemy's going to move means the likelihood of you having an enemy without a navmesh agent is very small if you're making a library where you want to work on many different things then maybe you would consider making this more decoupled i personally like this solution because what we've done is couple them through the scriptable object so if we want to change anything with this coupling we only have one place to make those changes what i personally like to do is set this up with a single function call setup enemy passing in the enemy that we're gonna set up and have the scriptable object find whatever references it needs and set up all those configurations because that configuration is responsible for assigning all these values because it knows about what those values are and how they should be applied so yes it's true i have coupled the nav mesh agent and whatever other configurations need to be applied together with the enemy so i can't have an enemy necessarily without the navmesh agent depending on how you set this up though you can not have that coupling there because a lot of times i'll just check did we have an atmosh agent applied if not then we just want to apply those configurations and move on with our day that way our scriptable object knows how to configure stuff and if something's not set up we can log a warning maybe or just skip it all together if we know that that's a possibility that maybe this particular type of enemy doesn't have that kind of component so it's okay if it's missing a possible alternative to this if you really don't like that coupling is have whatever you have spawning your enemies have that set up all these values based on the scriptable object and the configuration because whatever spawning the enemies also probably needs to know about how those game objects are configured i personally did it that way before and that class grew quite large so then the alternative was to move that to a different class and when i was moving into different class i felt like the configuration was the best class to put that in the last thing i want to talk about here is something that we talked about in ai series part 19 round spawning and scaling up enemies if you want to take a second to imagine a game you have like llama survival where you are spawning enemies over time and after you complete a wave or around we will buff up the enemies in this case what you're defining on the scriptable objects will be the base configuration for your enemies because you don't want to modify those at runtime if you do modify these at runtime in the unity editor these values will scale up this becomes a problem because most of the time when you're testing your game you're running it in the unity editor so your base values keep getting overwritten as you scale up your enemies and your game ends up becoming extremely hard when you put onto the device because now your enemies have been scaled up so many times that level one is impossible to play this is not a problem when you're actually running the game on the device though making these changes and scaling up on the device will not override the base values this is a really important distinction that when you modify the base scriptable object in the unity editor regardless of its play mode or in edit mode that will update the scriptable object the same behavior does not apply to a built game so something i always recommend if you're going to mutate the data of your scriptable objects at runtime and you want them to be reset to the base configuration at some time you need to do one of two things one is you can do as i did in ai series part 19 create copies of your base scriptable objects and then scale up the copy because that's just in-memory data then if we then remove all references to that particular scriptable object copy then it will be cleaned up by the garbage collector later on that way we retain our base configurations and we can solve that scale-up effect at more powerful more engaging enemies the alternative is to have some kind of reset functionality on the base scriptable object that you're going to use and make it restore the default values this way you effectively are doing the same thing just in reverse so the only solutions i have for you is to copy the data and then either use the copy data or restore the base data from the copy there is a downside to this though because whenever you want to create a copy of your scriptable object the fastest way to do that is with reflection that's the least amount of code for you to write but reflection is also relatively slow depending on your game maybe that way is fine maybe there's not a big performance issue there for me i was creating new agents at runtime and they're following me around so i need to quickly be able to create this up so i did was in the copy function just manually write this value equals instance value this value equals instance value this value equals instance value for every single value on the scriptable object also on any subscriptable objects i had to do the same thing pass in the new configuration and ask like the attack configuration to scale itself up and honestly the code isn't very fun to write but it is the fastest way that i know about at least at runtime to achieve that copy to quickly recap just everything we've talked about so far number one scriptural objects allow you to create configuration driven games that can both reduce the memory footprint and increase your productivity by reducing the amount of configurations you have to set up because you can reuse configurations across different objects where it makes sense it does take time to get used to writing code this way i know it does take a little bit of time but it does provide you some pretty cool benefits that i do think is worth taking some time to get accustomed to writing code that way number two if you're going to modify the values of the scriptable object at runtime do not modify your base scriptable objects unless you want them to persist across runs if you don't want them to persist across runs make sure that you're making copies of your scriptable objects and use those whenever you're mutating the data number three stripper object instances are effectively static so if you're modifying values you will modify all the values for everybody that's referencing that scriptable object so make sure you're thinking about what data you put in there should it be something that affects everybody or should it be something that only affects one game object or one instance of a script i want to give a huge shout out to all of my patreon supporters every one of you is helping this channel grow reach more people and add value to more people and that means more people are making their game development dreams become a reality if you want to support this channel you can go to patreon.com academy choose which tier is right for you get a voice out starting at the awesome tier and some other cool perks at the tremendous and phenomenal to your level speaking of those awesome tier supporters i have andrew bowen gerald anderson autumn k matt parkin and paul berry thank you all for your support i am so grateful and hey real fast guys i know normally at the end i'm asking you to like and subscribe and i i definitely appreciate it when you do that but one thing i want to ask that i don't normally ask you to do is share this video if you got value out of the video share this video with somebody or some group that you think would also get value out of this video i'd really appreciate that that really helped the channel grow reach more people and i valued more people and you know that that makes more people make their game dev dreams become a reality remember new videos posted every tutorial tuesday and i'll see you next week oh i forgot to flex here like and subscribe
Info
Channel: LlamAcademy
Views: 10,799
Rating: undefined out of 5
Keywords: Unity, Tutorial, How to, How to unity, unity how to, llamacademy, scriptable, object, scriptableobject, scriptable object, so, unity so, unity scriptable object, explained, explain, what are scriptableobjects
Id: dIAAi54Ty58
Channel Id: undefined
Length: 19min 27sec (1167 seconds)
Published: Tue May 17 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.