Using ScriptableObjects for a basic State Machine

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi welcome to our uh video on using a state machine in our unity games uh this is a project that we've been working on for a number of different videos uh mostly to explore some more advanced game patterns although i think we also used it for ui basics uh so it's it's sokobon it's a circle button game and here's where we're at with it we can move around we can push blocks push them onto goals we have a timer we have a move counter um which i added off off recording another previous video show me adding that but it's you know it's all it's all pretty much there undo and redo or sorry undo and restart although we could add redo pretty easily um and uh what we are missing is the ability to win the game we have we have the ability to win the game we have we have won and we'll keep winning every time we move um but nothing happens on screen when we win um so when you win a number a number of different elements could respond to this to this event of winning they could uh they could turn on a ui panel they could disable player movement they could play a background sound they could uh initiate a little moving to the next level or moving an animated transition there are all sorts of different systems in my game that might care about the current state of the game being it winning paused or um just those two right uh and then being in game play or not um so we want to handle that in an elegant way our reflux is to head to the game manager script and maybe add booleans like has one and then in our player input script we you know we check if the game manager dot has one is false before doing any input checking uh maybe we do the same thing in a ui block and we turn on a ui panel we just check for that variable and then we say okay that works great we're good to go and then we go and we try to add a pause feature to the game and then we're copying and pasting some code and it basically works the same and now like five more things i'll need a reference to this one script and it gets even messier um so then we say okay i know the solution to this we'll use an event system instead of the booleans and that kind of helps some things it helps some update loops and all the other code to be prettier and easier to work with but the game manager still has to touch everything and it kind of forces our hand into a singleton pattern and my gut instinct when i'm doing unity development is to not get forced into an architectural pattern of any kind but particularly not one that enforces a strong dependency in my project like singleton's um so how can we get around that how can we have this piece of information that everything knows about the game state while have not having it be in the game manager script and the answer is scriptable objects is if we i sort of gave it away in the way i phrased that previous sentence i gave away the the secret but if we turn the uh instead of having the game manager tell everything what they need to know having everything listened to the game manager instead we wrap up the idea of a game state of the current global state of the game into an object into a piece of data that will make a scriptable object and anything anywhere can just look at that sculpture blue object and be like what's the state of the game game manager will look at that scriptable object and tell it to pause or unpause tell it to start playing or not start playing and so on and the anything else will go in and out of various states um so that state pattern that flow chart of you have you have a piece of information that can only conceptually be like one thing at a time and we move between them that's sort of the high level pattern we're using although if i say state pattern i think i don't think this is actually it will be but there are ways to do it with just like an enum and a scriptable object and not like a whole whole thing we're going to do it the fancy way because that's the point of these videos is to be is to raise our our beginner intermediate level up to like intermediate intermediate level maybe maybe somewhat advanced um in a simple in a simple scope um so let's make a new folder for our state machine and for our scripts and we are going to make a new scriptable object that's going to represent our machine uh and it's gonna loop through what different states we can have so let's add a scriptable object let's call it uh i'll just call it state machine uh i'm i'm in a git repository you can look at all this code up on github that's what those pop-ups are uh so let's call this state machine and we'll put it in my little folder i don't like i don't like in unity i don't like how big this menu is right if you right click and go to create like this menu is already pretty big so i like to make for all of my custom stuff i like you know one two maybe three little you know my gameplay stuff my utility stuff whatever it is i kind of put them all in a different thing state machine should probably be its own folder it's not really part of soca bond we could general there's gonna be a pretty general system that we could apply to many games so i'll change that and let's see okay um so what is the state machine gonna do it's gonna know about the current state basically uh and it's gonna enter and exit states so our state the actual piece of data that holds a state could be something like an integer and it could be zero for game play and one for victory and two for lose and three for paw or negative one for paws ooh fancy that's gross that's ugly we know better we know that we should add it we could do an enum to hold on to that state and then now we can give them you know we could give it a we don't have a lose condition in this game but um we could write all of our states as an enum and then the state machine could grab those but we're not going to do it that way we're going to be even one step fancier we're going to have an object that is going to represent what the current state can be it could be you know it could be just a class that we have somewhere uh that's a pretty standard approach of this play you know it doesn't inherit from model behavior or anything it's just kind of its own object and we can make a bunch of them and then and then have some code in that object but we're going to go even one step further in the data oriented design section we're going to use a scriptable object to handle our state and i'm going to call it state and now using the little whatever i call that folder what did i call that folder um and now i can create actual states these states right now are going to be empty we're going to add some code and take advantage of the fact that we can but for now they're just going to be these like data objects i can just like make in unity and have so we could have a current state class let's see what that looks like in our if we make some scriptable objects and have it if we go to my game data uh let's make some new folders let's call one of them settings and put our settings in there let's make a new folder and let's call this one game state and we'll make a new state machine game state machine uh and it has a little public slot for a state uh so i'm gonna make a new folder for my game states default states and we'll make a new state and we'll call this one gameplay the default state you're just playing the game and we'll make a new one and call it victory these are empty they don't do anything but they're here and in my state machine let me lock that in my state machine i can drag them in there or drag this one in there and now and now our machine's starting to make sense we can have objects reference this game state machine and they can get whatever the current state is nice that is convenient and then they can use that to check on things and do whatever they want to do so let's figure out let's write some public functions for manipulating this game state let's set this to be private and we'll do a little underscore there so we know it's private we'll make a little public getter uh what did i do current say hey whatever i don't even know if i'm going to use that um what do you want to do what is your convert to auto um this allows me to serialize that this this particular style of getters and setters this video is not about getters and setters this video is about state machines okay so we need a function that allows us to set the current state of the game and we'll just get told what state to go into and we'll set the current state to the new state and that's kind of all we need right now so with that we can go to our game manager and we can make some public references to the uh [Music] serialize private state machine machine i know that all right seriously okay uh date victory state and now in here we can do machine dot set current state to victory state right when we win the game we're just going to change the state into the victory state and then on start we will take our machine uh and we will set it to the gameplay state i'm actually just going to do a little init function here a little public initiator uh keyboard uh public initiator which is going to set current state to a default state instead of having the gameplay i have to know what the starting state is we'll just have this little initiation function because that tends to be useful for scriptable objects for like clearing your variables uh and resetting information and initiating things um something i think we end up tending to need is if we're using scriptable objects in a way that is like per scene load uh that we need some objects in the scene it's like oh by the way you know you don't get the event function so let me tell you when to initiate so we'll have that whilst you use set current state here to our default state so now when the game begins the machine will get initiated it'll go to its default state which we'll assign to be the gameplay state in the inspector and then as we play when it when when we win it should update so we'll take our default state we'll set that to gameplay beautiful we'll go to our game manager unlock that and we'll set our machine to our only machine and we'll set our victory state to victory and let's so if i select our game state we see it says gameplay if we play the game uh then hover my mouse over here to have it update did it not have to click off of it back on ah it didn't work oh because i can't see what the current state is i'm looking at the default state variable okay let's make this public temporarily normally i would use like naughty attributes to have like a read-only field in the inspector so i could see it i wish that was built into unity by default okay now i can actually see the current state here look it says gameplay uh and then if i win hey now it says victory huzzah that actually worked wow i'm going crazy another cup of coffee um so now we can have objects uh do like listen to this the current state they can reference the machine they can get the current state and they can check when we enter and exit um when we do that like checking we're going to want some kind of event system to fire off we want that maybe we want to add an action to this or an event to the state machine that's going to have it get called on state change and it's going to pass out the current stage we could have listeners for that oh and if that state is my state then i'll care about it we're not going to do that we may want to do that we want to have that feature there it doesn't really hurt anything but instead let's actually go to our state object itself and here let's store some information when this state is entered and when this date is exited that way instead of listening to i think for most of our objects that are going to be listening to the game state changing they're only only listening to one state they don't need to know about every single change and then checking if that state is something they care about because then they're going to need to reference two objects the machine and the state well why not just reference the state that they care about and have that state that they care about because we can put code there tell it things um so let's write a an on enter function and an on exit function and we'll have these fire off some events of its own so our state machine we will on the set current state if it's not null we'll call on exit i feel like we're going to get a weird issue where when i initiate the game we're going to leave the victory state it might cause some bugs we might want to reset that initial in the init function and then we'll take our current state and we'll call on enter so we'll leave the current state and we'll change it and then we'll enter the current state so the current state function should always get on enter called and on exit cold nice again because you're scriptable objects these this variable won't reset uh you know maybe we might need that i'm going to leave that off and just see if we get the bug see if we get those kinds of errors because that's you know for working with scriptable objects it's good to be aware about our big gatches and kachas and the disadvantages of scriptable objects being used in this one so how can we get other objects to listen to a state well let's do it the easy way for now which is to use an action uh public action on enter and on enter event event those cabos those functions if they have any listeners we'll invoke them nice so now we need to listen to them in a uh in a a game object i'm going to write this code in an empty unity script just to demonstrate it we'll call it state change listener and a little model behavior just to demonstrate the code that we're going to have to move around to all of our different things that listen to our game state changing um so we need a state and then on enable and on disable we want to subscribe to that and uh save that accident it's really important that we're going to have to subscribe and unsubscribe on disable because our scriptable object will hold on to those it won't get cleared when we leave play mode so when we leave play mode on disable we'll get calls and all right behaviors and on destroy will get called so we'll need to use that but this is the sort of the basic pattern that we'll have to put into all of our code but the way i just said that might have like raised a little red flag for you it's like hmm if there's a better way to do that maybe we can use this and use object inheritance to take advantage of not having to write code more than once i think so let's have uh let's have these events be virtual probably need to do on enable and disable too but let's have these events be virtual so we can override them in the children and then just have anything we lit that we listen to do its thing so one of the things that we care about is player input player input so instead of extending from mono behavior our player input can change to state change listener and it will we can only call input when we um are in the gameplay state so we'll make a little uh private cool can move we'll just have it be true i think writer wants me to make that have an underscore when we can move we'll move uh and we'll just grab our input and block it so if we can't move return can't move so now we just need to update this variable with the state machine so because we're listening to it and because we don't use on enable or on disable all we need to do is override on enter and can move equals true because this will be the gameplay state and we'll override on exit and we'll set can move to false very simple so we basically have these new event functions for our state let's see what that looks like in the game we can go to our player we can tell it to listen to the gameplay state and we were able to apply that to the prefab because we can always reference these these states because they're just objects in our data in our game so let's see if it works i am no longer able to move i've entered the victory state and my game has ended excellent uh what else cares about the game state well we could pause the game and unpause the game let's let's get back let's stay on the victory state for now and let's head to our ui i've created a panel on our canvas called paused uh that will be that uh but let's go ahead and duplicate it and let's call this one victory but we'll disable the image or no we'll enable it and make it green and transparent and then just put the text victory text it will [Music] so we have our victory panel oh that green on yellow is not good i'll just make that gray out you win and change that to a square so i get my borders nice so now i want to enable and disable this game object when we have one uh that feels like something i'm going to do more than once so let's write a new let's write a new little utility script here let's call it enable children with state and we'll make this extend state machine state change listener we'll override enter will override exit we shouldn't use the word event in there i don't like that anyway uh so now we just need to enable and disable all of our children for each transformed child in transform child dot set the game object dot set active true false nice so now i just need to make a basic a base object that will be empty and have that object and i will disable this image and it will disable its text so i'll call this one panel and then i'll make a new empty and i'll call this one victory victory victory panel victory text uh we'll make sure this is like full full screen uh and then we'll give this one the game object enable children with state game state victory maybe we do need it to leave the victory state when we start the game we might have some bugs with the beginning condition of the game uh we'll do the same thing for the paused state we'll add an empty paused we'll call this one past panel and we'll put it into pause okay we gotta apply all this stuff to the prefab it's been a minute since i've done multi multiple prefab i want to reply these to the canvas and not as overrides did i do that no go to the prefab editor nope right here right where apply to canvas apply to canvas why do canvas uh okay okay so now in my actual canvas prefab i have the the states uh so in paused i should add enable with pause we don't have apostate yet bring that in there make sure this is just kind of an actual empty and we can make a new pause state duplicate call paused there we go so let's see what happens all right they both disabled which is good and then the win state enabled which is great nothing else is referencing the game manager um i can't undo i can't i can restart but i stay in the win state oh no i do i knew i'd have a bug like that uh we need the what do we need we need the listener to on start check if uh it is the current state and then be enabled on start or not let's add a let's add a boolean for current state for is current we'll just we'll just move it around in the state um and then on our list on our enabled with machine actually in start we can do make that protected so i can access it let's set them to whether or not we're the current state or not and start which makes me feel like i need to initialize the state machine in a wake not in start [Music] but the bug went away can't undo restart and are all our enabling and disabling children objects are checking themselves uh because we may not get that exit event right you know if we're already begun in the victory panel is enabled or its children are enabled um we don't want that you know we want to turn it off when the game begins and since we're only listening to the victory state and we're not listening to the you know gameplay state entering we're just you know we don't have that exit for the victory state because it's just the beginning of the game uh we'll just check its current state on start that feels fine it's fine it's a little gotcha with scriptable objects these kinds of bugs uh let's add the pause how do we pause the game all right so the timer is going up we need to control the timer i have sit in play mode and i was like oh the bugs back okay uh let's add a little button restart button duplicate that and move it to the left and we'll call it balls and i'll have this one be centered that feels okay uh and what does a pause button do it's we'll pause the game that's probably a game manager thing right cause that game manager is the timer so we probably need to add a pause and unpause feature to the game manager so we'll need to know what the pause date is and then we'll add a little public pause unpause and when we pause we will set our timer do i start it or i resume it i'm pause it it's called on pause okay uh and then i will set the current state to the gameplay state i don't have a reference to gameplay state because i didn't want to reference the gameplay state because i use defaults but we need to know our we can't go to the previous state we can't return to previous state because we wrote too simple of a state machine uh it's really nice to keep your states in a like a stack in your state machine and then instead of setting current state you can decide like go back because really often we won't just like pause um pause then victory right we don't actually want to need to know what the current state is or you know we really don't like this this i have to know about all of my states in one script because at this point you know at this point you might think like well we don't really need this entire whole setup it could just be in the game manager and then it could it could um this project is at a level where it's simple enough that we can get away with the simple codes and we can see some of the ugliness like where more complexity would help like having a list for just going to the previous state having pause be controlled somewhere else and just have the timer listen to it but we're trying to keep things simple so it's kind of a balance uh balance of that works it works go to go to previous we're not going to write that but we could pretty easily uh so now when we push this button we will pause and then apply this as an override to the canvas um and then in the pause menu and it blocks out the screen so you can't see what you're doing slide a button zoom and the existence of the pause button will be part of the canvas prefab and the assigning it to the game manager object will need to be a part of the manager's prefab so let's make sure we can do that i think i did it right i don't know i don't use nesting prefabs very often pause and we're not set to instance of an object because i need the gameplay state to know about these things the game manager to know about these things there we go what's nice is you can just do that once and then be done with it pause because i left the play state i shouldn't be able to move resume and victory so my timer one two three pause wait a few more seconds it should be at five or six it's still at three our timer successfully paused i don't think i ever tested that code and uh our timer still runs when we win the game we're not using the state machine to control the timer we're just using uh i'll just pause it i don't remember if the stopping it will reset its values it will okay maybe we should stop it doesn't really matter does it need to get restarted re-initialize before we can we can check now it's impossible to three seconds 20 turns all right well that's uh that's it for the video um hopefully you saw some of the the tr the trip ups that can happen with using scriptable objects in a in a way but also some of the nice nice elements um you know we can just check this is current value of a state we don't need to use a base class here we don't need to generalize that code maybe we only want on enter or on exit um you know what else uses what else can i use the the state code we could put unity events in this state and then call functions on other scriptable objects and other another bits of game data um you know have a lot of piping connected without being part of a scene 14 moves two seconds let amigo that's it so we're using you know it's a little bit more advanced than using a like an enum in our game manager there's still some cleanup to be done there's still some some features like that frankly our machine could probably know about all the different states and it could have some nice little utility functions like victory or return to default things like that and that would save us from having to serialize this in a game object and we're just you know we're setting up our machine in the um in the scriptable object that's like a that's a more reasonable place to put that code and you know having it be this perfectly generalized solution that i could bring to other projects like i don't need that i could just make it work for this one and write you know and write these little utility uh these little utility functions that that i set up in my in my uh machine there's no reason to have the game manager need to know all that stuff it's just like one more kind of point of failure the state machine might as well be responsible for those things but as is it's a perfectly generalizable solution you know it's a little lacking in features but it's it's uh as far as demonstrating such a thing um i think i think useful so there we go how do i hide my video i had my managers all right uh that's all i got hope you enjoyed
Info
Channel: Hunter Dyar
Views: 2,322
Rating: undefined out of 5
Keywords:
Id: B2KvSQNVYxA
Channel Id: undefined
Length: 39min 48sec (2388 seconds)
Published: Thu Feb 17 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.