Balaur - A Deterministic 2D and 3D Game Engine in Rust

The Balaur editor: node tree, viewport, inspector and the Output dock

I have been building a game engine in Rust together with Sébastien Crozet, the author of Rapier (the physics engine behind Godot Rapier Physics). It’s called Balaur: 2D and 3D, MIT, open source.

Contents:

  1. Why another engine
  2. Hot reload in milliseconds
  3. Determinism, always on
  4. Scenes are TOML
  5. What is in the engine today
  6. The editor is a Balaur project
  7. Ship as one file
  8. Built on the Rust ecosystem
  9. Try it

Why another engine

Three things I kept hitting while fixing bugs in Godot:

  • Determinism. Rollback netcode, lockstep multiplayer, replays, reproducible bug reports. Same inputs have to give the same bits on every machine. Hard to add to an engine later.
  • Fixing things myself. In a big engine a fix waits on review, a merge and the next release. I wanted to fix a bug and ship with it the same day.
  • Shipping small. One executable, no sources, no compiler inside. On the web, Godot 4.7’s export template is 38.8 MB of wasm - 6.9 MB brotli, which is what a host actually serves - before the game’s own data. Balaur’s web template is 17 MB, or 4.5 MB brotli.

So I am making my own. On top: a scene tree of named nodes with scripts, like in Godot. Underneath: every node is an ECS entity and every subsystem is a plugin. Only plugin authors see that layer.

It is 0.1.0: builds from source, no binary release yet. The comparison page says when to pick Godot, Bevy or Fyrox instead.

Hot reload in milliseconds

Save a script while the game runs. New code is live in milliseconds, state survives.

A value changed and saved while the scene runs, then a breakpoint stepped in the Debugger dock

Scripts are Rune: Rust syntax without the types, async/await, no build step.

// scripts/spinner.rn
pub fn init(this) { this.angle = 0.0; }

pub fn update(this, dt) {
    this.angle += dt;
    this.node.set_rotation_euler(0.0, this.angle, 0.0);
}
  • A script that does not compile shows file, line and caret in the editor. The running code stays up.
  • Debugger: breakpoints in the gutter, F5 / F10 / F11 / Shift+F11, frames and locals. The game freezes, the editor stays live.
  • No Rust needed to make a game. Scenes are TOML, game code is Rune. Rust or C is for plugins.

More in Scripting.

Determinism, always on

Simulation runs in fixed_update(dt) at 60 Hz. Every tick hashes the world into a digest.

balaur run my-game --fixed-tick --record session.blr
balaur replay session.blr --verify          # stops at the first tick that disagrees
  • A recording stores input, not state. Minutes of play is kilobytes.
  • CI compares digests across three operating systems. A change that alters a digest without saying why does not merge.
  • Rapier runs with enhanced-determinism, libm does the transcendentals, Rune is forked so powf / powi match everywhere.
  • Network replies land at the start of a tick, in order. A replay covers logins and retries without touching the network.

This is what lockstep and rollback multiplayer need. Details in Determinism.

Scenes are TOML

[[nodes]]
name = "Ball"
position = [0.0, 6.0, 0.0]
script = "scripts/ball.rn"
body3d = "dynamic"
collider3d = { kind = "ball", radius = 0.5 }
shape3d = { kind = "ball", radius = 0.5 }

body3d, collider3d and shape3d are components from the physics and render plugins. One registration = scene key + script call + inspector row. See Scenes and nodes.

What is in the engine today

Physics - 2D and 3D through Rapier, fixed 60 Hz step, collider and centre-of-mass overlays in the editor.

Collider and centre-of-mass overlays over an example scene

Rendering - 2D and 3D on wgpu: sprites, tile maps, particles, meshes. 2D lights and occluders build a light map, post-processing on top.

2D lights and occluders casting shadows across a scene

Animation - clips, tweens, twelve easings, method tracks. 2D bones with skinned polygons, 3D rigs from glTF, look_at and two_bone_ik.

The Animate persona: the rig's bones in the tree, a bone selected in the viewport, and the timeline with its keys

An imported 3D rig and its bones in the editor

Shaders - WESL (WGSL with imports and variants) in material assets, linked at run time.

A material in the inspector with its shader open in the code editor

UI - widgets for HUDs, immediate-mode API for tools.

HUD widgets in the tree and over the safe area, with a button in the inspector

Audio and input - rodio; keyboard, mouse, gamepads, touch.

Networking - HTTP and websockets with compression, delivered once per tick, replayable. Gamend on top, the same backend from Gamend and the stress test: login, REST, realtime rooms, server hooks.

Stores - sign-in, achievements, leaderboards, cloud saves, purchases. Game Center, iCloud and StoreKit today.

Full list on Features. What is missing is on the Roadmap, each item linked to its plan.

The editor is a Balaur project

One binary is the editor, the CLI and every game’s runtime. The editor is itself a Balaur project, so editing an editor script hot reloads the editor.

An example project open in the editor: the World node selected, the viewport, the inspector, and the Output dock showing the boot

Tree, inspector, gizmos, timeline, rig tools, play-in-editor, undo. Docks, tools, commands and inspector sections can come from a project’s own Rune files. More in The editor.

Ship as one file

balaur new my-game
balaur run my-game                  # dev mode, hot reload on
balaur export my-game               # scripts to bytecode, assets into my-game.bpak
balaur play my-game.bpak            # run the pack, no compiler, no watcher
balaur edit my-game                 # open in the editor

export --target <platform> fuses the pack onto a runtime: one executable, no compiler, no sources inside.

Three run modes, same simulation:

Mode GPU Window What it is for
headless no no tests, CI, servers, determinism runs
offscreen yes no screenshots, automation, visual CI
windowed yes yes playing and editing

Windows, macOS and Linux today. iOS, Android and WebAssembly compile in CI; the browser canvas runtime is on the roadmap. See Shipping a game.

Built on the Rust ecosystem

Reuse, don’t rewrite:

Full list on Built on. Every crate is generated into the reference from a booted engine, so the docs cannot drift.

Try it

git clone https://github.com/balaurengine/balaur
cd balaur
cargo run -p balaur_cli -- new my-game
cargo run -p balaur_cli -- run my-game

Needs a stable Rust toolchain. Getting started · FAQ.

Contributions welcome, AI-assisted too, same bar: you understand and stand behind every line, and it comes with tests and docs. Principles say how the project is run. Propose big things in Discussions, bugs go in issues. A star on balaurengine/balaur helps.