A Better Way to Code Your Characters in Unity | Finite State Machine | Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video we're going to use the state machine programming pattern to simplify the code in our game and improve performance I use State machines for the player character in every game that I make and I do that so that I can avoid a mess of a bunch of checks like this where you have to tweak every other ability every time you add a new ability into the game and plus that's a lot of if statements running every single frame and they don't need to be with a state machine each state is self-contained which means that you're left with smaller more manageable scripts with Direct Control of what states can switch to what states this will make debugging your game easier because all of the logic is contained within each state the code is more performant because you're eliminating unnecessary checks every frame and a system like this is much easier to expand so it's more scalable as well the only downside is it's a little more work up front to get the state machines set up in the first place now I'm currently working on my commercial game samurado and for that game I wanted to have a high amount of enemy variety but I wanted to create a way to make it as easy as possible to create a high variety what I ended up with is a state Machine controller like what we're going to set up in this video but by the end you can literally plug in the behavior Logic for the enemy from a scriptable object we're going to set up three states in this video idle Chase and attack and with just these three states the number of unique combinations is exponential meaning with one set of logic there's obviously only one unique combination but with two sets of logic meaning two Idols two chases and two attacks now there's eight unique combinations with three sets now you have 27 unique combinations and so on and so on I'll show you how to store the logic in a scriptable object in my next video but before we can even do that we have to have a state machine set up in place so this video is going to be a standalone video that shows you how to set up a state Machine controller for your enemies ready let's go so here's my scene and we're going to create a really simple State machine Behavior where the enemy first starts off just randomly wandering but when you cross this trigger here the enemy will aggro to you and start chasing you and once you hit this trigger the enemy will start attacking you and he'll go back to his Chase Behavior if you've been outside the Striking Distance for a few seconds so you can see I only have some basic components on my enemy but no scripts just yet because we're going to do this from scratch so we're going to set up quite a few scripts so we'll definitely want some folders to stay organized I'm going to create an enemy folder inside scripts and in there I'm going to create all these folders base enemy types interfaces State machine and Trigger checks let's start with the base first create a script called enemy this will be the base script for all the enemies in our game and right away I'm going to want to use some interfaces for this script so over here let's create an eye damageable and I enemy movable interface inside I damageable we're going to create everything we need to deal with health and death so we'll need a damage and die functions as well as Max health and current health floats and this will not be sitting on a game object so it's not a mono behavior and it's not a class it's an interface interfaces don't actually Implement any specific code that we will do in our enemy script and let's do that now if we open our enemy script and we add the eye damageable interface to this class you can see that we get an error that's because an interface will force you to implement all of those methods and include all of the variables that we created so we can right click quick actions and refactoring and implement the interface and let's just get rid of all this junk code now let's actually set this up by implementing the damage function which I'm going to keep very simple and for the die we'll just destroy the game object in start let's set the current health equal to the max health and even though this is from an interface we can make it serializable by going like this now to show you this working I always like to create a separate script for each enemy type just in case I want to implement some Logic on top of the base class and I'll call this ghost and we'll have ghost inherit from the enemy class now you can see in the inspector that our health is showing now let's give our enemy the ability to move around so let's open up I enemy movable and add these two variables and these two methods now back in our enemy base class we're also going to add the I enemy movable interface and Implement those is a simple top-down game so I'm just going to make the rigid body velocity equal the velocity we pass in and check if the enemy should face the opposite direction which this code will handle by rotating the y-axis by 180 degrees if necessary foreign let's also make sure R is facing right pool is initializing to True since my enemy is in fact facing right by default and let's not forget to grab a reference to our rigid body now we're ready to set up our state machine we're going to create two scripts first enemy state will act as the base class for all enemy States and enemy State machine is just a simple little script to keep track of which state we're currently in let's handle enemy State first so first off this is not a monobehavior and we're going to want to have access to our enemy class as well as our enemy State machine class from within each state so we'll make these protected which behaves like a private variable from outside classes but it acts like public for any classes that derive from this enemy State script so all our individual states will have access to these variables we're also going to use a Constructor to pass that data in when we create an instance of this script which we will do soon so before we continue we need to think about the logic that we might need because remember states are self-contained and they hold the logic within them so we need to be able to do things like know when we've entered and exited the state we need to be able to use the update function and things like that so we're going to create virtual methods for all of those virtual means we can override them in an actual State later on but it's not like an abstract method where we're forced to call the method with virtual it's optional to override it so we've got enter exit frame update physics update just in case and let's also create an animation trigger event which might come in handy and before we continue let's go back to our enemy script and create an enum containing just a few types of triggers let's go with enemy damaged and play footstep sounds these are just examples and let's also create an animation trigger event here and we'll use that enum as a parameter and we'll fill this in once we've created our enemy State machine and to finish our enemy State let's pass in enemy.animation trigger type okay you could add on collision enter functions here or really whatever you want it just it depends on what you need but for me with this example all these functions should be more than enough now as for our enemy State machine script this is also not a monobehavior and we're just going to hold a reference to what our current state actually is we also want to create an initialize function which we'll call an enemy in just a sec just so the state machine knows which state to start in and we also need to create a change State method which we'll call every time we want to exit one state and enter a different one perfect so now let's create a folder called concrete States in here and create three more scripts one for each state we're creating so I'm going to make an enemy idle state an enemy chase state and an enemy attack state in each of them we're going to inherit from enemy state which will force us to call the Constructor method but now for each of them let's also generate all the override methods that we might need which we can do by right clicking and hitting quick actions and refactoring generate overrides great we're almost ready to start creating some logic here but first we need to grab an instance of these classes they are not monobehaviors so the instance isn't created automatically instead we need to go to enemy and set up some new variables for our state machine like so then in awake we'll set up the instance for each by making those variables equal to a new instance of each of them passing in this and state machine for the parameter on each of the individual states then in the start method remember I said we need to initialize the state machine well we'll do that right here now back in enemy State we've already handled these two because those are actually called from the state machine each time we change States and the animation trigger event is going to be called whenever we use a trigger in the animation let's finish that now we can actually call that event from the current state now now in our animation window when we add an animation event you can fill in the type here and perform logic in the state later on based on which enum you chose but we still need to call these two functions from somewhere so in enemy let's just call our frame update in update and let's call physics update in fixed update and there you go all of the logic is properly being called now so let's set up some super simple Behavior to test this out and we'll start an enemy idle state so we're going to need two serialized fields and the easiest way to do this for right now is to put them on the enemy script I want to set up a random movement range and speed and then back in the idle State we can have a private Target position and direction when we enter I'm going to set the target position to some random Point within a circle by calling this method here and in frame update I'm going to calculate the direction to that point and then we'll move the enemy to that point at his random movement speed and if we've hit our Target point then we'll go ahead and grab a new point so let's hit play and now you can see our enemy is in idle and he's just aimlessly wandering around so now the question is how do we get him to change States so I'm going to go with a very simple approach here I'm going to add two trigger areas to the enemy one called aggro radius one called Striking Distance aggro will have a trigger Circle collider set to 15 for the radius and Striking Distance will have a radius of 6. now we need these triggers to change a Bool which will then change our state so let's actually create one last interface called eye trigger checkable which will hold two bulls and two methods to change those rules let's Implement that to our enemy script thank you the set aggro status will just change the is aggregate rule to whatever we pass in and same with the set Striking Distance pool now in our trigger checks folder we're going to create two very simple scripts one called enemy aggro check and one called enemy Striking Distance check in enemy aggro we're just going to get a reference to the player and the enemy in the Wake function and we want to set the is aggroad pool to True when the player hits the trigger and false when the player exits the trigger and let's make sure we change our player to have the player tag or this isn't going to work the enemy Striking Distance check will work exactly the same way we'll get a reference to enemy and the player in the awake function and change the Striking Distance pool in the on trigger enter 2D and on trigger exit 2D functions so now in our idle State let's add a check that says if the enemy is aggroed will enter the enemy chase state to test this let's just stop the enemy's movement when he enters and print a message to the console Let's test okay there you go so in this state I'm trying to keep this as simple as possible we only need two private variables and the player transform can actually be grabbed in the Constructor because just in case you didn't know a Constructor is called as soon as an instance of this class is created so right here in the enemy script so in this case since we called this in the enemy's awake function this is acting like an awake function and all I'm going to do is calculate the direction to the player every frame and move the enemy in that direction and this time we'll say if the player is within Striking Distance to the enemy then we'll change our state to the attacked state I've set up this simple Sprite to act as our projectile it just has a rigid body and a trigger collider and I've made it a prefab so we'll need to create a reference to that bullet in our enemy script and then in our attack state I'm going to grab a reference to the player transform and create a timer a time between shots and an exit timer a time till exit and a distance to start counting exit float so what we're going to do is we're going to say that the enemy will shoot projectiles every two seconds but if we've been outside of his range for this many seconds then we'll exit back to the chase state so in our frame update I'm going to make sure that we keep our enemies still I'm also going to update our timer and if that timer is greater than our time between shots then we'll reset the timer calculate the direction to the player and instantiate a bullet and since we serialized it as a rigid body we have immediate access to that component so let's just set the rigid body's velocity here towards the player then we can say if the distance between the player and the enemy is greater than the distance to exit don't do this every frame by the way it's expensive I'm doing it here because it's the simplest but distance checks are expensive and there are better ways to do it but if this is true then increment the exit timer and if the exit timer is greater than the time till exit then we'll go back to the chase state and if we are within the distance then we'll reset the exit timer because we're close enough and he doesn't need to chase us okay let's test and there you go a super simple enemy base and state machine setup you can use for your enemies in your game again my next tutorial is going to pick up right where this video leaves off and we're going to make it a thousand times better by moving the logic from the states themselves and into a scriptable object scriptable objects can very much be more than just data containers I promise you that you just need to know how to work with them do me a favor and if you found this video helpful then please give this video a like so that it gets shared more and if you really want to support my wife and I to allow us to continue making videos like this one right here then continue supporting us on patreon like all of these awesome people I want to give a very special thank you to all of our Hall of Fame patrons Jacobian duck zondra Kessler Darren Preen throbbing wind Fontaine weight couch and Christopher Nichols as well as our Early Access patrons syoma Ken wait and Mason Crow if you choose to support us on patreon you can get early access to all of our YouTube videos monthly Alpha builds and more
Info
Channel: Sasquatch B Studios
Views: 53,064
Rating: undefined out of 5
Keywords: unity, unity2d, tutorial, unity tutorial, sasquatch b, sasquatch b studios, unity beginner tutorial, unity state machine tutorial, unity how to create a state machine, state machine tutorial, unity state machine for enemies, state machine, finite state machine, unity state machine for characters, unity state machine ai, unity state machine scriptableobject, state machine tutorial for beginners, unity state machine tutorial for beginners, unity state machine pattern
Id: RQd44qSaqww
Channel Id: undefined
Length: 16min 9sec (969 seconds)
Published: Thu May 18 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.