Menu
CoddyTech

States as Classes

Part of the Game Architecture in Unity section of Coddy's Unity journey. Lesson 35 of 64.

Section 3's switch on an enum keeps the work of every state in one Update. That stops scaling once states carry data of their own: a timer that only one state uses ends up as a field of the MonoBehaviour, next to the fields of every other state, and each case must remember to reset what it uses. The state pattern from C# gives each state a class of its own, and every class implements one interface. This road beacon blinks or stays lit, and Space switches between the two:

public interface IState
{
    void Enter();
    void Tick();
    void Exit();
}

public class BlinkState : IState
{
    private Renderer lamp;
    private float timer;

    public BlinkState(Renderer lamp)
    {
        this.lamp = lamp;
    }

    public void Enter() { timer = 0f; }

    public void Tick()
    {
        timer += Time.deltaTime;
        if (timer >= 0.5f)
        {
            timer = 0f;
            lamp.enabled = !lamp.enabled;
        }
    }

    public void Exit() { lamp.enabled = true; }
}

public class SteadyState : IState
{
    public void Enter() { }
    public void Tick() { }
    public void Exit() { }
}

public class Beacon : MonoBehaviour
{
    public Renderer lamp;
    private IState blink;
    private IState steady;
    private IState current;

    void Start()
    {
        blink = new BlinkState(lamp);
        steady = new SteadyState();
        SetState(steady);
    }

    void Update()
    {
        if (Keyboard.current.spaceKey.wasPressedThisFrame) SetState(current == steady ? blink : steady);
        current.Tick();
    }

    void SetState(IState next)
    {
        if (current != null) current.Exit();
        current = next;
        current.Enter();
    }
}

The states are plain C# classes, not MonoBehaviours. You make them with new and hand them what they need through the constructor, here the lamp's Renderer, because they are not components and cannot call GetComponent or show fields in the Inspector. Unity never calls their methods either: the Beacon is the MonoBehaviour, so its Update calls current.Tick() every frame.

The timer belongs to BlinkState alone, and its Enter sets it to 0 every time the beacon starts blinking. Exit tidies up when the state ends: Space can come while the lamp is switched off, and Exit turns it back on, so SteadyState never starts with a dark lamp. SetState runs the old state's Exit, then the new state's Enter. A third state, such as a slow pulse, is one more class, and Beacon's Update does not change.

challenge icon

Challenge

Medium

Write the turret's scan state.

  1. In IState.cs, declare public interface IState with void Enter();, void Tick(); and void Exit();.
  2. In TurretStates.cs, write ScanState : IState, given the head. Enter: angle 0, head Color.green. Tick: sweep the head's local y angle between -45 and 45, 90 degrees a second.
  3. Turret: enter one in Start, tick it in Update.

Press Play: the green head sweeps. Then Check.

Try it yourself

using UnityEngine;

// ScanState

All lessons in Game Architecture in Unity

Practice on your own: Unity playground