"Cualquier juego que no está en la web, en 2026, está dejando dinero en la mesa."
— Anónimo, post de blog sobre distribución indie, 2024.
Bevy se publicita como "write once, run everywhere": tu código Rust se compila a nativo (Linux/Windows/Mac) y a WebAssembly (web). En la práctica, hay matices. La buena noticia: para el 80% de los juegos 2D, la promesa se cumple. La mala: el 20% restante (compute shaders, algunos efectos de post-process) requieren trabajo extra.
Este anexo te lleva de "tengo un binario nativo" a "tengo un juego en itch.io". Cubre:
wasm32-unknown-unknown.index.html mínimo.# cap-E — sección E.2: instalar target wasm.
rustup target add wasm32-unknown-unknown
Para desarrollo iterativo, instalá también wasm-server-runner (corre un servidor local que sirve tu build):
cargo install wasm-server-runner
Y para optimizar el binario final, wasm-opt (parte de binaryen):
# macOS
brew install binaryen
# Linux (varios métodos; ver binaryen docs)
# Windows: descargar binarios.
Cargo.toml para web# cap-E — sección E.3: Cargo.toml.
[package]
name = "mi_juego_2d"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = { version = "0.18", features = ["2d"] }
# El resto de tus deps.
Caveat: la feature dynamic_linking no funciona para WASM. Si la activás, el build falla. Asegurate de no tenerla en el Cargo.toml cuando compiles para web.
# cap-E — sección E.4: build para web.
cargo build --release --target wasm32-unknown-unknown
# Genera: target/wasm32-unknown-unknown/release/mi_juego_2d.wasm
El binario pesa 30-80 MB sin optimizar. Para reducir:
# cap-E — sección E.4: optimizar.
wasm-opt -Oz -o mi_juego_2d-opt.wasm mi_juego_2d.wasm
wasm-strip mi_juego_2d-opt.wasm -o mi_juego_2d-final.wasm
# Bajar a 15-30 MB.
Para testear en el navegador, la forma más simple con wasm-server-runner:
# Configurar el runner.
mkdir -p .cargo
cat > .cargo/config.toml <<EOF
[target.wasm32-unknown-unknown]
runner = "wasm-server-runner"
EOF
# Correr.
cargo run --release --target wasm32-unknown-unknown
# Abre http://127.0.0.1:8000 automáticamente.
Alternativa sin cargo run: usar wasm-pack + npm:
# cap-E — sección E.4: alternativa con wasm-pack.
cargo install wasm-pack
wasm-pack build --release --target web
npx serve .
index.html mínimoTu juego necesita un HTML que cargue el .wasm. El mínimo:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Mi Juego 2D</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; margin: 0 auto; image-rendering: pixelated; }
</style>
</head>
<body>
<script type="module">
import init from './pkg/mi_juego_2d.js';
init().catch(console.error);
</script>
</body>
</html>
El import init from './pkg/...' carga el glue que wasm-pack generó. Si no usás wasm-pack, podés generar el glue a mano con wasm-bindgen:
# cap-E — sección E.5: wasm-bindgen sin wasm-pack.
cargo install wasm-bindgen-cli
wasm-bindgen --out-dir ./pkg --target web ./target/wasm32-unknown-unknown/release/mi_juego_2d.wasm
Y el index.html apuntaría a ./pkg/mi_juego_2d.js.
bevy_cli: el camino oficial (alpha)Desde 2024-2025 existe una herramienta CLI oficial mantenida por TheBevyFlock: github.com/TheBevyFlock/bevy_cli. Sigue en alpha pero ya soporta el flujo web de principio a fin y va a sustituir a wasm-server-runner + wasm-bindgen-cli en el workflow cotidiano.
# cap-E — sección E.5b: bevy_cli (alpha).
cargo install --locked bevy_cli
# Compilar y servir para web con un solo comando:
bevy build --web
bevy run --web # sirve en http://127.0.0.1:8000 con hot-reload de assets
# Equivalente nativo:
bevy run
Estado real al cierre del libro: alpha. Para producción, seguís necesitando wasm-bindgen/wasm-pack manual, pero para iterar en desarrollo, bevy run --web es enorme ganancia. Verificá siempre el README del repo antes de actualizar; la API cambia rápido.
BEVY_*.tomlBevy y su CLI leen (o leerán, está en estabilización) un archivo BEVY_*.toml en la raíz del proyecto para configurar el build web: assets, optimizaciones de wasm, features del crate, etc. Es el equivalente al bevy.toml de metadatos pero orientado al build:
# cap-E — sección E.5c: ejemplo de BEVY.toml (alfa, revisar docs actual).
[web]
title = "Mi Juego 2D"
canvas-size = [1280, 720]
assets = ["assets/"]
optimize-wasm = true
[features]
default = ["bevy/audio"]
web = [] # features adicionales sólo para web
[build]
release = true # equivalente a --release
Caveat: el esquema de BEVY_*.toml se está definiendo con bevy_cli. Si el archivo no existe o el CLI no lo encuentra, usá bevy build --web --release directo. No inventes claves que no estén en el README del CLI a tu versión.
Los assets (sprites, sonidos, etc.) son un punto delicado. En nativo, Bevy los busca relativos al directorio de trabajo. En web, el "directorio" es la URL del servidor.
Por defecto, Bevy 0.18+ detecta el directorio de los assets según la URL. Si tu juego está en https://miusuario.github.io/mi-juego/, Bevy busca assets en https://miusuario.github.io/mi-juego/assets/. Para casos más complejos, podés setear BEVY_ASSET_ROOT:
//! cap-E — sección E.6: custom asset root.
use bevy::prelude::*;
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins.set(AssetPlugin {
file_path: "/static/assets".to_string(),
..default()
}));
app.run();
}
Caveat: en web, los assets deben servirse desde el mismo origen que el HTML (no podés tener assets en otro CDN sin CORS). Para simplificar, poné todo en la misma carpeta.
No todo lo que anda en nativo anda igual en web. Lista de caveats a esta edición:
bevy_hanabi por ejemplo), va lento o sin efecto en navegadores con WebGL2..wasm se descarga, se compila en el navegador, se inicializa. Para un juego de 20 MB, son 5-15 segundos según conexión.Performance general: un juego 2D simple (sprites, sin compute, sin shader custom) anda a 60 fps en web en hardware modesto. Un juego con mucho shader o compute, va a 30 fps o menos. Probá en Chrome, Firefox y Safari, en escritorio y móvil.
Bevy 0.19 + wgpu estabiliza el backend web. La elección entre WebGPU y WebGL2 se decide en build time mediante features de wgpu, y el navegador se selecciona en runtime según lo que soporte:
bevy_hanabi o cualquier compute.# Build para WebGPU-first con fallback WebGL2:
[dependencies]
bevy = { version = "0.19", features = ["webgpu"] }
# Build sólo WebGL2 (máxima compatibilidad, sin compute):
[dependencies]
bevy = { version = "0.19", features = ["webgl2"] }
Bevy hace la elección en runtime según navigator.gpu. Si el navegador expone WebGPU, lo usa; si no, cae a WebGL2 (si lo compilaste) o falla con un mensaje claro. No asumas que tu jugador tiene WebGPU: un 20-30% del tráfico web en 2026 todavía cae al fallback.
Los navegadores bloquean cualquier audio que empiece sin interacción previa del usuario. Esto rompe el caso típico de Bevy en el que el AudioPlayer arranca música en Startup. Solución patrón: esperar el primer gesture del usuario (clic, tecla, toque) antes de iniciar el contexto de audio.
//! cap-E — sección E.7c: workaround de autoplay.
use bevy::prelude::*;
// Resource flag: arranca en false, se pone en true en el primer input.
#[derive(Resource, Default)]
struct AudioUnlocked(bool);
fn unlock_on_input(
keys: Res<ButtonInput<KeyCode>>,
mouse: Res<ButtonInput<MouseButton>>,
mut unlocked: ResMut<AudioUnlocked>,
) {
if !unlocked.0 && (keys.just_pressed(KeyCode::Space) || mouse.just_pressed(MouseButton::Left)) {
unlocked.0 = true;
// Aquí sí podés arrancar la música con `commands.spawn(AudioPlayer(...))`.
}
}
Patrón típico de UI: mostrar un botón "Jugar" en el menú principal. El OnPointer<Click> de ese botón es el gesture que desbloquea el audio. Así matal dos pájaros: el usuario interactúa (desbloquea audio) y el juego arranca cuando él lo decide. Para el resto del juego, ya no hay bloqueo de autoplay.
itch.io es la plataforma estándar para juegos indie. Ofrece hosting gratis, sin review, y comisión opcional.
Pasos:
.wasm, el index.html, y la carpeta pkg/:```bash
zip -r mi-juego-web.zip index.html pkg/ assets/
```
Truco: en la página de tu juego en itch.io, en "Embed options", activá "Fullscreen button" para que los usuarios puedan jugar en pantalla completa.
GitHub Pages es hosting gratis para proyectos open source. Si tu juego es open source, es la opción natural.
Pasos:
gh-pages.index.html, pkg/, y assets/ a la raíz de esa rama.gh-pages y la carpeta raíz.https://tu-usuario.github.io/tu-repo/.Para automatizar el deploy, GitHub Actions con actions/upload-pages-artifact + actions/deploy-pages:
# cap-E — sección E.9: deploy automático a GitHub Pages.
name: deploy-web
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- run: cargo build --release --target wasm32-unknown-unknown
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- run: |
wasm-bindgen --out-dir pkg --target web \
target/wasm32-unknown-unknown/release/mi_juego_2d.wasm
cp index.html pkg/
# (opcional) cp -r assets pkg/
- uses: actions/upload-pages-artifact@v3
with:
path: pkg
- id: deployment
uses: actions/deploy-pages@v4
WebAssembly (Wasm): binario estándar que se ejecuta en navegadores (y otros runtimes). Es el target de Rust para web.
wasm32-unknown-unknown: target de Rust para compilar a Wasm.
wasm-pack: herramienta oficial de Rust para empaquetar crates como paquetes wasm.
wasm-bindgen: herramienta que genera el glue JavaScript ↔ Wasm.
wasm-opt: optimizador de Wasm (parte de binaryen).
wasm-server-runner: cargo runner que sirve tu build localmente.
bevy_cli: herramienta oficial (alpha) de TheBevyFlock que reemplaza al flujo manual wasm-bindgen + servidor. Soporta `bevy run --web` / `bevy build --web`.
BEVY_*.toml: archivo de configuración (alfa) que lee `bevy_cli` para assets web, optimizaciones wasm y features. Esquema en estabilización.
BEVY_ASSET_ROOT: variable de entorno / setting de Bevy que dice dónde están los assets.
WebGL2: API gráfica de navegadores anterior a WebGPU. Limitada, no soporta compute.
WebGPU: API gráfica moderna de navegadores. Soporta compute. Estable desde Chrome 113, Firefox 141 y Safari 18.2 (2023-2024). Bevy 0.19 lo elige en runtime cuando está disponible.
Autoplay policy: reglas de los navegadores que silencian audio hasta que el usuario interactúa. Workaround: arrancar el audio en el primer user gesture.
Wasm GC: garbage collection nativo de Wasm. Estable desde finales de 2023 / 2024 (Chrome 119, Firefox 120, Safari 18.2).
Fullscreen API: API del navegador para fullscreen. Requiere user gesture.
Autoplay policy: reglas de los navegadores que silencian audio hasta que el usuario interactúe.
itch.io: plataforma de hosting de juegos indie. Estándar de la industria.
GitHub Pages: hosting gratis para repos de GitHub.
gh-pages (rama): convención para la rama que sirve GitHub Pages.
HTML5 game: juego que corre en el navegador, usualmente canvas + JS/Wasm.
Wasm MVP (Minimum Viable Product): el primer estándar de Wasm. Limitado pero portable.
Wasm GC (Garbage Collection): propuesta de GC en Wasm. **Estable desde finales de 2023 / 2024**: soportado en Chrome 119 (oct 2023), Firefox 120 (nov 2023) y Safari 18.2 (dic 2024). Permite que lenguajes con GC nativo (Java, Kotlin, Dart, OCaml) generen Wasm más compacto. Para Rust no supone un cambio directo porque Rust no usa GC, pero libera espacio para mejorar tooling.
WebAssembly (W3C, 2017) — el primer estándar de Wasm. Inicialmente pensado como "C++ en el navegador", terminó siendo "Rust en el navegador". Bevy es uno de los frameworks más serios que apuntan a Wasm.
Emscripten (Alon Zakai, 2010) — el precursor. Compilaba C/C++ a JavaScript/asm.js. Sin Wasm, los juegos web eran emscripten + asm.js (lento).
Rust + Wasm (2018) — el combo que democratizó la cosa. Mozilla empujó fuerte; el cap. D del Rust Book tiene un tutorial clásico.
PixelFederation / Slott.co (2018) — uno de los primeros juegos Bevy publicados en web. Demostró que la promesa era real.
Bevy 0.6 (2022) — primera versión con soporte oficial para web.
itch.io (Leaf Corcoran, 2013) — la plataforma que hizo accesible publicar juegos indie. La sección "Web" de itch.io está llena de juegos Bevy/Rust.
Itch.io "play in browser" (2017) — feature que permite subir HTML5/Wasm y jugar sin descarga. Bevy va perfecto acá.
Google Stadia (2019-2023) — la intentona de Google de stream de juegos. Muerta, pero dejó infraestructura: Wasm en Chrome es muy maduro.
WebGPU (W3C, 2023–2025 estable) — la nueva API. **Chrome 113** (2023), **Firefox 141** (2025) y **Safari 18.2** (2024) ya lo ofrecen estable. La promesa: GPU access comparable a nativo en el navegador.
WebGPU adoption (2024-2026) — la migración está madura. En 2026 los tres motores principales lo soportan por defecto. Bevy 0.18+ usa WebGPU cuando está disponible, fallback a WebGL2.
❌ Asumir que el juego corre igual de bien en web que en nativo.
✅ Testear específicamente en web, en Chrome, Firefox y Safari, en escritorio y móvil.
💡 Por qué: WebGL2 ≠ Vulkan. Wasm ≠ nativo. Los matices de performance se acumulan.
❌ Olvidar el `wasm-opt` y subir un `.wasm` de 80 MB.
✅ Correr `wasm-opt -Oz` antes de publicar.
💡 Por qué: un `.wasm` sin optimizar es 3-5× más grande. El usuario abandona antes de que descargue.
❌ Hardcodear paths absolutos a los assets.
✅ Usar paths relativos y configurar `BEVY_ASSET_ROOT` si es necesario.
💡 Por qué: tu juego va a estar en `https://mi-usuario.github.io/mi-repo/`, no en `C:\games\mi-juego\assets\`. Los paths relativos funcionan siempre.
❌ Asumir que el audio suena al cargar.
✅ Iniciar el audio en respuesta a un user gesture (click, keypress).
💡 Por qué: las autoplay policies de los navegadores silencian el audio hasta que el usuario interactúe. Si querés música, poné un botón "Start" o esperá al primer input.
Problema: tenés un juego nativo que anda perfecto. Lo compilás a web. Anda. Pero a 30 fps. O no se ve el primer sprite. O el audio no suena.
Solución: tratá web como un target separado con su propio ciclo de QA. No es lo mismo que el nativo.
Build nativo → QA en Windows/Mac/Linux.
Build web → QA en Chrome/Firefox/Safari (escritorio + móvil).
Cuándo sí:
Cuándo no:
wasm32-unknown-unknown con wasm-opt para reducir tamaño.index.html mínimo y wasm-server-runner para iterar.Empezaste con un "Hola Mundo" en Bevy. Terminaste con un libro entero: ECS, render, física, audio, networking, dev tooling, release, web. El camino de un Bevy-dev indie está trazado. Lo que viene ahora es tuyo: elegí un proyecto, empezá por M1 (cap. 28), y construí algo. El libro es el mapa; el viaje es tuyo. ¡Éxitos!
Más allá de WASM, Bevy está en proceso de portar a no_std (sin la librería estándar de Rust). Esto abre la puerta a hardware embebido y consolas retro. La iniciativa es de la comunidad, con el tracking oficial en el issue #15460.
Qué hay portado al cierre del libro:
bevy_appbevy_ecs (parcial; GBA tiene issues con atomic CAS)bevy_utilsbevy_tasksstd por ahora.Game Boy Advance es el target estrella de la comunidad. Existe un crate bevy_mod_gba (de bushrat011899) que integra Bevy con el HAL agb para GBA. La POC funciona: un demo con sprites, input, y render 2D corre en hardware real (con hypervisor DS) y en mGBA.
# Cargo.toml de un proyecto Bevy en GBA.
[dependencies]
bevy = { version = "0.18", default-features = false }
bevy_ecs = { version = "0.18", default-features = false }
bevy_app = { version = "0.18", default-features = false }
bevy_utils = { version = "0.18", default-features = false }
agb = "0.20"
bevy_mod_gba = "0.1"
//! Anexo E — sección E.6: setup GBA.
#![no_std]
#![no_main]
use bevy::prelude::*;
use bevy_mod_gba::{AgbPlugin, Sprite};
use agb::{display, input};
#[no_mangle]
pub fn main() -> ! {
let mut app = App::new();
app.add_plugins(AgbPlugin::default());
app.add_systems(Startup, setup);
app.run();
loop {}
}
fn setup(mut commands: Commands) {
// Spawn de un sprite en el tile de inicio.
commands.spawn(Sprite::new("player"));
}
Otros targets no_std testeados:
wasm32v1-none): target tier 2 estabilizado en Rust 1.84. Diferente del wasm32-unknown-unknown que usa E.1.thumbv4t-none-eabi): parcial (atomic CAS no soportado en armv4t, requiere portable_atomics).Cuándo usar Bevy en embebidos:
wasm4 (consola virtual de 4KB de RAM; Bevy no entra ahí todavía, pero la POC está).Cuándo NO usarlo:
El estado real al cierre del libro: la POC de GBA funciona en emulador y en hardware real con hypervisor. No intentes publicar un juego comercial con esto todavía. Pero si quieres ver tu código Bevy corriendo en una consola de los 90, hoy es posible. Eso ya es un hito.
★ **wasm4 (2021)** — una "consola virtual" de 4KB de RAM y 160x160
píxeles. Organizan game jams donde tienes que hacer un juego
completo que entre en 4KB. Bevy no cabe todavía, pero el día
que el no_std madure, los jam de wasm4 serán el banco de pruebas.
★ **bevy_mod_gba (2024)** — bushrat011899 logró correr Bevy en
Game Boy Advance. El demo es 200 líneas: sprite, input, render.
Funciona en mGBA (emulador) y en DS real con hypervisor. El
autor documentó el journey en su blog; es lectura obligada
si te interesa el Bevy en retro.
★ **bevy_ecs en UEFI (2024)** — el primer "bootable Bevy" sin
SO. Boots en menos de un segundo. Útil para demos técnicas
en conferencias, no para juegos.