My First State Pattern

Hey all,

Terrific course thus far! I’ve never attempted to code any StateMachine/StatePattern/FiniteStateMachine… (at least that I’m aware of… hehe)… so, without further ado!

My little thing I created after watching the lecture


Interfaces

public interface IState
{
    void Grounded(IStateContext context);
    void Jump(IStateContext context);
    void Fall(IStateContext context);
    void Land(IStateContext context);
    void Crouch(IStateContext context);
}
public interface IStateContext
{
    void SetState(IState newState);
}


StateBehaviour

using UnityEngine;

public class StateBehaviour : MonoBehaviour, IStateContext
{
    [SerializeField] LocomotionState currentState;

    public void Crouch() => currentState.Crouch(this);
    public void Fall() => currentState.Fall(this);
    public void Jump() => currentState.Jump(this);
    public void Land() => currentState.Land(this);

    void IStateContext.SetState(IState newState) => currentState = newState as LocomotionState;
    

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Q)) Crouch();
        if (Input.GetKeyDown(KeyCode.W)) Fall();
        if (Input.GetKeyDown(KeyCode.E)) Jump();
        if (Input.GetKeyDown(KeyCode.R)) Land();
    }
}

States Information

using UnityEngine;

public class LocomotionState : ScriptableObject, IState
{
    [SerializeField] protected LocomotionState[] states;

    public virtual void Grounded(IStateContext context){}
    public virtual void Jump(IStateContext context){}
    public virtual void Fall(IStateContext context){}
    public virtual void Land(IStateContext context){}
    public virtual void Crouch(IStateContext context){}
}

Overriding!

using UnityEngine;

[CreateAssetMenu(fileName = "Grounded LocomotionState", menuName = "Locomotion/New Grounded LocomotionState")]
public class GroundedState : LocomotionState
{
    public override void Crouch(IStateContext context)
    {
        context.SetState(states[0]); // <- Grounded
    }

    public override void Fall(IStateContext context)
    {
        context.SetState(states[1]); // <- InAir
    }

    public override void Jump(IStateContext context)
    {
        context.SetState(states[1]); // <- InAir
    }
}
using UnityEngine;

[CreateAssetMenu(fileName = "Crouch LocomotionState", menuName = "Locomotion/New Crouch LocomotionState")]
public class CrouchState : LocomotionState
{
    public override void Crouch(IStateContext context)
    {
        context.SetState(states[0]); // <- Grounded
    }

    public override void Fall(IStateContext context)
    {
        context.SetState(states[1]); // <- InAir
    }

    public override void Jump(IStateContext context)
    {
        context.SetState(states[1]); // <- InAir
    }
}
using UnityEngine;

[CreateAssetMenu(fileName = "InAir LocomotionState", menuName = "Locomotion/New InAir LocomotionState")]
public class InAirState : LocomotionState
{
    public override void Land(IStateContext context)
    {
        context.SetState(states[0]); // <- Grounded
    }
}

Key Differences:
Rather than spawning new instances of each different State script, I just made my States ScriptableObjects.

Another take away is that the main ‘LocomotionState’ class that each State is inheriting has virtual members so they can be overriden to do unique things inside when they themselves are called. I guess it means there’s some flexibility… not all Land calls will do the same thing… depending on the additional code within those State override methods…

Lastly the States themselves have children states within an array that get set when overriding.

Anyways, awesome course!

The one thing to watch out for with this is that ScriptableObjects are a shared resource… so every StateMachine running that resource will share the same instance level variables… Fortunately, we’re already passing the context (the state machine), so just remember that any persistent state info will have to remain on the context, not on the state itself.

Just so I can further cement the understanding and please do correct me if I’m wrong.

By a shared resource do you mean that for example, each State of ‘GroundedState’ SO can potentially share any variables with one another? So if it wasn’t for me directly using the ‘IStateContext’ then this would be an issue because it would be accessing information the other states shouldn’t know about each other?

Instance level variables are the information each state stores individually in their SO?

Just trying to wrap my head around it logically

If you create multiple GroundedStates through the [CreateAssetMenu], i.e. creating an asset in the Assets folder, each individual instance has it’s own instance variables. If, however, you have multiple characters referencing the same ScriptableObject (asset in the assets folder), any instance variables are shared for everybody accessing it.

A great example of this is in the RPG course with the WeaponConfigs… Each weapon type has it’s own WeaponConfig, a Sword, a Dagger, Fists, etc. It’s common for several of the enemies to be using the same WeaponConfigs (all grunts use swords, all archers use bows, etc). Rather than having 100 bows in the assets, we simply have all of the archers reference the bow.

A common pitfall some of our students in the RPG Course run into is the idea that all they need to do is store the current instance of the weapon that is instantiated by the WeaponConfig in the WeaponConfig itself. They do this because some students think the way we find the weapon on the character (when the character changes weapons) to be cumbersome… but if 100 archers all equip a bow, the only bow that will be stored in the instance would be the last one… To top that off, as each archer equips their bow, the model on the previous archer would be destroyed, meaning that only the last archer would have his bow showing…

Because of this, we have to gather our state from the information provided by the Fighter, which for my example is the context.

As long as any state is retrieved from the context, rather than being stored in an instance variable, your scriptable states can be used by every character.

Exactly

Yes, each asset on the disk is an instance of the ScriptableObject. Instance level (global) variables are unique to each asset. It only gets risky because every link to the same asset would be using the same instance level data from the SO.

Privacy & Terms