Skip to content

Game Mode

A game mode is the rules of a match. A level names its class, and opening the level constructs the mode with the options the transition supplied, builds the game state from the mode’s gameStateClass, and runs its beginPlay after every declared actor has begun play. The mode is the only object that adds players and bots, restarts their pawns, moves the match through its phases, and decides when the match is over.

type GameModeClass = new () => GameMode;

A mode is constructed with no arguments, so a constructor sets the mode’s class fields and its own defaults. world, options, and state are assigned before beginPlay runs.

type MatchPhase = "waiting" | "playing" | "over";
PhaseMeaning
waitingThe match is being set up. This is the phase a mode holds when it begins play.
playingThe match is running. GameState.elapsed accumulates only in this phase.
overThe match is decided.

The phase changes only through setPhase.

type EndPlayReason = "destroyed" | "level-closed";
ReasonGiven to
destroyedAn actor and its components ending play because the actor was destroyed, and a component removed by detach.
level-closedEvery controller, actor, component, and game mode ending play because the world is closing.
class GameMode {
readonly world: World;
readonly options: Readonly<Record<string, unknown>>;
readonly state: GameState;
readonly phase: MatchPhase;
gameStateClass: new () => GameState;
playerStateClass: new () => PlayerState;
playerControllerClass: ControllerClass<PlayerController>;
pawnClass: ActorClass<Pawn> | null;
beginPlay(): void;
tick(dt: number): void;
endPlay(reason: EndPlayReason): void;
addPlayer(options?: PlayerOptions): PlayerController;
addBot(
type: ControllerClass<AIController>,
options?: BotOptions,
): AIController;
restart(controller: Controller): Pawn | null;
spawnPoint(controller: Controller): Transform;
pawnDied(controller: Controller, pawn: Pawn): void;
setPhase(phase: MatchPhase): void;
}
MemberSemantics
worldThe world this mode governs.
optionsWhatever world.open was given, or an empty object for the start level.
stateThe world’s game state, the instance built from gameStateClass.
phaseThe match phase. "waiting" when the mode begins play.
gameStateClassDefaults to GameState. Read once, when the world is built.
playerStateClassDefaults to PlayerState. Read on each addPlayer and addBot.
playerControllerClassThe controller addPlayer builds when its options name none.
pawnClassThe pawn restart spawns. null for a mode whose controllers possess nothing.
beginPlayRuns after every declared actor has begun play. Where a mode adds its players and sets its phase.
tickRuns once per frame, after every actor has ticked and after collision has been reported, so the mode decides the match from a settled world.
endPlayRuns when the world closes, after every actor has ended play.
addPlayerBuilds the player state, the player controller, and the pawn, and possesses. Assigns the next free index when the options name none.
addBotThe same for an AI controller. A bot’s player state carries the next free index.
restartDestroys the controller’s current pawn, spawns pawnClass at spawnPoint(controller), and possesses it. Returns null when pawnClass is null.
spawnPointWhere restart places a pawn. The base implementation returns the center of the design field.
pawnDiedRuns when a possessed pawn is destroyed, after the pawn’s endPlay. The base implementation does nothing.
setPhaseSets the phase, writes it onto the game state, and emits match:phase. Setting the phase it already holds emits nothing.

The base class’s beginPlay, tick, endPlay, and pawnDied do nothing, so a mode overrides only what it needs.

interface PlayerOptions {
index?: number;
name?: string;
controller?: ControllerClass<PlayerController>;
pawn?: ActorClass<Pawn> | null;
}
FieldMeaning
indexThe index the player state carries. Absent, addPlayer assigns the next free index.
nameThe name written onto the player state.
controllerThe controller class to build. Absent, addPlayer builds playerControllerClass.
pawnThe pawn class to spawn and possess, in place of pawnClass. null adds a controller that possesses nothing.
interface BotOptions {
name?: string;
pawn?: ActorClass<Pawn> | null;
}
FieldMeaning
nameThe name written onto the bot’s player state.
pawnThe pawn class to spawn and possess, in place of pawnClass. null adds a controller that possesses nothing.

addBot takes the controller class as its first argument, so BotOptions names no controller. A bot’s player state carries the next free index and sits in state.players alongside a player’s.

class GameState {
readonly world: World;
readonly players: readonly PlayerState[];
phase: MatchPhase;
elapsed: number;
}
MemberSemantics
worldThe world this state belongs to.
playersEvery player state, in index order, a bot’s alongside a player’s.
phaseThe phase setPhase last wrote.
elapsedSeconds accumulated while the phase is "playing".

elapsed is the match clock rather than the world clock. A game carries its own match figures by subclassing GameState and naming that subclass in gameStateClass.

class PlayerState {
readonly index: number;
readonly controller: Controller;
name: string;
score: number;
}
MemberSemantics
indexThe participant’s index, assigned when the player or bot is added.
controllerThe controller this state belongs to.
nameThe participant’s display name.
scoreThe participant’s score.

A game carries its own per-player figures by subclassing PlayerState and naming that subclass in playerStateClass. The state is the durable half of a participant: it survives every respawn its controller performs.

ConditionResult
beginPlay throws while the start level is openingengine.initialize rejects with the cause
tick throws under runThe error propagates to the host, and the loop schedules the next frame
tick throws under advanceadvance rejects with the cause, and the remaining frames do not run

A throw under run leaves the loop alive, so one bad frame leaves the game running. A throw under advance stops at once, because a caller stepping an exact number of frames needs the failure rather than the frames after it.

GameMode, GameState, and PlayerState are exported as classes from @clockwyrks/structured-2d. GameModeClass, MatchPhase, EndPlayReason, PlayerOptions, and BotOptions are exported as types.