Core Concepts

State Machine

Pushdown automaton pattern for managing complex entity behaviors.

MavonEngine uses a pushdown automaton pattern for entity state management. States are stacked: a new state can be pushed on top (suspending the previous one) and later popped to resume it. This allows complex behaviors like interruptions and resumable actions.

Reference: The pattern is taken directly from Game Programming Patterns — State chapter.

EntityState

All states extend the abstract EntityState class.

import { EntityState, Actor } from '@mavonengine/core'

class IdleState extends EntityState {
  enter(): void {
    // Called when this state becomes the active state
  }

  suspend(): void {
    // Called when a new state is pushed on top of this one
  }

  leave(): void {
    // Called when this state is permanently removed from the stack
  }

  update(delta: number): EntityState | void {
    // Return a new state to transition, or void to stay in this state
    if (this.entity./* some condition */) {
      return new WalkState(this.entity)
    }
  }
}

Lifecycle methods

MethodWhen it's called
enter()State becomes the top of the stack
suspend()Another state is pushed on top
leave()State is popped off the stack permanently
update(delta)Every tick, only called on the top state

Transitioning

Return a new EntityState from update() to transition. The current state's leave() is called and the new state's enter() is called.

update(delta: number): EntityState | void {
  if (this.shouldAttack()) {
    return new AttackState(this.entity)
  }
}

Accessing the entity

Every state has a reference to its owning Actor via this.entity.

class WalkState extends EntityState {
  update(delta: number): EntityState | void {
    // Move the entity
    this.entity.position.x += delta * speed
  }
}

Usage with Actor

States are managed automatically by Actor.update(). Initialize the first state in your actor's constructor:

import { Actor, EntityState } from '@mavonengine/core'

class MyActor extends Actor {
  constructor() {
    super()
    this.state = [new IdleState(this)]
  }
}

The stack is available at actor.state (an array where the last element is the active state).

Example: idle → walk → attack

class IdleState extends EntityState {
  update(delta: number) {
    if (input.keysPressed.has('KeyW')) return new WalkState(this.entity)
  }
}

class WalkState extends EntityState {
  update(delta: number) {
    this.entity.position.z -= delta * 5
    if (input.keysPressed.has('Space')) return new AttackState(this.entity)
    if (!input.keysPressed.has('KeyW')) return new IdleState(this.entity)
  }
}

class AttackState extends EntityState {
  private timer = 0

  update(delta: number) {
    this.timer += delta
    if (this.timer > 0.5) return new IdleState(this.entity)
  }
}