---
title: Bevy
url: 'https://wiki.teletypegames.org/platforms/bevy'
markdown: 'https://wiki.teletypegames.org/platforms/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/woodpecker/platforms/bevy/pipeline.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. On teletypegames.org the builder is `bevy-ttg`: the `bevy-builder` image with the crates of every TTG Rust project preloaded into the cargo registry, so the build steps no longer download several hundred crates each (a new dependency is still downloaded as usual). The helper steps (version, upload, publish) run in the `ci-tools` image, which has git, jq, curl and zip preinstalled, so they install nothing. Preview the served pipeline:

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

## Steps

| Step | What it does |
|---|---|
| 1. version | the engine's shared step (since WarpEngine 1.7): the version from `metadata.json`; on a tag a version tag has to match it; outside the repo's default branch and on a `dev-<branch>` tag `dev-<version>-<branch>`, with no version `dev-<commit>`; write `.version` |
| 2. build | in the builder image (`bevy-ttg` on teletypegames.org): 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 | **on the tag only:** web zip, binary zips and `<name>-<version>.metadata.json` via `POST /build/upload`, `X-Update-Secret` header |
| 5. publish | **on the tag only:** `POST /build/publish?platform=bevy` |
| 6. tag | the engine's shared step (since WarpEngine 1.9), **after a push only**: a new version in the source gets its `v<version>` tag, whose pipeline runs the steps marked *on the tag only*; a version tagged already releases nothing; a dev build moves the branch's `dev-<branch>` tag onto the commit, so the newest dev build is published. See *Versions and releases* on the [WarpEngine](https://wiki.teletypegames.org/engines/warp-engine) page |

Raising the version is the release: a push builds and tags, and the tag's pipeline uploads and publishes (WarpEngine 1.9). A push that leaves the version as it is only builds.

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](https://wiki.teletypegames.org/platforms/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: [Platforms](https://wiki.teletypegames.org/platforms.md)
- Next: [Commodore 64](https://wiki.teletypegames.org/platforms/c64.md)
