Input · class

ActionMap

Explained in Moving things.

class ActionMap
import { ActionMap } from '@driftengine/core';

Constructor

new

constructor(input: InputSource, definitions: Readonly<Record<string, ActionDefinition>>)
ParameterTypeDescription
inputInputSource
definitionsReadonly<Record<string, ActionDefinition>>

Accessors

NameTypeDescription
canRumblegetbooleanWhether the pad these actions read has motors this browser can drive.
More

Pad 0, which is the pad every other member of this class already reads. An action map is one player's controls, so "the pad the actions come from" is the device a rumble belongs on; a consumer running local multiplayer reaches InputSource.pad(n).rumble directly.

Grey the control out when this is false. It is re-read rather than cached, because a pad unplugged and plugged back in is a different device behind the same slot.

Methods

down

down(action: string): boolean

Whether anything bound to this action is held.

ParameterTypeDescription
actionstring

pressed

pressed(action: string): boolean

Whether anything bound to it went down since the last poll. Readable by anything.

ParameterTypeDescription
actionstring

consumePress

consumePress(action: string): boolean

The same edge, claimed: true for exactly one caller.

ParameterTypeDescription
actionstring
More

Claims through the device rather than keeping its own set, so a claim made here is also gone from input.keyPressed and from the pad's own pressed. Two claim registers over one physical press is how a press gets acted on twice.

vector

vector(action: string, out: { x: number; y: number; }): void

Fill out with an analog action's direction.

ParameterTypeDescription
actionstring
out{ x: number; y: number; }
More

The larger of stick and keys, never the sum. A player holding a key while pushing the stick the same way is asking to go that way, once; adding the contributions would send them at twice the speed, which is the bug a naive merge ships with. What it costs: the two cannot be combined to exceed the rim, which is the intent. What would make it wrong: an action where two devices genuinely should add, and nothing has asked for one.

This is the reader for a direction. If the two axes are two separate controls — throttle and steering, pitch and roll — read them one at a time with axis, which does not normalise them against each other. See its note for what the joint normalisation cost one consumer.

axis

axis(action: string, axis: 'x' | 'y'): number

One axis of an analog action, on its own, not normalised against the other.

ParameterTypeDescription
actionstring
axis'x' | 'y'
More

vector is right for a direction and wrong for two controls, and the difference is not cosmetic. On foot the two axes are one direction, so a keyboard diagonal must be shortened to the rim or the player walks faster askew than straight ahead — that is what vector is for and it should stay the default. At a wheel the same two axes are throttle and steering, which are two separate controls that happen to share an action: normalising them together means a driver holding forward and left gets 0.707 of each and can never reach full lock while accelerating.

A consumer lost two weeks to this. The report was "the car does not turn and it is slow", the whole vehicle model was rewritten looking for it, and it was not in the vehicle model — it was Math.hypot three call frames away, doing exactly what it was written to do. They shipped four duplicate digital actions bound to the same keys to get around it.

The pad is unchanged either way: a stick already gives its two axes independently, so a caller driving with one reads the same numbers from both methods. The keyboard is where they part.

rumble

rumble(durationMs: number, strong: number, weak: number): boolean

Rumble that pad for durationMs, at two magnitudes in [0, 1]. Answers whether it was taken.

ParameterTypeDescription
durationMsnumber
strongnumber
weaknumber
More

false where there is no pad, no actuator, or nothing to play. Never throws.

stopRumble

stopRumble(): boolean

Stop whatever that pad is playing. Answers whether the platform took it.

bindingsFor

bindingsFor(action: string): readonly Binding[]

What currently satisfies an action.

ParameterTypeDescription
actionstring

rebind

rebind(action: string, binding: Binding): readonly string[]

Give an action a binding, and say which actions lost it.

ParameterTypeDescription
actionstring
bindingBinding
More

Reports rather than decides. A binding already serving another action is the interesting case in every rebinding screen there has ever been, and what to do about it is a product decision: steal it silently, warn, or refuse. A consumer that wants uniqueness enforced reads the return and rebinds back.

resetToDefaults

resetToDefaults(action?: string): void

Put one action, or every action, back to what the game shipped with.

ParameterTypeDescription
action?string

save

save(store: KeyValueStore, key: string): void

Write the bindings a player has actually changed, and nothing else.

ParameterTypeDescription
storeKeyValueStore
keystring
More

A diff rather than a snapshot, and this is the load-bearing decision. A snapshot freezes the map at the version that saved it: a game that later adds an action, or changes a default nobody had rebound, finds every returning player still on the old set with no way to tell a deliberate choice from a stale record. A diff means an untouched action always follows the current default and only real choices survive an update.

Through KeyValueStore because the engine calls no platform API a consumer might want to supply differently — the rule that put saves behind this seam.

load

load(store: KeyValueStore, key: string): void

Apply a stored record over the defaults.

ParameterTypeDescription
storeKeyValueStore
keystring
More

An unreadable record is the defaults, reported once. A player whose stored bindings cannot be parsed should get a working game rather than a broken one, and should not have that decided silently — the same shape as every other refusal in this engine.