Skip to content

How it works

Two programs work on a Dacha project. Your own toolchain builds and serves the game. You launch the editor separately against the same directory. They meet at the project’s configuration.

The workbench editor reads a project's source and assets and writes data.json. The project's own bundler imports data.json and builds the game, which runs in a browser tab. Workbencha desktop editor you launch yourselfreadswritesyour projectsrc/components, systemsdata/data.jsonscenes, actors, systemsdata/assets/textures, audio, fontsimported as a moduleYour gameyour bundler · your dev server · Engine.play()browser tab
Two programs, one directory. The editor reads your code and writes the configuration, and your bundler imports that same configuration.

The two programs never talk to each other. One writes the configuration, the other reads it. That is enough for an edit-and-see loop:

The editor and the running game, side by side. Saving in the editor changes data.json, and the dev server reloads the page.

In the starter project the configuration lives in data/data.json. Here is a scene with a single actor in it, shortened, to show the shape of what the editor writes:

{
"startSceneId": "690e…",
"scenes": [
{
"id": "690e…",
"name": "level",
"actors": [
{
"id": "2190…",
"name": "background",
"children": [],
"components": [
{
"name": "Transform",
"config": {
"offset": { "x": 0, "y": 0 },
"rotation": 0,
"scale": { "x": 1, "y": 1 }
}
},
{
"name": "Sprite",
"config": {
"src": "grass-tile.png",
"width": 256,
"height": 256,
"fit": "repeat",
"sortingLayer": "background"
}
}
]
}
]
}
],
"systems": [
{ "name": "CameraSystem", "options": { "windowNodeId": "root" } },
{
"name": "Renderer",
"options": { "windowNodeId": "root", "backgroundColor": "#1a1a1a" }
}
]
}

There is no logic anywhere in the file.

Notice that components and systems appear as names, not as code. "Transform" and "Renderer" are strings. Your code supplies the classes behind those strings.

You pass the engine every class the configuration can refer to, and it builds the world:

import {
Engine,
Renderer,
CameraSystem,
Transform,
Sprite,
Texture,
} from 'dacha';
import config from '../data/data.json';
const engine = new Engine({
config,
systems: [Renderer, CameraSystem],
components: [Transform, Sprite],
assets: [Texture],
});
void engine.play();

That is the whole shape of an entry point. The starter project lists a few more built-in classes, and collects your own with a glob instead of naming them one by one.

The systems array here is a list of what is available. The systems list in the configuration decides which of them actually run, and in what order. The order of the array in this file does not matter. Configuration covers that split.

config is imported like any other module, so a save in the editor reaches the running game through your dev server.

The editor finds your components and systems by scanning your source for files that match a set of patterns. It imports each match and reads the decorators on the exported classes. They name each class and list the fields the inspector should draw. See how scripts are found. It reads your assets directory the same way.

Once the world is built, the loop takes over. It runs on requestAnimationFrame and calls systems in two separate phases.

One frame runs every system that implements fixedUpdate, zero or more times, then every system that implements update, once. Built-in and project systems sit in the same lists. One frame · requestAnimationFramefixedUpdate0…n times per framePhysicsSystemCharacterController… and your systemsupdateonce per frameInput systemsAnimatorAudioSystemRenderer… and your systems
Each phase runs a plain list of systems, in the order the configuration gives. The renderer is one entry in the update list, not a stage of its own.

update runs once per rendered frame, at whatever rate the browser gives you. fixedUpdate runs on its own clock, independent of the frame rate, so that a game behaves the same on a 60 Hz laptop and a 144 Hz monitor. When you write a system you choose the phase.