FERRETGame engine over Babylon.js
Ferret

Getting started

Set up#

Ferret is a pnpm workspace (packages/*, examples/*, tools/*). Babylon.js, Havok, recast-navigation and hls.js come in as development dependencies; esbuild bundles everything, so there is no other build step.

pnpm install
pnpm typecheck      # tsc --noEmit over every package
pnpm test           # vitest: unit tests of every package
pnpm build          # tools/build.mjs: dist/web, dist/beroun, dist/lib
pnpm serve          # tools/serve.mjs [port] [dir]: a static server for dist/web

pnpm test:e2e runs the Playwright tests against the built samples. pnpm docs builds this site into dist/docs.

What a build produces#

OutputWhat it is
dist/web/<sample>/each sample as a web page: open index.html through pnpm serve
dist/beroun/<sample>/the same sample as an OS Beroun app: manifest.json, icon.svg, the page and the bundle
dist/lib/ferret.jsthe engine as one script that exposes Ferret and BABYLON globals
dist/lib/ferret/the release: engine, Babylon, loaders and the WASM files (Havok, recast), for apps that load the engine from OS Beroun's shared library

The samples show what each part is for: greybox (a character in a grey-box level), npc (NavMesh walkers, doors, levers), showcase (media, UI, transformations) and physics (the realistic-physics capabilities).

A first game#

A game is a canvas, createGame, a level and a start call. This is the shape of examples/greybox:

import { createGame } from '@ferret/game';
import { PlayerController, spawnCharacter } from '@ferret/character';

const game = createGame({
  canvas: document.getElementById('c') as HTMLCanvasElement,
  storageKey: 'my-game',                       // one key per game in a browser
  input: { actions, bindings },                // see "Input and UI"
});
const { world, platform, scene } = game;

// ...build the level's meshes into `scene`; static meshes with checkCollisions are solid

const { entity } = spawnCharacter(world, 'hero', new BABYLON.Vector3(0, 1, 0));
const player = entity.add(PlayerController, { camera: { view: 'first' } });
game.display.trackCamera(player.rig.camera);   // the field of view follows the screen's aspect

world.events.on('character:jump', () => platform.log('jump'));
game.start();                                  // render loop + fixed-step simulation

createGame gives you the platform, the Babylon engine and scene, the World, the Display, the InputService and the PhysicsService. Anything else is a service you add to the world.

Where to go next#

Build this documentation#

pnpm docs                         # dist/docs
pnpm serve 8080 dist/docs         # then open http://localhost:8080

The guides are the Markdown files in docs/; the API pages are read from the TypeScript sources. See Writing the docs.