Creating States for an Actor

What is a state?

A state represents a specific group of actions that should be performed while an actor’s specific condition is met.

The usage strategy

After being correctly crafted, a State script should be attached to the same GameObject as the StateMachine. The machine will then automatically recognize it and use it as it should.

Also, the State class, as the Actor class, is abstract. It is so because a state is specific for an specific actor. Being that way, it does not make any sense to a state to exist without knowing its reason for being.


The code

Below is the base code for a successful state. Take a look at it and feel free to copy and use it as you wish.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Handy2DTools.Actions;

public class MySpecialState : State
{

   #region State Methods

   public void OnLoad()
   {
      // Register your transitions here
   }

   public void OnEnter()
   {
      // Executed at the same frame actor enters this state
   }

   public void OnExit()
   {
      // Executed at the same frame actor exits this state
   }

   public void Tick()
   {
      // Actor's Update()
   }

   public void LateTick()
   {
      // Actor's LateUpdate()
   }

   public void FixedTick()
   {
      // Actor's FixedUpdate()
   }

   #endregion
}

And, of course, there is a Script Template ready for you at Create -> Handy 2D Tools -> FSM -> New State.


Ok. Now that we’ve seen the code, let’s take a deep dive into each of those methods. All these methods are optional and you only need to implement the ones you will use inside your state.

OnLoad()

This method represents the moment a StateMachine loads this State script into its handled states. So it is inside the State.OnLoad() that you should put any logic regardind initialization before the machine starts running.

// Some other code

Func<bool> Falling => () => actor.rigidBody2D.velocity.y < 0;

public void OnLoad()
{
   // Register transitions and stuff
   AddTransition(Falling, GetComponent<FallingState>());
}

// Some other code

The State.OnLoad() is the best place to register transitions. Wich we will cover on the Transitioning Between States section.

This method is called only once in the StateMachine life cycle.

OnEnter()

This method is called the exact moment a StateMachine sets this State script as its current state.

// Some other code

public void OnEnter()
{
   actor.animator.SetBool("isWalking", true);
}

// Some other code

The State.OnEnter() is perfect for start playing animations, initializing variables, properties and etc.

OnExit()

This method is called right before a StateMachine wich has this State script as its current state, transition into another state.

// Some other code

public void OnExit()
{
   actor.animator.SetBool("isWalking", false);
}

// Some other code

The State.OnExit() is perfect place to stop animations and reset variables, properties and etc.

Tickers

Once a State is defined as current state by the machine, its “Ticker methods” will be called at each time its actor “frame handlers” are executed.

They are:

  1. Tick(): Called every time the actor Update() method is called.

  2. LateTick(): Called every time the actor LateUpdate() method is called.

  3. FixedTick(): Called every time the actor FixedUpdate() method is called.

// Some other code

float xSpeed = 10.0f;
float xInput;

public void Tick()
{
   xInput = Input.GetAxis("Horizontal");
}

public void FixedTick()
{
   actor.rigidBody2D.velocity = new Vector2(xInput * xSpeed, actor.rigidBody2D.velocity.y);
}

public void LateTick()
{
   if (xInput != 0) {
      actor.camera.ZoomIn();
   }
   else {
      actor.camera.ZoomOut();
   }
}

// Some other code

Hint

If you are not familiar with the LateUpdate() method, it is called once per frame after the Update() as a way to organize your script execution.

Refer to the Handy 2D Tools FSM Life Cycle for visual information about Tickers being called.

Transitioning Between States

There are two ways for a state to transition into another state:

  1. By calling the Machine’s ChangeState() method.

Note

As seen on the State Documentation, all the states hold reference to the Machine they belong to. Therefore, accessing the machine’s methods from within a state is as easy as using its State.machine property as we can see below.

// Some other code

public void FixedTick()
{

     if (somethingWeirdHappened)
     {
        State newState = GetComponent<OtherState>();
        machine.ChangeState(newState);
     }

}

// Some other code
  1. By registering a StateTransition inside its State.OnLoad() using the State.AddTransition() method.

Using this approach (wich is highly recommended) the machine will, at each of the “frame handlers”, evaluate if any condition is met considering given priorities. If so, it will automatically transition into the target state.

A StateTransition consists in a Condition, a target State and a priotity level. But there is no need for you to instantiate it. Just call the State.AddTransition() method and pass the those 3 arguments. I take care of the rest for you. Here is how you use it:

Func<bool> Condition => () => something > anotherThing;
State someState = GetComponent<SomeState>();
int priority = 1;

AddTransition(Condition, someState, priority);

But, if you somehow prefer, here is how a StateTransition is instantiated and registered:

Func<bool> Condition => () => something > anotherThing;
State someState = GetComponent<SomeState>();
int priority = 1;

StateTransition transition = new StateTransition(Condition, someState, priority);
AddTransition(transition);

Note that to declare a Condition you MUST approach using a Func<TResult> Delegate. Do not worry if you are not familiar with this yet. All you need to know is that the machine will call this as a method (function) and its context will be evaluated as true or false only when the machine do so. In case this represents news for you, for now, just follow the syntax in the code below each time you want to register a StateTransition in a state.

To declare the target state, since all states are components attached to the same GameObject, you can just use the GetComponent<State>() method.

At last, priority for the machine is read descending. Wich means the higher the integer you give as third argument, sooner its condition will be evaluated. otherwise it will be read as the declaration order. The default priority value is 0.

// Some other code

// Example of conditions using delegate functions and recurring to lambda functions
// IMPORTANT!! It MUST be a delegate function. Check the docs for further understanding
protected Func<bool> Idle => () => actor.rigidBody2D.velociy.x == 0; // Here we take advantage on lambda functions so we do not need to declare a method.
protected Func<bool> Moving => () => actor.rigidBody2D.velociy.x != 0;

// Called to set the state able to be used by the Machine.
// Usually where you should register state transitions.
public void OnLoad()
{
    // Example of how to add transitions
    AddTransition(Idle, GetComponent<MovingState>()); // No priority means priority = 0
    AddTransition(Moving, GetComponent<MovingState>(), 1); // Setting higher priority on third argument for this one. It will be executed before.
}

// Some other code

Note

The State.OnLoad() is called only once on the Machine’s life cycle. Meaning that any GetComponent<State>() (or, well… anything else) used inside of it will not be called multiple times.

The inspector

For a simple state, a simple name. Just that. You can set a Name for your state using Unity’s inspector so you get some better visual feedback when taking a look at the state machine.

Set a name:

State Name on inspector

And see it in action while inpecting the StateMachine:

State Name appearing on machine

That is it about creating a state for now. Let me show you a little about how to use our precious state machine.