Skip to content

Attributes and state machines ​

Two small, separate tools that live together in core:

  • An Attribute<T> is a value with modifiers, an optional clamp and listeners. Use it for stats such as speed, armour or max health.
  • A StateMachine<TOwner> holds named states and only moves along the edges you permit. Use it for doors, AI moods, menus and animation states.

They do not know about each other. An attribute never starts a state machine, and a state machine owns no attributes.

ts
import { Attribute, AttributeModifier, StateMachine, StateMachineState } from "gamekit";

// An attribute is a value with ordered modifiers and an optional clamp.
const speed: Attribute<float> = new Attribute<float>(100.0, (v: float): float => v < 0.0 ? 0.0 : v);
speed.addModifier(new AttributeModifier<float>("boots", 10, (v: float): float => v + 20.0));
speed.addModifier(new AttributeModifier<float>("slow", 20, (v: float): float => v * 0.5));
console.log("speed now " + speed.value);    // (100 + 20) * 0.5 = 60
speed.removeModifier("slow");

// A derived attribute recalculates from others.
const strength: Attribute<int> = new Attribute<int>(8);
const carry: Attribute<int> = new Attribute<int>(0);
carry.derive([strength], (values: any[]): int => (values[0] as int) * 5);
strength.setBase(10);
console.log("carry " + carry.value);

// A state machine only takes the edges you permit.
class Door {
    log: string[] = [];
    locked: boolean = true;
}
const door: Door = new Door();
const machine: StateMachine<Door> = new StateMachine<Door>(door);
machine.addState(new StateMachineState<Door>("closed", {
    enter: (owner: Door, context: any): void => { owner.log.push("closed"); },
}));
machine.addState(new StateMachineState<Door>("open", {
    enter: (owner: Door, context: any): void => { owner.log.push("open"); },
}));
machine.permit("closed", "open", (owner: Door, context: any): boolean => !owner.locked);
machine.permit("open", "closed");
machine.start("closed");
const whileLocked: boolean = machine.transition("open");   // the guard says no
door.locked = false;
const unlocked: boolean = machine.transition("open");
console.log(door.log.join(", ") + "; locked: " + whileLocked + ", unlocked: " + unlocked);
text
speed now 60
carry 50
closed, open; locked: false, unlocked: true

Attributes ​

CallWhat it does
new Attribute<T>(base, clamp?)A value with a base. The clamp sees the fully modified value.
addModifier(new AttributeModifier(id, order, apply))Adds a modifier. Lower order runs first; equal orders keep the order you added them.
removeModifier(id)Removes one and returns whether it was there.
setBase(value)Changes the base.
derive(dependencies, calculate)Computes the base from other attributes. A cycle is rejected before it can take effect.
clearDerived()Goes back to the stored base.
subscribe(listener)Calls listener(next, previous) on change. Returns a subscription to dispose.
value, inspect()The resolved value; a debug snapshot with counts and recalculations.

Recalculation is synchronous and has no ticker or global registry behind it.

State machines ​

CallWhat it does
new StateMachine<TOwner>(owner, clock?, cap?)A machine for an owner. The clock is optional; cap bounds queued transition work (default 64).
addState(new StateMachineState(name, { enter, exit, update }))Adds a state. Each callback is optional and receives the owner.
permit(from, to, guard?)Allows an edge. The guard (owner, context) => boolean can refuse it.
start(name, context?)Enters the first state.
transition(to, context?)Moves along a permitted edge. Returns false when a guard refuses. Throws when the edge was never permitted.
update(delta)Calls the current state's update(owner, delta). Nothing updates on its own.
transitionAfter(delay, to, context?)Schedules a transition. Needs a clock with schedule, such as App.ticker or a test ClockScheduler.
dispose()Cancels timed work and exits the current state.

A transition requested from inside a state callback is queued and runs after the current one, so callbacks never recurse. inspect() reports queued and scheduled work for debugging.

Reference: Attribute, AttributeModifier, StateMachine, StateMachineState.