b3d-controller

The casual way to read the standard controller. Drop a <tosi-b3d-controller> into a scene and it self-wires the whole unified input stack — keyboard/mouse, the on-screen glass gamepad, a hardware gamepad, and (in a headset) the XR controllers — then hands you a merged ControlInput every frame via drive. No inputFocus + gameController boilerplate, no bespoke key listeners: just read input.forward / input.turn / input.shoot / … and drive whatever you like.

It's a b3d-controllable with no body of its own, so it also respects scene input focus: on a page with several live demos, only the scene you're hovering/interacting with receives input (one gamepad no longer drives them all).

Demo

Steer the launcher with A/D (or the left stick), and pull the right trigger (or F / the glass B button) to fire at the cube field. Same controls on keyboard, touch, and in VR — because it's the standard controller, not a demo hack.

import { b3d, b3dController, b3dSkybox } from 'tosijs-3d'
import { demoSun, orbitCam, patternGround } from 'demo-utils'

// A rover we drive around: left stick / WASD → move + turn. The controller merges keyboard,
// the on-screen glass pad, and any hardware/XR pad, and hands `drive` the result each frame.
let rover // set in sceneCreated
const controller = b3dController({
  mapping: 'biped',
  drive(input, dt) {
    if (!rover) return
    rover.rotation.y += input.turn * dt * 2.4 // turn (A/D · left stick X)
    const step = input.forward * dt * 7 // drive (W/S · left stick Y)
    rover.position.x += Math.sin(rover.rotation.y) * step
    rover.position.z += Math.cos(rover.rotation.y) * step
  },
})

const scene = b3d(
  {
    gamepad: 'left_stick', // glass gamepad shows only what this demo uses
    sceneCreated(el, BABYLON) {
      orbitCam(el, { radius: 16, beta: Math.PI / 3.4, target: [0, 0.5, 0] })
      rover = BABYLON.MeshBuilder.CreateBox('rover', { width: 1.2, height: 0.7, depth: 1.9 }, el.scene)
      rover.position.y = 0.5
      const mat = new BABYLON.StandardMaterial('rover-mat', el.scene)
      mat.diffuseColor = new BABYLON.Color3(0.85, 0.45, 0.2)
      rover.material = mat
      el.register?.({ meshes: [rover] }) // cast a shadow on the ground
    },
  },
  demoSun(),
  b3dSkybox({ timeOfDay: 10 }),
  patternGround({ size: 40 }),
  controller,
)
preview.append(scene)
tosi-b3d { width: 100%; height: 100%; }

Attributes

Attribute Default Description
mapping 'biped' Which control scheme maps the gamepad to ControlInput: 'biped', 'car', or 'aircraft'
drive (input: ControlInput, dt: number) => void, called each frame with the merged input (set in code / via the creator). Named drive, not onInputon* props become DOM event listeners

Put it inside a <tosi-b3d-input-focus> only if you want that manager to drive it instead — on its own it wires input itself.