Skip to content

Control Schemes

Control schemes provide strongly-typed input bindings as ECS components. They are useful when you want compile-time safety for action names and binding types.

import { defineInputScheme, key, dualAxisWASD } from '@shell/input-manager';
const FootControls = defineInputScheme('foot', {
attack: key('KeyF'),
move: dualAxisWASD(),
});
const Player = Hero.with(FootControls);
// Override per-entity
const Player2 = Hero.with(FootControls, {
data: new ControlScheme({
attack: key('KeyM'),
move: dualAxisArrowKeys(),
}),
});
world.addPlugin(
InputManagerPlugin.config({
schemes: [FootControls],
}),
);

Registering a scheme adds a dedicated evaluation system that runs after InputManagerSystem and evaluates the scheme’s bindings each frame.

ControlScheme provides type-safe access based on the binding kind:

const player = world.queryFirst(FootControls);
const controls = player.getComponent(FootControls)!.actions;
if (controls.justPressed('attack')) {
// 'attack' is known to be a button action at compile time
}
const dir = controls.axis2d('move');
// 'move' is known to be a dual-axis action at compile time
MethodAvailable ForDescription
pressed(action)Button actionstrue while held
justPressed(action)Button actionstrue for one frame on press
justReleased(action)Button actionstrue for one frame on release
axis(action)Axis actions1D axis value
axis2d(action)Dual-axis actions2D axis value
value(action)Any actionNumeric value
export type InputSchemeComponent<
B extends Record<string, InputBinding>,
N extends string,
> = ComponentBuilder<
Instance<ControlScheme<B>>,
{ actions: { type: Instance<ControlScheme<B>> } },
N
>;
export function defineInputScheme<B extends Record<string, InputBinding>, N extends string>(
name: N,
bindings: B,
): InputSchemeComponent<B, N>;