Skip to main content

State Machine

A state machine design pattern is a way of designing a system that can be in one of several specific states (modes), with rules for switching between these states. Each state represents a different behavior or action, and the system moves between them based on certain events or conditions. It’s useful for organizing systems that change behavior, like user interfaces or games.

State Machine Pattern

interface State {
enter(): void;
exit(): void;
update(dt: number): State;
}
class StateMachine {
private currentState: State;

constructor(initialState: State) {
this.currentState = initialState;
this.currentState.enter();
}

public changeState(newState: State): void {
this.currentState.exit();
this.currentState = newState;
this.currentState.enter();
}

public update(dt: number): void {
var state = this.currentState.update(dt);

// if the state changes, change the current state
if (state && state !== this.currentState) {
this.changeState(state);
}
}
}

We have a StateMachine class that manages the current state of the system. It has methods to change the state and update the current state. The State interface defines the methods that each state must implement.

Let's implement an example using the state machine pattern.

Weapon System

A weapon can have different states like Idle, Firing, Reloading, etc. We need a state machine to manage these states.

class Weapon {
public fireRate: number; // shots per second
public reloadTime: number; // seconds
public magazineSize: number;
public currentAmmo: number;

private stateMachine: StateMachine;

constructor() {
this.stateMachine = new StateMachine(new IdleState(this));
}

public update(dt: number): void {
this.stateMachine.update(dt);
}

public spawnBullets(): void {
// spawn the bullet(s)
// this method can be also a state
}

public startFiring(): void {
this.stateMachine.changeState(new FiringState(this));
}

public stopFiring(): void {
this.stateMachine.changeState(new IdleState(this));
}
}

Each state of the weapon system will implement the State interface, anh they should have a reference to the weapon object. So, we need a WeaponState class for all the states to extend.

class WeaponState implements State {
protected weapon: Weapon;

constructor(weapon: Weapon) {
this.weapon = weapon;
}

public enter(): void { }
public exit(): void { }
public update(dt: number): State { }
}

Let's define the states.

class IdleState extends WeaponState {
public enter(): void {
console.log("Weapon is idle");
}


public update(dt: number): State {
// do nothing
return this;
}
}
class FiringState extends WeaponState {
private timeSinceLastShot: number;

constructor(weapon: Weapon) {
super(weapon);
this.timeSinceLastShot = 0;
}

public enter(): void {
console.log("Weapon is firing");
}

public update(dt: number): State {
this.timeSinceLastShot += dt;
if (this.timeSinceLastShot >= 1 / this.weapon.fireRate) {
this.weapon.currentAmmo--;
this.timeSinceLastShot = 0;
weapon.spawnBullets();

// out of ammo, go to reloading state
if (this.weapon.currentAmmo <= 0) {
return new ReloadingState(this.weapon);
}
}

// still firing
return this;
}
}
class ReloadingState extends WeaponState {
private timeSinceReload: number;

constructor(weapon: Weapon) {
super(weapon);
this.timeSinceReload = 0;
}

public enter(): void {
console.log("Weapon is reloading");
}

public update(dt: number): State {
this.timeSinceReload += dt;
if (this.timeSinceReload >= this.weapon.reloadTime) {
this.weapon.currentAmmo = this.weapon.magazineSize;
return new FiringState(this.weapon);
}

return this;
}
}

Now we have a weapon system that automatically fires bullets after calling Weapon.startFiring(). It will reload when out of ammo and start firing again until Weapon.stopFiring() is called.

All the logic for each state is encapsulated in the state class, making it easier to manage and extend the system.