How to Code a Simple State Machine (Unity Tutorial)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
oh hey Charles oh hey oh man I'm so glad you're here you got a second to look at something yeah sure what's up it's that design pattern you showed me the other day the state pattern I get it in theory but I'm having trouble implementing it oh yeah you I'm gonna walk you through it again oh man that would be awesome of course man always happy to help let me just grab a chair alright so show me the project and what do you have so far right so I'm trying to use the state pattern to implement turn-based combat so far all I have is this simple battle see it starts with some dialogue and then moves into a combat phase I get to go first so I'll go ahead and attack and then my opponent will go and it'll switch back and forth until one of us wins state pattern seems like a good fit for this yeah I think so too I've got one class called battle system that's responsible for all the behavior keeps track of the battle and changes its behavior based on the current state the problem is that my implementation keeps track of state using an enum right and that doesn't scale well at all no unfortunately it doesn't all of my behavior logic has to explicitly account for every state which means I have to add a bunch of confusing if statements throughout my code and that's the very problem the state pattern solves now that video you sent me the other day describes the pattern really well but I think I need to see an action in order to really grasp it well why don't we dive right in then we can start with a plan we need to encapsulate each state and its behavior so the first thing we'll do is create an abstract class called state then we'll create another abstract class called state machine that's responsible for keeping track of the current state we'll have the battle system implement this class once that's in place we'll migrate all of the behavior out of battle system and into individual state classes and we'll replace all that logic with calls to the current state how does that sound I love it great let's get to work so like I said the first step is to create a new class that we're going to call State and we're gonna make this class abstract because we need our future states to implement it and to implement methods that represent the behaviors that our states are going to be responsible for now to determine what those are we're gonna need to switch back to the battle system and let's minimize some of these regions we're gonna focus on the execution region now this sort of gives us a hint of what our states are currently responsible for and that's gonna be handling the on attack button and on heal buttons for now as we can see by this if statement the only states that are responsible for handling these buttons is the player turn state so when we go to implement the player turn state class we're gonna have to add some logic to handle those buttons apart from that each state is gonna have some sort of logic that fires whenever it's set to the current state on the system so with all that in mind let's go back to the abstract state class we're gonna add three methods that each state is gonna have to implement and that's gonna be a start method an attack method and a heal method some things to note is that each one is going to have to be virtual so it can be overridden by the deriving class and each one is going to have to return ie numerator because we're going to call them as co-routines and by default we'll just have them yield break that way we don't actually have to go and implement them in every class if they're not overridden then they'll just go ahead and yield break by default hey I was wondering about this last time is it okay to have methods called attack and heal doesn't that go against the solid principles or something that's a good point it actually does break the open-closed principle which states that a class should be open for extension but close to modification right because I have to modify the code in order to add new states exactly so what's the deal is there something wrong with the code well normally I'd say yes but state machines are a special case because they're supposed to be finite if we open up our code to extension then anyone can add a state at runtime by hard-coding our state's we guarantee that our state machine stays finite at the code level hmm okay that makes sense also keep in mind that this is just one approach to the state pattern there are other more sophisticated implementations like unities animation controller that do support adding states at runtime but honestly for what we're doing here we don't really need anything that complicated gotcha all right that's all I wanted to know next just to kind of keep things separate we're gonna add a new class we're gonna call this state machine now of course the logic we're about to add we could add it directly to the battle system but just to make this easy to follow we're gonna put it into its own abstract state machine class and then have the battle system implement this so first unlike our current implementation that keeps track of the state with an enum the state machine abstract class is actually going to keep track of a state object which for now we'll make protected so that the driving class can delegate behavior down to the current state and then we'll add a public function called set state that takes in a state and uses it to set the current state of the state machine now something that's important to note here is that some implementations have the state machine responsible for changing its own internal state but in this case our derived state classes will take on that responsibility now another thing to note is that we want each state's start method to be triggered whenever we set the state of the machine so we'll go ahead and have this implement the model behavior class so that we can call start co-routine and pass in state dot start like so so that's pretty much all we have to do to lay down the groundwork the next step is to actually start implementing all of our behaviors so first we'll head on over to the battle system class again and we'll have the battle system actually implement the state machine class which remember derives from mana behavior and then we can start delegating these behaviors down to the current state and that would be the attack and heal behaviors will replace this player attack method with a call to state dot attack and likewise the player heal method now that behavior will get pushed down to the actual state class perfect so now all of the behavior is being delegated to the abstract state classes now we actually have to start implementing those classes with our existing behaviors so let's minimize this region and we'll take a focused look at the state specific behaviors region which as you'd expect contains all of our state specific behaviors and now it's just a matter of migrating each one out into its own class so let's start with an easy one we will do the begin battle state so I'm gonna copy all this we'll create a new class we'll call it begin have it implement state and then we're going to override the start method because that's the only behavior that the begins state is responsible for and right off the bat you can see we've already run into our first little snag the begin logic actually depends on being able to access the interface and the enemy so there are a couple ways to handle this the one we can pass the battle system into each of the state's behavioral methods or a little easier approach is to just pass it in on the constructor and have a protected field that we can reference in the deriving classes so we'll just go ahead and update the state class to have a reference to battle system that we will initialize from a public constructor perfect so now if we go back up to the begin method we can see that this is now complaining because we need to implement that constructor and now we can actually reference the interface directly from the battle system as well as the enemy perfect next we haven't cleared up all of our errors just yet we can see here that there's still some leftover state logic from the battle system class but instead of doing all this what we're gonna do is call battle system dot set state and we're going to pass than a new player turn object which we haven't created yet and we're gonna pass in the battle system this player turn object is again going to be another state so we can use some intellisense to quickly generate that class and we'll go ahead and just have a call to base constructor I also move it out into its own class file so just like that we're already ready to start writing the next state and that's the player turn so again we're gonna switch back to the battle system we'll minimize the begin battle method for now since we've already migrated that behavior and actually before we jump on to the next bit of behavior let's scroll up to the execution region we can see this logic that calls the begin battle method now instead of calling begin battle what we can do is call set state and pass in a new begin state class passing in this which is the battle system and of course this will trigger the begin States start method in a KO routine as well as setting it to the current state and if we scroll down to the state specific behaviors region again we can see that we no longer have any need for this method it's all been migrated out so I'll just get rid of it so we can see a little bit of progress all right next up we've got the player turn and the player turn has pretty much all the main behavior for now we can see that there's this bit of logic that should get executed on the start then we have the player attack and then the player heal functionality so we'll grab that all one by one switch back to player turn we're going to override the start method paste in this logic and again same issue we had before we just need to call battle system that interface and that's gonna set the dialogue to say choose an action next we'll do the player attack logic so we'll copy this go back to player turn class override the attack method and then paste that in again update all of the references to variables on the battle State and we can see again that we're in a position to implement two more states we've got n game and enemy turn so in this case if is dead is true that means that the enemy is dead so we're actually going to call battle system dot set state and pass in a new one state and if the player is not dead then we're just going to switch the turn over to the enemy's turn so we'll call battle system dot set state and passing a new enemy turn state class of course neither of these have been implemented so we'll go ahead and do that with intellisense just like before make sure to use the constructor on the base class and then put these into their own class files alright so now we're going to go back to the battle system class and do the same thing for player heal so we'll copy this logic from this method go back to player turn class and override he'll paste in the logic make a reference to battle system interface battle system dot player and just like before in the case of a heal you can't win or lose so what'll happen is we'll just call battle system dot set state and pass in again a new enemy turn so we can just pass the turn-on to the enemy and with that we have completed the player turns state class this is definitely the most complicated one because it's responsible for the most behavior so the last three should be much simpler but Before we jump into those we can actually get rid of these player heal player attack and player turn methods now we're throwing an error right here because the enemy turn actually used to make a call to the player turn method but we're about to yank this code out so in a second and won't even matter so let's copy this we're gonna go to the enemy turn and this logic only gets called on the start method since it's AI driven it simply does an attack waits a second and then switches the turn based on whether or not the player died so we're gonna do all the same things that we did in the previous State classes it's time if is dead is true in the context of an enemy turn that means that the player actually died so what we'll do is we'll call battle system dot set state and we'll call a new lost state class and if the player survives it then of course we're just gonna set the state to the player turn just like before we'll go ahead and implement lost and move it to its own class file now we'll go back to the battle system and we can remove the enemy turn method since we've already migrated that out of the battle system class as we can see we're at our last state here and what we were doing was handling winning and losing all in the same method but in this case we have two separate states so what I'll go ahead and do is copy the one logic out we'll switch to the one state class override the start method and paste in this logic that sets the dialogue text to say you won the battle and same thing for the lost state we're going to just copy this your defeated text and move it to the lost state course credit to override start and then we'll paste that in and yield break now we can go back to the battle system and now we don't need this method anymore and in fact we actually no longer need this region because this battle system is now simply responsible for delegating down to the current state which me we now no longer have to keep track of state in an enum and we can delete this state field and all references to it we no longer need to check if we're on the player turn all that matters is that the current state is going to handle these behaviors so if the current state doesn't implement attack or heal it's just gonna do nothing when those buttons are pressed but hey don't take my word for it why don't we switch back to the editor and test it out so we'll hit play as you can see a wild opponent has appeared so that was that begin state then it kicks it off into the player turn so I can choose to attack or heal now because the player turned state implements these behaviors when I hit attack you can see that actually attack the opponent and it switched over to the enemy the enemy attacked me could do heal you can see that I feel renewed strength works like a charm and there you have it your very first state machine you know what I get it now I think watching you implement it really helped out glad to hear it man always happy to help I really appreciate it man and you know what now I can start adding new states hey I've got time if you want to do it now I can watch and help you out if you run into any problems yeah that's a great idea let's do it alright so the first thing I should do is create a new class and I'm gonna call this yield since it's gonna be a yield state and I'm gonna have this class implement the state class that we've created okay I want to switch to the yield state whenever the enemy's health goes below 20% so that means I'm gonna have to set the battle system to this state from the enemy turn state now the enemy turns see only implements the start method which makes sense so it's gonna have to be somewhere in this logic but instead of the enemy attacking right away I'm gonna have the enemy check its own health if the enemy's current health is less than or equal to 20% of its total health then we're going to call battle systems set state and pass in a new yield state and at this point I could treat this like a guard clause and I could just yield break to break out of the kuru team now in the yield state all do is implement the start method and then update the dialogue to say that the enemy has yielded and I'll just worry about adding the extra logic later so Wow is that it I guess we'll switch back to unity and test it out press play go ahead and attack a couple times we'll get him down to 20% then look at that the opponent has yielded wow that was actually really easy that's the power of the state pattern and don't forget we're only using a simple implementation that's right you know I really like to read into those more sophisticated implementations that you mentioned earlier absolutely there are plenty of courses and videos you can check out to learn more about it I'll send you some links that would be great thanks man no problem anyway man I gotta get back to work so I'll catch you later sounds good to me I'll catch you later man special thanks to my top supporters Burke was 3d dark with photography and Z Richard Stan's Thomas our star and Trond [Music]
Info
Channel: Infallible Code
Views: 85,736
Rating: 4.8601518 out of 5
Keywords: unity finite state machine, finite state machine, finite state machine example, finite state machine unity, state machine, finite state machines, state machine tutorial, state machine unity, unity state machine, unity state machine tutorial, unity, state design pattern, state pattern, unity 3d, unity fsm, unity fsm tutorial, unity game development, unity tutorial, programming unity, brackeys, coding, fsm, game dev, game development, game programming, gamedev, programming, unity3d
Id: G1bd75R10m4
Channel Id: undefined
Length: 19min 22sec (1162 seconds)
Published: Sun Jan 05 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.