flame_state_machine

A lightweight and flexible finite state machine package for the Flame game engine, written in Dart.

Manage complex stateful behaviors for your Flame Components with ease, enabling clean and maintainable game logic.

Features

  • Generic state machine designed to work seamlessly with Flame Components
  • Supports prioritized state transitions with custom guard conditions
  • Easy registration of reversible transitions
  • Lifecycle callbacks for entering, exiting, rendering and updating states
  • AnyState support for transitions valid from any current state

Usage

1. Create states

Extend the State<T> class to define your custom states:

class IdleState extends State<Enemy> {
  @override
  void onEnter(Enemy enemy, [State<Enemy>? from]) {
    print('Enemy entered Idle state');
  }

  @override
  void onExit(Enemy enemy, [State<Enemy>? to]) {
    print('Enemy exited Idle state');
  }

  @override
  void onRender(Enemy owner, Canvas canvas) {
    // optionally render idle-specific visuals here (useful for debugging)
  }

  @override
  void onUpdate(double dt, Enemy enemy) {
    // handle idle behavior
  }
}

2. Setup state machine in your Flame component

Mix in HasStates and provide a StateMachine instance:

class Enemy extends PositionComponent with HasStates<Enemy> {
  @override
  late final StateMachine<Enemy> stateMachine;

  Enemy() {
    final idleState = IdleState();
    final runningState = RunningState();
    final deathState = DeathState();

    stateMachine = StateMachine<Enemy>(
      owner: this,
      initialState: idleState,
    );

    stateMachine.register(
      to: deathState,
      guard: (enemy) => !enemy.isAlive,
      priority: 100,
    );

    stateMachine.register(
      from: idleState,
      to: runningState,
      guard: (enemy) => enemy.isMoving,
      reverse: true,
    );
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas);
    // stateMachine.render(canvas); // called automatically by HasStates mixin
  }

  @override
  void update(double dt) {
    super.update(dt);
    // stateMachine.update(dt);  // called automatically by HasStates mixin
  }
}

3. Register transitions with guards

Use register() to define valid state changes and their conditions:

stateMachine.register(
  priority: 1, // transitions with higher priority values will be checked first
  from: IdleState(), // if not provided the transition can occur from any state
  to: RunningState(),
  guard: (enemy) => enemy.isMoving,
  reverse: true, // automatically registers reverse transition
  reversePriority: 1, // priority for the reverse transition
  reverseGuard: (enemy) => !enemy.isMoving, // guard for the reverse transition (Constructed automatically if not provided)
);

Or use addTransition() to add a StateTransition Object manually:

stateMachine.addTransition(
  StateTransition(
    from: IdleState(),
    to: RunningState(),
    guard: (enemy) => enemy.isMoving,
  )
);

Note

If you use addTransition(), you must define the reverse transition yourself, like so:

stateMachine.addTransition(
  StateTransition(
    from: RunningState(),
    to: IdleState(),
    guard: (enemy) => !enemy.isMoving,
  )
);

API

  • StateMachine<T> — Core FSM logic
  • State<T> — Base class for your states (override onEnter, onExit, onRender, onUpdate)
  • StateTransition<T> — Defines transitions between states with guards and priorities
  • HasStates<T extends Component> — Mixin for Flame components to attach a state machine

Contributing

Contributions and suggestions are welcome! Feel free to open issues or submit pull requests.