Thoughts on Building a Game with XState
After hearing about XState's good reputation for years, I used it to build the prototype for my game. These are my thoughts.
January 4, 2025
alive
by default, loses hp
when taking damage
, and enters dead
state if hp drops to zero.const mobStateMachine = setup({
types: {
context: {} as { hp: number; maxHp: number },
input: {} as { maxHp: number },
events: {} as { type: "damage", damage: number },
},
}).createMachine({
id: "mobState",
initial: "alive",
context: ({ input }) => ({
hp: input.maxHp,
maxHp: input.maxHp,
}),
states: {
"alive": {
on: {
"damage": {
target: "hit",
actions: assign({
hp: ({ context, event }) =>
Math.max(context.hp - event.damage, 0),
}),
},
}
},
"hit": {
always: [
{
target: "alive",
guard: ({ context }) => context.hp > 0,
},
{
target: "dead",
guard: ({ context }) => context.hp === 0,
},
],
},
"dead": {
type: "final",
},
},
});
createMachine
API function similarly to blueprints—describing how the state machine should work but not performing the work itself. The actual state machines that execute in the program are called actors, which are instances created by the state machine factory.const mobActor = createActor(mobStateMachine, {
input: { maxHp: 5 }
});
mobActor.start();
console.log("mob state:", mobActor.getSnapshot().value); // "mob state: alive"
console.log("mob hp:", mobActor.getSnapshot().context.hp); // "mob hp: 5"
mobActor.send({ type: "damage", damage: 3 });
console.log("mob state:", mobActor.getSnapshot().value); // "mob state: alive"
console.log("mob hp:", mobActor.getSnapshot().context.hp); // "mob hp: 2"
mobActor.send({ type: "damage", damage: 3 });
console.log("mob state:", mobActor.getSnapshot().value); // "mob state: dead"
console.log("mob hp:", mobActor.getSnapshot().context.hp); // "mob hp: 0"
always
or after
, on the other hand, while demand more awareness, make it simple and natural for describing conversion states.send
API, which listens to nothing but the intended events for the current state. The encapsulation of logics makes the state machine robust, which is perhaps my favorite part of XState.hp
property would be necessary.spawn
API for vertical hierarchy, or the system
API for more general communication among actors. This feature further improves encapsulation by eliminating unnecessary event calls from the user side. It took me a while to comprehend how the actors are supposed to communicate, but the design assures safety and structure.