CLEAN Game Architecture with ScriptableObjects | Unity Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys in this video I'm going to show you how to use scriptable objects to help you improve your game architecture by the end we're going to be reading input plain sound effects and referencing our player's Health with no dependencies we're going to do all of this using scriptable objects and they will be just as easy to use and access as Singleton but without the cons that come with using Singleton ready let's go so I use Singleton classes a lot when I need to slap things together quickly and if you don't know what a Singleton class is it's a tiny little pattern that ensures you can only have one instance of a script cuz if you have more then you destroy the game object that that script is sitting on and the beautiful thing about this pattern is since we only have one instance of this script we can make a static reference to it which means we don't need to worry about which instance we're using so we don't need to worry about get component or anything like that we can just call methods directly from that script however this does have drawbacks let's say I have this audio manager Singleton here which handles playing all of my sounds and now I've just created a new character and I want to be able to chuck them into a small isolated test scene to make sure that they're working properly but without that audio manager game object as soon as I try to play a sound I'm going to get an error and at first for smaller projects this can easily be worked around but as your project grows in complexity and you have Singleton references everywhere now everything is dependent on the existence of everything and you're needing to run basically the whole game Loop in order to test one new little character which is not ideal the ideal game architecture will allow you to have everything be completely self-contained and just work you could create a blank scene and Chuck little pieces of functionality in there and they should just work no managers no dependencies of any kind so I'm going to show you one way we can go about this for our input using a scriptable object and scriptable objects are much more than just data containers for things like inventory and dialogue systems because they are assets they are globally accessible and seen independent so let's use that to our advantage I have this little scene here with my player character that can run around and my input is currently being read from this user input script here which as you can see is a Singleton class so what I'm going to do is delete this because we're going to make a new one I've got my input action asset here and my preferred method is not to generate a C class and that's just because the way that I set up my key rebindings in my projects they don't work with a class that's generated but I will show you the code for what we're about to do if you decide that you want to use a generated C Class here afterwards so I've got one action map called player and inside we just have a few actions I want to keep this very simple so we're going to create a script called input reader and let's open it up as we said this is going to be a scriptable object and you can call the menu and file name whatever you want let's add the input system namespace and now we'll need to link the input action asset to this script so let's create a serialized field for it right here and some boilerplate code that we're going to fly through we need an input action for each of our actions so one for move jump and attack we can assign those using the find action method and now we need to assign our callbacks so we'll set up an on move on jump and on attack function with a callback context and now we're going to need to manually subscribe and unsubscribe the started performed and cancelled for all of our actions we also need to enable and disable those input actions and now we're going to add the unity engine. events namespace because we're going to set up some public events of type Unity action and for the move let's make sure that we use a vector 2 as the type okay now in our on move we'll invoke the invent making sure to return a vector two and in case you don't know this question mark here is just shorthand for if this isn't null for on attack if the event is not null and the context is started then we'll invoke the event and for on jump we'll invoke the jump event if the context is performed and we'll invoke the jump canceled event if the context is canell all right now let's create an asset and as soon as you create an asset you're going to get a null reference exception don't worry about that just assign the asset here and clear the error on enable gets called on a scriptable object whenever it's created but now that it's assigned we don't need to worry about it so now in our player here we just have a couple of small things to do we need a reference to that input and we can subscribe methods to the events from our input reader so I'm just going to assign my input float here and set a bull between true and false here that's all my movement is really looking for and actually I'm going to change this to a public and my Tac is very similar it's just in its own script but I'm grabbing the input from the player script subscribing to the event and unsubscribing and I can take this do attack function out of the update and get rid of this check for the input I had to make a few changes to My Player move functions as well the specifics are not important the part that's important here is that we're moving from checks directly from a Singleton which was user input to instead subscribing to events in a scriptable object asset file okay let's assign our asset in here and there we go you can see he's a prefab and I can create a brand new scene with nothing but a camera some lights and a floor and he just works there's no singl there's no input manager needed nothing and I said I would show you the input reader code if you prefer setting up your input action asset with a c generated class what's nice about this method is you just Implement an interface which is the name of the generated class with an i the name of Your Action map actions that will force you to create all of these methods here and the rest is basically the same there's just less boilerplate needed the biggest difference is we get a reference to our generated class and if it's null we create a new one and then set the callbacks and en that class and disable that class that's actually it for this one a lot of the setup and boilerplate stuff is taken care of for you in the generated class okay let's create an audio manager next and real quick if you're enjoying the video then thank you if you leave a like or a comment now we're going to handle this one a little bit different because you're not even going to need a reference to it on the player it's just going to sit in our project as an asset and everything's just going to work so I'm going to create a script called Sound Manager so and again we're going to change this to a scriptable object and the way I like to do sound is to spawn it in and disable it after it's done playing this has the advantage of being just as easy as using play clip at Point except you can actually assign the sounds to an audio mixer which I use in all my projects so to do that we need a sound object that we're going to spawn in and we'll use audio source as the type instead of a game object just so that we don't need to call get component and look for the audio Source each time we spawn one in okay so what we're going to do is make a method called play sound effects clip and we'll take in an audio clip the position to spawn it at and a volume so we're instantiating one in and by the way this is just an example but I highly recommend that you don't actually instantiate and Destroy you're going to want to incorporate this into an object pool so that you can reuse the same assets over and over but that's outside the scope of this video but I will throw in a card here in case you want an easy object pooling system you can incorporate with this method Okay so we've spawned in our sound object object and what the heck let's randomly adjust the volume and Pitch slightly every time we play a sound just to make it a little less annoying to listen to now we'll assign the clip the volume the pitch and finally we'll play the clip and we'll create an overload method that works exactly the same except this one is going to take in an array of Clips just in case you want to be able to pass in an array of clips and have one picked randomly for you now the thing is I want to be able to just call Sound Manager so doplay sound effects clip from any script so for that to be possible these need to be static methods which means these also now need to be static but now this is giving us an error because in order for this to work in a static method we need an instance of this scriptable object so we'll set up a private instance and a public property with a getter and we'll say if the private one is null then we'll do a resources. load and find our Sound Manager asset this way and in order for that to work we are going to need to make sure that we save our asset into a folder called resources somewhere okay and then we'll return the instance so now we can say instance. sound object so back in our project we'll create a folder called resources and create a sound manager in there and now we just need to create our sound object so let's make a new object add an audio source and we don't need on awake because we're playing it with code and whether you're using an object pool or whether you're destroying it you're going to need a script to handle that either way so I'll create a script called sound destroyer and attach it and this is as simple as getting a reference to the audio Source in awake and we'll turn our start into a co- routine and grab the length of the audio clip weight the length of that clip and then in my case I'm destroying it or you could deactivate it and return it to your object pool okay so let's turn this into a prefab and assign it to our Sound Manager I've got this little 8bit jump sound effect here so we're actually going to play it when our player jumps and you can see it's as easy as calling Sound Manager so doplay sound effects clip and just pass in the arguments now you can see the jump sound is playing whenever we jump there's no Singleton in the scene there's nothing that lives in the scene at all all right the next example is really cool but it's a pretty different way to work and it takes a little bit of getting used to so we've got our player here and he's got a little player Health script on him with a Max Health variable pretty standard stuff now we're going to do two things we're going to add a health bar to the player and when the player's Health gets below a certain value we're going to play a heartbeat sound and we can achieve this easily if we're just a little bit creative with our scriptable objects but doing it this way the health bar doesn't need to know about the player the health bar doesn't even need to be attached to the player we could attach it to anything and have it fill based on any float value so we're going to create a script called float variable it's going to be a scriptable object and we'll create a float called value and just to make things easier for later we'll create a method that allows us to directly set it or apply changes to it now let's create two new Flo variable assets and let's add our health bar to our player you'll notice it has a little script called health bar on there and we're just passing in references to that player's current health and Max health and finding the fill amount based on that and I am keeping this very very simple for the sake of this tutorial so let's pass those in I'll replace my player's Max Health here and also I'll bring in this Dam damage tester object here which will just damage the player every time I press t on my keyboard and finally I'll bring in this sound when low object prefab here as well again very simple we reference our player's current health and if it's below a threshold then we're going to play the audio and if it's above the threshold then we'll stop the audio really really basic stuff and you can see I can press T the health bar will update and if we get too low that sound starts playing but nothing needs to know about the existence of anything else I could even get rid of my player and the sound effect will still work if I change the value [Music] around I can add things into the scene delete things out even in play mode and everything just works now because it's a scriptable object the health will persist even after you exit play mode so you're going to want to code something up that will reset that for you when you want but like I said scriptable objects are far more powerful than something to just store data you can use them to build a game that's more mod easier to edit and easier to debug there's a talk that I'm going to link below that goes really in depth into this stuff that I highly recommend you check out if you're interested in taking these Concepts further that's all I got guys I hope you enjoyed bye
Info
Channel: Sasquatch B Studios
Views: 7,367
Rating: undefined out of 5
Keywords: unity, unity2d, unity tutorial, sasquatch b, game development, unity tips and tricks, unity tips for beginners, unity game architecture, unity game architecture best, ScriptableObjects, scriptable objects, scriptable object, singleton, unity singleton, unity scriptableObject, better data in unity, game architecture, unity clean code, unity why are singletons bad, unity singleton alternative, unity what are scriptable objects
Id: wzPputN4Ts4
Channel Id: undefined
Length: 13min 12sec (792 seconds)
Published: Thu Apr 11 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.