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
MediumWrite the turret's scan state.
- In
IState.cs, declarepublic interface IStatewithvoid Enter();,void Tick();andvoid Exit();. - In
TurretStates.cs, writeScanState : IState, given the head. Enter: angle 0, headColor.green. Tick: sweep the head's local y angle between-45and45,90degrees a second. 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
1Components, Not Hierarchies
Small ComponentsA Base Enemy ScriptAbstract ComponentsComponents Working TogetherRecap: Modular TurretRecap: Twin Cannon4Serialized Data
Serializable ClassesLists in the InspectorEnums in the InspectorRecap: Loot TableRecap: Roll the Loot7State Machines
States as ClassesThe Alert StateA State MachineTransitionsEnemy BrainsThe Knight AttacksRecap: Door States2Interfaces in Unity
IDamageableInterfaces and TryGetComponentGetComponentsRecap: Interact KeyRecap: Nearest in ReachPractice on your own: Unity playground