---
title: Bevy
url: 'http://wiki.teletypegames.org/development/bevy'
markdown: 'http://wiki.teletypegames.org/development/bevy.md'
lang: en
date: '2026-07-31'
description: 'Bevy is an open source, data-driven game engine written in Rust. State lives in components, and plain Rust functions ("systems") run over them in parallel — this is ECS. There is no visual editor: a project is a Cargo crate, which fits a Makefile + CI workflow well. WebAssembly is first-class, so t…'
taxonomy:
  tag:
    - howto
---

# Bevy

Bevy is an open source, data-driven game engine written in Rust. State lives in components, and plain Rust functions ("systems") run over them in parallel — this is ECS. There is no visual editor: a project is a Cargo crate, which fits a Makefile + CI workflow well. WebAssembly is first-class, so the same code runs natively and in the browser.

**Website:** https://bevyengine.org

# Tutorials

- [*The Bevy Book — Getting Started*](https://bevyengine.org/learn/quick-start/introduction/)
- [*Bevy Examples*](https://bevyengine.org/examples/)
- [*Unofficial Bevy Cheat Book*](https://bevy-cheatbook.github.io/)
- [*Bevy ECS explained*](https://bevyengine.org/learn/quick-start/getting-started/ecs/)
- [*Deploying Bevy to the web (WASM)*](https://bevy-cheatbook.github.io/platforms/wasm.html)

---

# Makefile

Native builds are plain `cargo build`. Web builds target `wasm32-unknown-unknown` and run `wasm-bindgen` for the JS glue. The `index.html` shell is downloaded at export time, so every game uses the same loader.

Templates are in [bevy-tools](https://git.teletypegames.org/build/bevy-tools): copy `example-makefile.make` to `Makefile` and set `PROJECT` to the crate name from `Cargo.toml`.

**Version pinning matters.** The `wasm-bindgen` CLI and the `wasm-bindgen` crate must be the same version:

```toml
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "=0.2.100"
```

Keep `WASM_BINDGEN_VERSION` in the Makefile in sync. The CI image (`git.teletypegames.org/build/bevy-builder`) ships the matching CLI — rebuild it with a new `--build-arg WASM_BINDGEN_VERSION` when you bump.

## Variables

| Variable | Description | Default |
|---|---|---|
| `PROJECT` | Project / crate name | `bevydemo` |
| `WASM_BINDGEN_VERSION` | CLI version, must match Cargo.toml | `0.2.100` |
| `BIN_DIR` | Native binary | `bin` |
| `DIST_DIR` | Exported artifacts | `dist` |
| `VERSION_FILE` | Version file for CI | `.version` |
| `INDEX_HTML_URL` | HTML shell | bevy-tools `web/index.html` |

## Targets

```sh
make build                   # cargo build --release -> bin/$(PROJECT)
make run                     # cargo run
make wasm                    # release wasm + wasm-bindgen -> dist/game.js, dist/game_bg.wasm
make export VERSION=1.0.0     # wasm + index.html zipped as bevydemo-1.0.0.html.zip
make watch                   # rebuild on change under src/ (needs fswatch)
make clean                   # remove bin/, dist/ and target/
```

`make wasm` needs the target and the CLI:

```sh
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.100
```

## VS Code

Copy `example-tasks.json` to `.vscode/tasks.json`. Tasks: **Run** (default, `Cmd+Shift+B`), **Build & Run**, **Build WASM**, **Make build**.

## Generated artifacts

| File | Description |
|---|---|
| `bin/bevydemo` | Native release binary |
| `dist/game.js`, `dist/game_bg.wasm` | wasm-bindgen output (packaged, then removed) |
| `bevydemo-<version>.html.zip` | Web build |
| `bevydemo-<version>-win-x64.zip` | Windows build (mingw cross-compile) |
| `bevydemo-<version>-linux-x64.zip` | Linux build (glibc) |

## Requirements

Local only: `make`, `git`, `jq`, `zip`, `curl`, the Rust toolchain with the `wasm32-unknown-unknown` target, a matching `wasm-bindgen` CLI, and `fswatch` for watch mode. CI images bring their own tools.

---

# Pipeline

The pipeline is not in the game repo. Woodpecker asks the update server, and WarpEngine serves the whole config from `/build/config` (template `lib/warp_engine/ci_templates/bevy.yaml.erb`).

The repo needs three things:

```yaml
# .woodpecker.yaml
platform: bevy
name: bevydemo   # only when the software name differs from the repo name
```

- `metadata.json`
- the `application_token` Woodpecker secret: a WarpEngine ApplicationToken with `update` + `upload` scopes

Builder images come from the update server's `ci_platforms` config. Preview the served pipeline:

```sh
curl "https://teletypegames.org/build/config?platform=bevy"
```

## Steps

| Step | What it does |
|---|---|
| 1. version | read the version from `metadata.json`, else the git short hash; outside `main`/`master` prefix it as `dev-<version>-<branch>` |
| 2. build | in the `bevy-builder` image: wasm build + `wasm-bindgen`, packed as the versioned zip |
| 3. binaries | native linux-x64 (glibc) and win-x64 via mingw (`x86_64-pc-windows-gnu`) |
| 4. upload | web zip, binary zips and `<name>-<version>.metadata.json` via `POST /build/upload`, `X-Update-Secret` header |
| 5. publish | `POST /build/publish?platform=bevy` |

The builder image is a debian Rust toolchain with the `wasm32-unknown-unknown` and `x86_64-pc-windows-gnu` targets, mingw-w64 and the `wasm-bindgen` CLI preinstalled, built from `builder.Dockerfile` in bevy-tools.

Binary details:

- An `assets/` directory is packaged next to the binary — Bevy loads it at runtime, it is not embedded.
- No mac build: darwin cross-compilation needs osxcross, the same planned toolchain as the ebitengine mac targets.
- The API registers the `<name>-<version>-<slug>.zip` files as `win_x64` / `linux_x64` release assets.
- A cold Bevy release build compiles several hundred crates, so expect a much longer build step than the other engines.

## Demo project

[bevy-demo](https://git.teletypegames.org/demos/bevy-demo) — animated "TELETYPE GAMES" wave text on Bevy 0.16, the same demo as [ebitengine-demo](http://wiki.teletypegames.org/development/ebitengine).

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Development](http://wiki.teletypegames.org/development.md)
- Next: [Commodore 64](http://wiki.teletypegames.org/development/c64.md)
