States as Classes
Part of the Game Architecture in Unity section of Coddy's Unity journey. Lesson 28 of 50.
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
MediumA sentry turret guards the yard. The arrows walk the Player (ready). Give the turret two states, written as classes:
- In
IState.cs, declarepublic interface IStatewithvoid Enter(),void Tick()andvoid Exit(). - In
TurretStates.cs, write two plain classes that implement it.ScanStategets the turret's head in its constructor. It sweeps the head from side to side: the head's local y angle moves at 90 degrees a second between-45and45, starting from 0 and turning upward each time scanning starts. Its Enter also colors the headColor.green. AlertStategets the head and the player. Its Enter colors the headColor.redand logsIntruder!; its Tick turns the head to face the player around y only, with no tilt; its Exit logsAll clear.- In
Turret.cs, make both states in Start and start scanning. Each frame, switch to the alert when the player is closer thanrange(4) to the turret, and back to scanning when they are not, then run the current state's Tick. A change runs the old state's Exit, then the new state's Enter.
Press Play, walk up to the turret and away again, then Check.
Try it yourself
using UnityEngine;
// ScanState and AlertState
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Game Architecture in Unity
1Components, Not Hierarchies
Small ComponentsA Base Enemy ScriptAbstract ComponentsComponents Working TogetherRecap: Modular Turret10Project: Arena Survivor
The HeroEnemy Types