FERRETGame engine over Babylon.js
Ferret

Input and UI

Actions#

A game declares actions, not keys. An action is a button or a two-axis axis2. Bindings map each input kind to the keys, pad buttons or touch controls that drive the action:

const actions = {
  move:  { kind: 'axis2' },
  look:  { kind: 'axis2' },
  jump:  { kind: 'button' },
  use:   { kind: 'button' },
  menu:  { kind: 'button' },
};
const bindings = {
  keyboard: { move: [{ axisKeys: ['KeyA', 'KeyD', 'KeyS', 'KeyW'] }], jump: [{ key: 'Space' }], use: [{ key: 'KeyE', label: 'E' }] },
  console:  { move: [{ axisKeys: ['ArrowLeft', 'ArrowRight', 'ArrowDown', 'ArrowUp'] }], use: [{ key: 'Enter', label: 'A' }], menu: [{ key: 'Escape', label: 'B' }] },
};

InputService samples them every fixed step: down(a), pressed(a), released(a), axis(a), lookDelta(). The active InputKind (console, keyboard, gamepad, touch) follows whatever the player used last, and prompt(a) returns the label of the current binding ("A", "E", "Tap") for UI text.

On OS Beroun the cursor, A and B reach the page as keys, so the console kind is an ordinary key map. Pointer Lock is only for a mouse; it is never required.

For tests and scripted scenes, inject(action, value) forces an action, and enabled = false makes every action read as released while a menu is open or a cutscene plays.

Touch#

TouchControls generates the on-screen controls from the game's touch bindings: a stick on the left, look on the right, buttons on the edge. createGame adds it on phones and tablets (touchControls: false to opt out).

The UI overlay#

UIService is an HTML/CSS layer above the canvas. Everything it shows is data first, so the logic runs and is tested without a DOM.

PieceUse
HUDsetHud('hp', 'HP 80') / setHud('hp', null): named lines
PromptsetPrompt('[E] Open door'): the use-prompt for the focused Interactable
Toaststoast('Picked up: brass key'): ToastQueue, at most three at once
Loadingthe loading screen, driven by the level service's events
MenuopenMenu(def): a MenuModel steered by up / down / accept / back, so the console's D-pad, A and B, a mouse and touch all work; the game pauses while it is open

UIOptions.scale returns the UI scale (from uiScaleFor(screen)), and the CSS is written in em so the overlay stays readable from 640×480 up to 4K. pauseMenu builds the menu that the menu action opens.