During development, the scripts in our games
are going to get more complex, and that is why it’s so important that our code is organized,
easy to understand and expandable. Today we are going to do something incredibly
necessary when programming our games to achieve and maintain these characteristics. We are going to refactor our current character
controller script by implementing a hierarchical state machine, state factory and adjust for
just proper naming conventions for our current variables! Of course, you can grab access to this project’s
files and support the channel over on Patreon.com/iheartgamedev. And now! Let’s get started! As a quick refresher, in the previous videos
of this series we learned to make our character move and jump! We did so by adding the built in character
controller component to our Jammo character model that’s free to use from the hit youtube
channel, Mix and Jam. We added animations to Jammo, and integrated
Unity’s new input system to monitor the player’s input on both keyboard and controller. In our code, which is where we will be spending
the majority of this tutorial, we set up callback functions to handle each of the player’s
inputs that we set with the new input system. The callbacks update variables that we use
to control Jammo’s movement and transition between its animations in additional functions
that are appropriately titled “handleAnimation”, “handleRotation” and within the “Update”
function. For jumping specifically, we attempted to
replicate Mario’s jump by creating 3 different jump heights and jump durations, that are
set when our character is first instantiated into the game. The “handleJump” function is called when
the player first presses the jump button and “handleGravity” is always being called
in “Update” to constantly enforce gravity on Jammo. If you really want to understand all of this
code, I highly suggest checking out the previous videos and learning from there! But, we are now at least on the same page
of what each function generally does and have a firm overview of the project. So this is our starting code and by the end
of this video, it will be broken down into a hierarchical state machine. And we’ll have a solid understanding of
the fundamentals behind that. To begin this code refactor, let’s talk
a little bit about C# naming conventions. Variables in C# are typically written in Camel-Case
or Pascal-Case. Camel-case is when the first letter of the
variable is lowercase and any proceeding words within the variable have their first letter
capitalized. Pascal-case is just like Camel-case except
the first letter is capitalized. Now, when do we use each when programming
in Unity? We will notice that when generating a new
script from within the Unity editor, the auto-generated class name is Pascal-Case. That is to say, classes in C# are typically
written using Pascal-case. We will also notice that the auto-generated
Start and Update variables are capitalized too! This is because Methods and Functions are
written using Pascal-Casing, as well. Now let’s say we have an example function
and it takes a parameter. The class type of the parameter, as we know,
will remain Pascal-case, but the parameter itself will be Camel-case. And if we were to create a variable local
to this function, it would also be camel-case. The same can only sometimes be said to the
class’s local variables, otherwise known as member variables. If a member variable has the public or protected
access type, it is typically Pascal-cased. However, private member variables are denoted
by an underscore and then camel-cased. If we want to get more specific, an older
method would prefix an “m” prior to the underscore. This prefix is lifted from what’s known
as “Hungarian” notation, so go ahead and check that out if you are interested in learning
more. A couple of noteworthy specifics include static
variables being written at the top of the Class. Interface declaration, prefixes a capital
“I” to a Pascal-written name. And Enums are also written using Pascal. Cool! Opening up our current AnimationAndMovementControllerWithJumping
script, we will now use this knowledge to refactor the script’s variables. Most noteworthy is that we’ll be adding
underscores to the member variables. And already, we can see that in the script’s
functions, we can more easily tell the difference between variables defined in the scope of
the class versus variables local to the function. Wonderful! Ok! So what is wrong with our current script? As we can see, it clearly works, and is written
using relatively understandable functions. However, the logic is already growing to be
a little more complex than it should be and there’s not even that much going on: Jammo
can move and jump. There is no crouching, swimming, dodging,
wall running… none of that. If we were to add all of this functionality
for Jammo in this single script, not only would it get much bigger, but it would get
so much more complex because we would need to add extra conditions so that the new logic
wouldn’t interfere with the old logic, and vice-versa. On top of that, all of the conditional logic
would always be running with every update which could lead to performance issues! So, what do we do? If we remember from the explanation of state
machines in the original State Machine tutorial on this channel: we can solve this “massive
script” problem by essentially splitting the logic into smaller chunks called States. Each State will contain the functionality
only relevant to that given state. For example, in a Running state, we’ll have
logic that controls Jammo’s movement speed! Jammo will then switch between each of the
states we’ve defined so that only one state and it’s associated logic is active at a
given time. Clean, efficient and understandable. However, in this tutorial, we’re going to
take State Machines to the next level with a Hierarchical state machine. In a hierarchical state machine, within each
state, there can be a “substate” and even further, that state can have its own substate
and so on! Hierarchical state machines allow an increase
in overall complexity of the machine while still providing the same benefits of a finite
state machine: only running code necessary to the current states, minimizing conditional
statements, easier debugging… etc.. As an example, in our current project: if
Jammo is on the ground the gravity will be different than when it’s in the air. But in both, we still want Jammo to be able
to move around and rotate. So what we’ll do is have a two tiered hierarchical
state machine: the root state will handle Jammo's gravity, the sub-states will handle
the transitions between the idle walk and run movement states. To begin, let’s break down how state machines
actually work, and don’t worry if this doesn’t all make sense at the start. Hopefully it will by the end of the video
after we implement it ourselves. With state machines, we have three different
concepts: Context, Abstract State and Concrete States. Each State that Jammo can possibly be in when
the game is running will be a Concrete State. As mentioned, this includes running, and we
also currently have walking, idle, grounded and jumping. These “Concrete States” are all individual
classes that derive from the same Abstract State, or Base Class. This means that they inherit the public and
protected methods and variables defined from that Base Class. Deriving from the abstract state guarantees
that all of the Concrete States will have particular functions and variables which will
keep our code clean. The context is what handles the instances
of concrete states. Context will pass data to the currently active
concrete state, and in the case of hierarchical state machines: active states, so that the
states can react with their specific logic. For example, the context can pass the player’s
input to the running state which will then adjust Jammo’s current velocity. Data passed to the concrete states from the
context will also determine when one concrete state should switch to another. Like if the jump button is pressed from the
grounded state, it should switch to the Jump State and Jammo should jump. And that’s about it for the concepts of
a state machine! Now, let’s begin its implementation. In our project, we will create a new folder
in our assets titled “StateMachine”. Here we will create a new script “PlayerStateMachine”
and move it into the folder. We will continue by adding the rest of the
scripts we will need for this entire tutorial: “PlayerBaseState”, “PlayerGroundedState”,
“PlayerJumpState”, “PlayerIdleState”, “PlayerWalkState”, “PlayerRunState”
and “PlayerStateFactory”. All of our new scripts that we just created
will essentially replace our current “AnimationAndMovementControllerWithJumping” script. However, they will retain a lot of the same
logic, and if the state machine works as intended, Jammo should function exactly the same. So let’s remember that going forward. Looking at our blueprint of a state machine,
we can map these out to their purpose: “PlayerStateMachine” will be considered the “Context”. “PlayerBaseState” refers to the abstractState. Grounded, Jump, Idle Walk and Run are all
concrete states. And the StateFactory creates the concrete
states from within the context. Let’s start with the context. Opening the PlayerStateMachine script, we
know that the context will store a lot of the data that the concrete states need to
perform their logic. What this means is that the PlayerStateMachine
script will retain all of the variables from the old animationAndMovementController script. Let’s go ahead and copy all of the member
variables over now. It’s worth noting, that by keeping the variables
in the context instead of within the concrete states themselves, we could technically have
multiple state machines running off of the same data. In addition to the variables, we are going
to want to keep a few of the functions, as well. We still want the callback functions that
we use to track the player’s input using the new input system: onMovementInput, onJump
and onRun. Be sure to add the namespace, UnityEngine.InputSystem,
at the top of the file. And we’ll need to go ahead and copy the
entire Awake method which sets the callbacks, and assigns some of our member variables like
the character controller, the input system “player input”, and the animator. We’ll also see the “setupJumpVariables”
function being called. Again this function makes sense to keep within
the State Machine class because it’s where we first assign these values that the jump
state will use. So we’ll copy and paste that function. Our update function will keep the characterController’s
call to move because it’s used in all of our movement states, we’ll also keep “handleRotation”. The last we’ll copy is the OnEnable and
OnDisable methods which are used to enable and disable the Action map of our input system. Here’s a quick look through our code. And if we replace our original script with
our new playerStateMachine script, in play mode we’ll see Jammo currently only able
to rotate. Ok! We will come back to the context many times
as we go forward, but for now let’s move on to the Abstract State, or PlayerBaseState
file. The purpose of the abstract state is to establish
methods that all concrete states must have. This is because Concrete states derive from
the base state. To start, we will remove all of the “using”
namespaces from this file. We won’t need them. Next, we’ll remove “Monobehavior” from
the class declaration and add “abstract” after the access type. Abstract means that we cannot create an instance
of this class. Remember we only create instances of the concrete
states. Next we’ll create the following methods
for concrete states and go over each in detail when we use them: EnterState, UpdateState,
ExitState, CheckSwitchStates and InitializeSubState. All of these methods will also be declared
“abstract” which means that the concrete state classes will need to define their functionality
themselves. Below the abstract functions, we will declare
the remaining functions to be locally defined later: UpdateStates, ExitStates, SwitchState,
SetSuperState and SetSubState. In all five of our concrete states, we will
replace “Monobehavior” with “PlayerBaseState”. Immediately, an error will appear in each
stating that we need to define the abstract methods from the base state. Let’s write them all as empty methods for
now, but this time with the term override instead of abstract. Great! Despite not doing anything, we can now create
instances of these concrete states! In our context file, we can declare a new
member variable “_currentState”. And, thanks to Jason Storey for the recommendation,
we have a clean way of creating concrete states: a State Factory. In our PlayerStateFactory file, we’ll remove
the using namespaces and “Monobehavior”. We’ll declare a member variable “_context”
at the top of the file of type player state machine and then we’ll write what’s known
as a constructor by typing “public PlayerStateFactory that expects a parameter currentContext of
type PlayerStateMachine”. The constructor function is what’s called
when we create a new instance of a class, so here we’re saying that a new instance
of a PlayerSateFactory requires a PlayerStateMachine to be passed in. Inside the constructor function, we’ll set
the _context member variable to the passed in currentContext argument! And now the StateFactory will hold reference
to our State Machine! Next we’ll declare five new public methods,
one for each of our concrete states: Idle, Walk, Run, Jump and Grounded of type playerBaseState. Inside each, we’ll return a new instance
of their respective concrete states. For example, Idle returns a new PlayerIdleState
instance. Awesome. Back in our context file, let’s declare
another member variable titled “_states” and in our Awake method set that equal to
a “new PlayerStateFactory” passing in “this”. Remember, in the PlayerStateMachine file,
“this” refers to itself, which is a PlayerStateMachine instance. And like we just programmed, PlayerStateFactory’s
constructor expects a PlayerStateMachine instance to be passed in! Right below that in the awake method, we’ll
set _currentState equal to the invoked _states GroundedState method. And then invoke _currentState’s Enter State
method. We now have our groundedState instance set
to the current state within the context. If we place a Debug.Log inside the enter state
of the PlayerGroundedState, adding the PlayerStateMachine script to Jammo and pressing play -- we will
now see the statement logged! Great! We have entered our first instance of state. Now a big feature of state machines is switching
between states. In our PlayerBaseState file, we’ll declare
and define a method, “SwitchState”, that will accept a parameter “newState” of
type “PlayerBaseState”.”SwitchState” will first call “ExitState” which means
that whatever the current state is will perform the logic defined in its ExitState method. We’ll then call “EnterState” on the
“newState”, and end the method by setting the context’s “_currentState” to the
newState. But wait, we don’t currently have access
to the context from within our concrete or base state. Fortunately, our playerStateFactory makes
this an easy fix! In our PlayerStateFactory class, we can pass
in the same context that we stored in the playerStateFactory’s constructor as an argument
for each new concrete state. We’ll also pass in “this” as a second
argument, giving each concrete state access to the factory itself! Immediately, we’ll be met with an error
stating that the constructors of each concrete states do not expect an argument to be passed
in. So in each of our concrete states, we’ll
go through and add constructor functions that have a parameter “currentContext” of type
PlayerStateMachine and “playerStateFactory” of the same type. And to take this even FURTHER we can create
a constructor function inside of the base state that does the same thing. In the base state we’ll add a protected
variable titled “_ctx”, short for context and a protected variable “_factory”. We’ll create a constructor that expects
“currentContext” and a playerStateFactory parameter, the same as the concrete states. Then we’ll set _ctx equal to the currentContext
and we’ll do the same for the _factory member variable with the playerStateFactory. Now we’ll get errors in all of the concrete
states again. This is because the base constructor now expects
these arguments. To fix this, we’ll extend each state’s
constructor by writing “: base” and pass in the arguments of from the local state’s
constructor. What this does is pass the concrete state’s
argument directly to the BaseState’s constructor. Now in the base state, we have access to the
context. This means we can try to set the context’s
current state to the newState. But wait! Another error. This time it’s because the context’s current
state is not publicly accessible. We could just add the “public” access
type, but we also have the option to create “getter” and “setter” methods for
member variables. Getter and Setters are a cleaner way to access
member variables of another class. They allow us to designate whether we want
the accessing code to only be able to read a value, by using the getter, and or able
to change the value using the setter. The notation to declare a getter setter is
to write the access type, in this case “public”. Followed by the type of the variable, “PlayerBaseState”
for the currentState, and a Pascal notation variable typically of the same name of the
member variable: in this case “CurrentState”. We then use brackets and add whichever of
the getter setter methods we want. In this case, we’ll add both, so we’ll
type “get brackets return _currentState and set _currentstate is equal to value. Now in the switchState method of the base
state, where we have access to the context, we can use the setter to reassign the value! Awesome! We’ll end up using getters and setters for
pretty much all of our member variables that are accessed from concrete states. Doing so makes sure that we know exactly how
we are using those variables. Ok! Because the _ctx variable is “Protected”,
that means that tis variable is inherited by the classes that derive from the PlayerBaseState. This means we have access to the context and
state factory in each of our concrete states, and we also have a working “SwitchState”
method. But when should we use the switchState method? We’ll remember we created the method titled
“checkSwitchStates” on each of our concrete states. When should the Grounded State switch to another
state? Well, we can imagine that if the player presses
jump when they’re on the ground, it should switch to the playerJumpState. Using our knowledge of getter setters, we
can see in our PlayerStateMachine that “_isJumpingPressed” is updated when the player presses the jump
button. So we can create a new getter for IsJumpPressed
and access that in the groundedState. If IsJumpPressed is true, we’ll call SwitchState
and pass in the invoked Jump method of the inherited _factory. That’s the beauty of having access to both
the context and the factory from within the concrete states. It encapsulates the state machine logic so
that we don’t need to write public state-related functions inside of the context. Instead they can be handled inside the state
itself. So what happens when we switch to the new
state? From our switchState method, we know that
“EnterState” is invoked on the PlayerJumpState. If we look at our original script, we have
conditions set in handleJump so that it occurs only one time when the player first presses
jump and they are grounded. Well, that’s the point of the GroundedState
- Jammo is grounded. And we know that EnterState is only called
one time! So we can copy the logic from within that
conditional and paste it in it’s own handleJump function of the playerJumpState, then invoke
it in the EnterSTate function. However, remember we need to access all of
these variables from the context now. This means that we should make getters and
setters for each variable. And then replace each variable in the logic
of the PlayerJumpState with the new getter or setter. Now remember, we still need to call CheckSwitchStates
somewhere for its logic to actually run. As we know, Update is supposed to run every
frame of the game and we definitely should check if the state should switch every frame. So in all of our concrete states, we’ll
invoke the CheckSwitchStates method. However, because our concrete states do not
derive from Monobehaviour anymore, we know that the Update method will not be run. This isn’t the case for the context though! So we just need to invoke the currentStates
public update method in the PlayerStateMachine’s update method! Testing in play mode and pressing jump, Jammo
will jump! However, as we can see, we’re still missing
a lot of logic -- like Gravity! In our original script we had a function handleGravity
that had multiple conditional statements. The first condition would be true when the
character controller was grounded. That logic would start the coroutine to reset
the jump count as well as apply the lower gravity amount. What’s cool about state machines, is now
we can keep the logic about the jump tied to the jump state! We’ll take the snippet related to the jump
and place it inside the ExitState method of the PlayerJumpState. Like before, we’ll now switch the variables
to getters and setters. And we’ll also move the definition of the
IJumpResetRoutine into the Jump class as well. ExitState is called during our switchState
method, so let’s also finish our checkSwitchState implementation for the JumpState. If the characterController is grounded, we
want to switch back to the grounded state. And this will call the EnterState method of
the grounded state. With the remainder of that original conditional
logic, In the GroundedState’s EnterState method, all we need to do is set the current
and applied movement Y values to the grounded gravity value from our context. For these setters, we can target the y value
of the Vector3 by declaring the type of the y value: a float. And now, we can just copy and paste the remaining
handleGravity logic into the PlayerJumpState and call it inside of Update. Testing in play mode and boom! Our jump logic is working and switching back
to our GroundedState! The only oddity is that we can now hold jump
and Jammo will continue to jump. To quickly fix this, we also now have an unnecessary
variable from our previous implementation: “isJumpAnimating”. With the JumpState’s exit state method,
we know Jammo won’t be animating because we disable the boolean with SetBool. So let’s rename this to “requireNewJumpPress”. We’ll then set this to true if jump is pressed
on exit of the jump state. In the context, we’ll rename the member
variable, getter and setter. And in our onJump callback, set it to false
when the jump button is pressed. Then in our groundedState’s CheckSwitchState
method, we’ll require the jump button to be pressed and the new RequireNewJumpPress
bool to be false. And now our state machine is effectively switching
between jumping and grounded exactly like before but only running the logic necessary
to do so! Perfect! Let’s finish this up with the hierarchical
aspects of the state machine implementation. Currently Jammo can Stand and Jump, and while
it’s doing this we want it to be able to move around. This logic will be handled in Jammo’s substates! Back in our PlayerBaseState, we have the remaining
methods “InitializeSubState”, “SetSubState”, “SetSuperState” and “UpdateStates”. Let’s first define “SetSubState” and
“SetSuperState”. “SetSub” will expect a parameter “newSubState”
of type PlayerBaseState, and “SetSuper” will expect a parameter “newSuperState”. “SetSubState” will set a new protected
member variable, _currentSubState, equal to this value. And “SetSuper” will set a new member variable
_currentSuperState equal to its parameter, as well. SetSub will then call SetSuperState on the
newSubState, passing “this” as its argument and therefore setting itself as the super
state of its own new substate! What this means is that anytime we call “SetSubState”,
we create almost a parent child relationship, but we also create the inverse as well: child
to parent. With that in mind, we can now go to our PlayerGroundedState
and PlayerJumpState to overwrite the inherited InitializeSubState method. Remember, we have three possible substates:
idle, walk and run. And we want to create the correct substate
from the moment both parents are constructed. In our original code, we had a method “handleAnimation”
that distinctly tells us when we want to transition between each. We can use this similar logic inside of the
“IntializeSubState” methods. We want a substate of “Idle” when the
character is not moving and not running. We want a subState of “Walk” when the
character is moving but not running. And we want a subState of “Run” when the
character is moving and running. And as shown, we’ll create the associated
getters for these two member variables of the state machine. We’ll then call each InitializeSubState
in the constructor of the Grounded and Jump states so that the proper substate, idle walk
or run, is created regardless of which superState is active. We can go ahead and add the proper logic in
each of these three substates to switch between one another: Idle should switch to both Walk
and Run depending on which of the movementPressed and runPressed variables are true. Walk should switch to either Idle or Run. And Run should switch back to Walk or Idle. Same as before, we call “CheckSwitchStates”
in our UpdateState method. Let’s quickly finish the movement logic
for each of these states. In the EnterState method of each, we will
access the context’s animator and appropriately set the IsWalking and IsRunning Hash booleans. In Idle, we’ll set the AppliedMovementX
and Z values to 0 in the EnterState method. In Walk, we’ll set AppliedMovementX and
Z values to the corresponding CurrentMovementInput values inside of the Update function. And the same for the Run state but multiplying
by a larger run multiplier. We’ll then create the appropriate getters
and setters in the context. If we enter play mode now, we’ll immediately
notice something. The substates aren’t doing anything. That’s because Update is only being called
in the context on the “CurrentState” but not its substates. Let’s change invocation to UpdateStates,
and back in our playerBaseState we will set updateStates to public and give it the following
definition: it will call its own UpdateState method, then if it has a subState will call
updateStates on that. This will cause all substates that are a part
of the “SuperState-SubState” chain to Update! We can actually do something similar for ExitState
with an ExitStates method, but we don’t have the need to with our current machine. Just a couple more things to fix now! Entering play mode once again, we can move
around -- however, we can no longer jump! But why? The answer is in our SwitchState method. What happens when we switch state from Idle
to Walk is that the context’s current state becomes the sub state! We need a way to tell if the concrete state
is at the top of the state hierarchy. To do this, in our playerBaseState, we’ll
create a new member variable: _isRootState that will default to false. We can then use this as a condition inside
of our SwitchState method so that we only switch the Context’s current state if it’s
at the top level of the state chain. And now in our Grounded and Jump States, we’ll
set this value to true in their constructors! But what about switching states that are not
root states? Well, if it’s not a root state, that means
that it’s a sub state and, therefore, will have a super state. So we’ll add a second condition that checks
to see if it has a super state, and if it does: to set the superState’s substate to
this new state. Essentially transferring the super state over
to the next state. Ok! One FINAL change we’ll need to make in this
script some of you may have picked up on. We’ve been using the private member variable
notation for a protected member variables in our base state. We can go ahead switch these to private, and
then create protected getters and setters for IsRootState, CTX and Factory And that’s it! Jammo is now working exactly as it was before,
but we now have the power of a hierarchical state machine that splits our previous logic
into condensed states and enjoy the many benefits that come with a state machine. Awesome job! There are definitely changes that can be made
here, and if you have recommendations, feel free to leave them in the comments. I love learning from the community! To vote on the next tutorial on this channel,
be sure to checkout the iheartgamedev patreon! And of course the biggest thank you to all
of the current supporters! You help make this happen. If you’re interesting in joining this awesome
growing community, we’d love to have you in the channel discord, and for updates on
the next video, be sure to follow me on twitter. But that’s all for today, thank you so much
for watching and I’ll see you in the next video!