How to use the state machine - DarkOceanInteractive/project-wendigo GitHub Wiki
State context
Begin by creating a state context: a MonoBehaviour
mainly responsible for holding the current state.
public class PlayerStateContext : AStateContext
{
private void Start()
{
this.SetState(new PlayerStates.IdleState());
}
}
AStateContext
inherits from MonoBehaviour
and provides you with a SetState
method to change state at any time. By default, we can for instance set the current state to an IdleState
that we will define later.
States
To avoid states names conflicting, we isolate states in namespaces. In this case, we want to create states for our player, so we add them to the PlayerStates
namespace:
namespace PlayerStates
{
public class IdleState : AState<PlayerStateContext>
{
override public void Update()
{
}
}
}
The AState
class provides the state class with a default implementation for each member of the IState
interface so that each state can override only the methods needed.
It also exposes a context
member in each state, which refers to the corresponding state context. You can access every member of the state context, including the members of a MonoBehaviour
, such as transform
or the GetComponent
method. Another use case will be explained later.
State interface
The IState
interface defines the methods a state can have and therefore override. Please refer to the current version of the state interface to have an up-to-date list of methods that can be overridden, as it may evolve with time.
Accessing the context's variables
States-specific variables should be declared in the states themselves as much as possible, but sometimes you might want to have some variables modifiable from Unity's Inspector tab. In that case, you can declare them in the state context, and access them from the states. Consider the following example:
- A state context containing a public variable that can be modified through Unity's Inspector tab:
public class PlayerStateContext : AStateContext
{
public moveSpeed = 10f;
private void Start()
{
this.SetState(new PlayerStates.IdleState());
}
}
- States that can access the state context's public members:
namespace PlayerStates
{
public class IdleState : AState<PlayerStateContext>
{
override public void Update()
{
// if any movement inputs, change state to `MovingState`
if (Input.GetAxisRaw("Horizontal") != 0f || Input.GetAxisRaw("Vertical") != 0f)
this.context.SetState(new MovingState());
}
}
public class MovingState : AState<PlayerStateContext>
{
override public void Update()
{
float xAxis = Input.GetAxisRaw("Horizontal");
float yAxis = Input.GetAxisRaw("Vertical");
// if no more movement inputs, change state back to `IdleState`
if (xAxis == 0f && yAxis == 0f)
{
this.context.SetState(new IdleState());
return;
}
var direction = new Vector3(xAxis, 0f, yAxis);
// get `transform` from the state context
direction = this.context.transform.InverseTransformDirection(direction);
// get `moveSpeed` variable from the state context
this.context.transform.Translate(direction * this.context.moveSpeed * Time.deltaTime);
}
}
}
Note: each state should be in its own file. Here, two states are defined side by side for demo purposes.