Welcome back!
It's been a little while, so hopefully my writing gears aren't too rusty. Next up in my series of programming patterns for games, I'm going to talk about State.
Introduction - What Do You Mean by "State"?
The formal definition for the state programming pattern is: "State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class."
To understand state in programming, let's look at some other uses of the word 'state'. For example, you might say you're in a state of utter joy and happiness; or maybe you're in the state of California. In either case, the state you're in, whether physical or emotional, comes with a set of rules. For example, if you're in Chico, California and you just love going bowling on the sidewalk, I'm sorry to tell you you're out of luck . If you're in a state of joy, you really wouldn't be able to start screaming in anger at someone and still claim to be in a state of joy.
Using 'state' in programming is similar, only we, as programmers, are creating the rules for each type of state, and the objects we're creating are abiding by them. The behavior of objects in your game can be dictated by what state is active, or what state they're in; they could follow a completely different set of rules for each state.
Why It's Useful
As game developers, we often write code that controls the behavior of an object; we make our objects move through space, use items, and take damage, but only when the circumstances are right within the game environment. To illustrate this, let's start with a classic example- jumping. Say you're working on a main character for your game, and you're trying to implement jumping. When the user presses, say, the 'A' button, you want your character to jump. A simple Unity example would look like this:
public class Player : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Jump();
}
}
}
Assuming the code for the method Jump
works correctly, we've got ourselves a character that can jump! However, that character doesn't have any limitations on when it can jump- if a user were to keep pressing the space button over and over, our character would jump higher and higher. We would have to introduce a variable that keeps track of when the character is on the ground, and only allow the player to jump when the character is grounded.
That's an easy enough solution, and it works perfectly well to solve the problem. However, as our game gets more and more complicated, having to create variables to keep track of when each behavior is allowed can quickly lead to a confusing jumble and odd behavior.
Imagine if we introduced flying, ducking, and running behaviors. We would have to set a bool variable to not allow jumping when flying or ducking, but make sure it does allow jumping when running, but only when grounded. Having to keep track of the state of multiple variables each time we want to have our character perform an action can easily lead to bugs and errors, and a big headache. That is just a relatively simple set of behaviors to manage; the more complicated your game, the bigger the chance for something to go wrong and potentially lead to some frustrating debugging sessions. While using a boolean value to dictate when an action can be performed, like with jumping, can be thought of as a simple form of state (the state being "grounded"), that method of controlling state is not ideal for more complicated gameplay. If we find ourselves creating a lot of these bool variables to control behavior in our script, it might be worth using a finite state machine instead.
Finite State Machine
With a finite state machine, or FSMs, we define different states that our object can be in throughout the game and the rules for their behavior in each state. The key is that only one state can be active at a time. With a FSM, we could create a Flying, Ducking and Running state for our player character and control variables for speed or animation differently depending on the state the player object is in. Let's look at an example FSM script created for Unity to see how this works:
public class FSM
{
public enum Step
{
Enter,
Update,
Exit
}
public delegate void State(FSM fsm, Step step, State state);
State currentState;
public void StartFSM(State startState)
{
TransitionTo(startState);
}
public void OnUpdate()
{
currentState.Invoke(this, Step.Update, null);
}
public void TransitionTo(State state)
{
if (currentState != null)
{
currentState.Invoke(this, Step.Exit, state);
}
var oldState = currentState;
currentState = state;
currentState.Invoke(this, Step.Enter, oldState);
}
}
This is a basic FSM pattern that I've used in my own projects- let's break it down!
An FSM should have a transition period between states so that the switch is not instantaneous. If we didn't have a transition period it would be easy for any state to transition to any other state, and part of the purpose of using our FSM is to be able to control if a transition from one state to another is even allowed, such as with the no-jumping-while-flying example. So, we first create an enum Step
that will allow us to divide our State into three parts- Enter, for any code we may need to run or initialize for use while in the state; Update, for any state specific behaviors that need to be called from an update function; and Exit, to clean up after, such as resetting variables or deactivating components.
Next, we create a delegate State function that takes in 3 arguments; the FSM, the step, and the state. We can use this to create our own functions that act as a State. In our character's class, we would simply have to create a function that shares this signature.
The first method of our FSM starts the finite state machine running. The object using the FSM is not in any state until this method is called. For a class that uses our FSM to determine a game objects behavior, we would normally want that to be active as soon as possible after the game object is spawned.
OnUpdate
is what you'd expect- it invokes any behavior specified in the Update section of an FSM State function. What this means for our character's class is that we want to be calling our fsm.onUpdate
function in one of the Update
functions provided to us by Unity.
Finally, as you also might have guessed, TransitionTo exits from the current state, then grabs the new state and transitions to it, which will run any of the code defined in the new state's Enter section before it reaches the code in that state's Update section.
That's all we need for our own finite state machine!
To create a class that uses our FSM, in our script we would create an instance of FSM and set it equal to a new FSM, and call its StartFSM
function with an argument of the state to start in, like this:
public class Player
{
FSM fsm;
FSM.State walking;
void Awake()
{
fsm = new FSM();
walking = FSM_Walking;
}
void Start()
{
fsm.StartFSM(walking);
}
void FSM_Walking(FSM fsm, Step step, State state)
{
if(step == FSM.Step.Enter)
{
//setup code for the state
}
else if (step == FSM.Step.Update) {...}
//and so on with exit
}
void FixedUpdate()
{
fsm.OnUpdate();
}
}
The State pattern
FSMs like the one in the example above are very useful, and they're definitely a step above creating tons of variables that must be set and reset multiple times throughout a script. However, using our FSM comes with its own problems. For one, if we wanted to add, say, a Swimming state to our character, we would have to reevaluate our entire state system to determine allowed transitions to and from the swimming state, which means messing with our Enter and Exit code for each state. It would be better if we could add or remove a state without having to redo a bunch of code to account for the new or removed state. Also, in our game objects script, we may have a lot of variables that are specific to a state, which makes our character script have a lot of responsibility. In Unity, and in object-oriented languages like C# or Ruby, it's often better to use a single-responsibility or component system where each class has one specific responsibility. Controlling for many states in a class sounds like a great candidate for separating some of that logic out of the Player class and into their own more specialized classes.
This is a better State pattern. Instead of many little FSM's running around in each game object containing the same repeated state logic, each state becomes its own class that implements an interface which provides all the step methods we named in our original FSM script, like onUpdate
. The individual game objects only have to worry about which state they are in rather than all the possible states they can be in. Any variables that are only used while in a certain state can be moved to that state's class and out of the Player script.
To do this, we first need to create an interface for all of our state's to implement, like this:
public interface IStateMachine
{
void Enter(Player player);
void Update(Player player);
void Exit(Player player);
}
And this is an example for a State class that implements the IStateMachine
interface:
public class JumpingState : PlayerStateMachine
{
public void EnterState(Player player)
{
Debug.Log("Entering Jumping State");
// Any setup or initialization code for jumping state goes here
}
public void UpdateState(Player player)
{
// Update logic for jumping state
// For example, apply jump force, handle jump duration, etc.
Debug.Log("Jumping...");
}
public void ExitState(Player player)
{
Debug.Log("Exiting Jumping State");
// Any cleanup or reset code for jumping state goes here
}
}
Assuming we have created a walking and jumping state, this is how we might write our code to utilize the different states and switch between them:
public class Player : MonoBehaviour
{
private IStateMachine currentState;
private void Start()
{
// Initialize the player with the Walking state
currentState = new WalkingState();
currentState.EnterState(this);
}
private void Update()
{
// Check for input or any other conditions to change states
if (Input.GetKeyDown(KeyCode.Space))
{
// Switch to Jumping state if the player presses the jump button
currentState.ExitState(this);
currentState = new JumpingState();
currentState.EnterState(this);
}
// Update the current state
currentState.UpdateState(this);
}
}
It's that easy! This makes our Player
script much cleaner. And it's now very easy to add or remove different states if our game changes.
That's it!
And there you have it: the State pattern. It's a pretty handy pattern for controlling the behavior of the various game objects in our game in a modular and easy to manage way.
It's important to point out that this pattern isn't always necessary; it depends on what you're making, how complex it is, and what your needs are. A simple FSM or even a few boolean values can work for a small project. It's important to understand the differences between them to determine which solution is best for you given the circumstances! Also, feel free to comment if you have additional info, or if I got something wrong.
If I was unclear or if you'd like to read more about the State pattern, I definitely recommend checking out this resource for an overview of the State pattern, and this one for another really thorough explanation. This one is great if you want to know more about having State classes inherit from a BaseState class, if you'd rather go that route.
Top comments (0)