By Max Wells, updated September 2026
TL;DR: Bevy is a data-driven game engine built in Rust, designed around the ECS (Entity Component System) pattern. Bevy 0.19 (June 19, 2026) is the current release; 0.18 (January 13, 2026) is the one most tutorials still target. The ECS core has been stable since 0.15, so getting-started knowledge carries across versions even though the render and UI APIs churn.
- ECS architecture: Entities, Components, Systems, a data-oriented model that is cache-friendly and parallel by default
- Performance: Bevy's scheduler runs non-conflicting systems in parallel automatically, with no manual thread management
- Adoption: roughly 48K GitHub stars, the most-starred Rust game engine by a wide margin
- Release cadence: a new minor version roughly every three months, each with a migration guide because breaking changes are routine
- Editor: still no official visual editor as of 0.19; Bevy remains a code-first engine
Who Should Read This?
This guide is for two readers. The first is a Unity or Godot developer curious about a Rust-native engine: it walks the ECS model from first principles so you can judge whether Bevy fits a real project. The second is a Rust engineer who wants games as a side project or a career direction: the code examples are runnable starting points.
On the career question specifically: dedicated Bevy job postings are still rare, so treat Bevy work as a portfolio and skill signal rather than a direct hiring path. The transferable value is ECS design, data-oriented performance work, and shipping something non-trivial in Rust, which reads well for systems and engine-adjacent roles. US game-industry engineering pay broadly runs around $110Kâ$180K, with senior engine and tools engineers higher, and Rust is increasingly on studios' evaluation lists for performance-critical systems.
What Is Bevy and Why Is It the Leading Rust Game Engine?
Bevy is a free, open-source game engine built entirely in Rust, designed around an ECS (Entity Component System) architecture that keeps game code modular, testable, and fast to iterate on.
Bevy is not the only Rust game engine (ggez, macroquad, and Fyrox all exist), but it has become the default choice by combining Rust's performance with a genuinely modern architecture. Where traditional game engines use object hierarchies and inheritance, Bevy uses composition: every game object is an entity with attached components, and systems process entities based on which components they have.
| Engine | Language | Architecture | Maturity | Stars |
|---|---|---|---|---|
| Bevy | Rust | ECS | Active development | ~48K |
| Unity | C# | GameObject/Component | Very mature | N/A (closed) |
| Godot | GDScript/C++ | Scene tree/Nodes | Mature | ~117K |
| Fyrox | Rust | Scene tree | Moderate | ~9.5K |
| macroquad | Rust | Immediate mode | Simple/limited | ~4.6K |
Bevy is not Unity or Godot in breadth: it has no visual editor, its asset ecosystem is thin, and the API still breaks between minor versions. What it offers is a clean, code-first ECS engine in a memory-safe language with no license fees or royalties. That trade is worth taking if you already know Rust, want to learn ECS properly, or plan to contribute to a fast-moving open-source project. It is the wrong trade if you need a mature editor workflow or want to ship a first commercial game in a hurry.
Bottom line: Bevy is production-ready for indie games, jam entries, and prototypes, but it stays a code-first engine with no official visual editor through 0.19. Choose it if you want Rust's performance and the ECS model and are comfortable working entirely in code. For editor-driven workflows, Unity or Godot remain the practical choice.
What Is the ECS Architecture and Why Does Bevy Use It?
ECS separates data (Components) from logic (Systems) and identity (Entities): enabling automatic parallelism, clear data flow, and a composable architecture that scales from tiny games to complex simulations.
Traditional game objects mix data and behavior:
// Traditional OOP approach (not Rust):
class Enemy {
position: Vec3,
health: f32,
fn update() { ... } // data and behavior coupled
fn render() { ... }
}ECS separates them:
// ECS approach:
// Entity: just an ID (e.g., entity 42)
// Components: pure data attached to entities
struct Position(Vec3);
struct Health(f32);
struct Enemy; // marker component
// Systems: functions that query for entities with specific components
fn move_enemies(mut query: Query<&mut Position, With<Enemy>>) {
for mut pos in &mut query {
pos.0.x += 1.0;
}
}Why ECS wins for performance:
- Components of the same type are stored contiguously in memory: cache-friendly iteration
- Systems that operate on different component sets can run in parallel automatically: Bevy's scheduler handles this
- No virtual dispatch: systems query concrete types, the compiler generates optimal code
The performance argument for ECS is not hypothetical. When a system iterates 10,000 enemies' positions, those positions are stored in a contiguous array in memory : the CPU's cache prefetcher can load them efficiently. In a traditional OOP hierarchy where each enemy object has position scattered across the heap alongside other fields, cache misses dominate the iteration time. ECS is the canonical solution to the "death by thousand cache misses" problem in game engines.
Bottom line: ECS isn't just an architectural preference : iterating 10,000 entities with contiguous component storage is cache-optimal in a way that OOP object hierarchies cannot match. Bevy systems run in parallel automatically based on which components they access, giving you multi-core performance without manual thread management.
How Do You Set Up a Bevy Project?
Add Bevy to your Cargo.toml and create an App with systems : that's the entire setup.
# Cargo.toml
[package]
name = "my-game"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = "0.19"
# Faster compile times during development
[profile.dev]
opt-level = 1
[profile.dev.package."*"]
opt-level = 3// src/main.rs : the minimal Bevy app
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins) // Window, rendering, input, audio, etc.
.add_systems(Startup, setup)
.add_systems(Update, (move_player, handle_input))
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
// Spawn a 2D camera
commands.spawn(Camera2d);
// Spawn a player entity with components
commands.spawn((
Player,
Speed(200.0),
Mesh2d(meshes.add(Circle::new(30.0))),
MeshMaterial2d(materials.add(Color::srgb(0.3, 0.5, 0.9))),
Transform::from_xyz(0.0, 0.0, 0.0),
));
}The [profile.dev.package."*"] setting is important for Bevy development. Bevy's first compile is slow : it compiles a large dependency tree. With opt-level = 1 for your code and opt-level = 3 for dependencies, the initial compile is still slow (~5 minutes on a typical laptop) but subsequent incremental builds are fast (10â30 seconds), and the game actually runs at a playable framerate in debug mode. Without this setting, unoptimized Bevy debug builds run at 2â5 FPS.
3 spots open this month â Check if you are eligible.
We help experienced developers transition into Rust roles at âĴ80KââĴ150K+ in Europe or $130Kâ$200K+ in the US.
How Do Components, Entities, and Systems Work in Practice?
Entities are IDs, components are data structs attached to entities, and systems are functions that query for specific component combinations : Bevy wires everything together automatically.
use bevy::prelude::*;
// Components : plain data structs
#[derive(Component)]
struct Player;
#[derive(Component)]
struct Enemy;
#[derive(Component)]
struct Speed(f32);
#[derive(Component)]
struct Health(f32);
// System: move players based on keyboard input
fn move_player(
keyboard: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
mut query: Query<(&mut Transform, &Speed), With<Player>>,
) {
for (mut transform, speed) in &mut query {
let mut direction = Vec3::ZERO;
if keyboard.pressed(KeyCode::ArrowUp) { direction.y += 1.0; }
if keyboard.pressed(KeyCode::ArrowDown) { direction.y -= 1.0; }
if keyboard.pressed(KeyCode::ArrowLeft) { direction.x -= 1.0; }
if keyboard.pressed(KeyCode::ArrowRight) { direction.x += 1.0; }
if direction.length() > 0.0 {
direction = direction.normalize();
transform.translation += direction * speed.0 * time.delta_secs();
}
}
}
// System: kill enemies with zero health
fn despawn_dead_enemies(
mut commands: Commands,
query: Query<(Entity, &Health), With<Enemy>>,
) {
for (entity, health) in &query {
if health.0 <= 0.0 {
commands.entity(entity).despawn();
}
}
}Query filters let you be precise about which entities each system touches:
With<Player>: entities that havePlayercomponentWithout<Enemy>: entities that don't haveEnemycomponentChanged<Transform>: entities whoseTransformchanged this frameAdded<Health>: entities that just got aHealthcomponent
The query filter system is where Bevy's ECS architecture pays off most visibly. Systems only process the entities they actually need : the ECS world maintains archetype tables so that "entities with Position AND Health AND Enemy" is a single, contiguous array lookup, not a search through all entities. Adding a thousand harmless background props does not slow down your enemy AI system, because the system only sees entities matching its exact query.
How Do You Load Assets (Sprites, Audio, Fonts)?
Bevy's asset system handles loading asynchronously : use AssetServer to load assets and Handle<T> to reference them.
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
// Load a sprite
let player_texture: Handle<Image> = asset_server.load("sprites/player.webp");
commands.spawn((
Player,
Sprite::from_image(player_texture),
Transform::from_xyz(0.0, 0.0, 0.0),
));
// Load and play audio
commands.spawn((
AudioPlayer::new(asset_server.load("audio/background.ogg")),
PlaybackSettings::LOOP,
));
}
// Load a texture atlas (sprite sheet)
fn setup_with_atlas(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
let texture = asset_server.load("sprites/character_sheet.webp");
let layout = TextureAtlasLayout::from_grid(UVec2::new(48, 48), 8, 4, None, None);
let atlas_layout = texture_atlas_layouts.add(layout);
commands.spawn((
Player,
Sprite::from_atlas_image(texture, TextureAtlas {
layout: atlas_layout,
index: 0,
}),
Transform::from_xyz(0.0, 0.0, 0.0),
));
}Asset handles are cloneable lightweight references : the asset itself is loaded once and stored in the Assets<T> resource. Multiple entities can hold handles to the same texture without duplicating the GPU memory. Bevy's asset system handles hot-reloading automatically in development builds: change a sprite or shader file and the game updates without a restart.
Which Bevy Version Should You Use, and How Fast Does It Change?
Use the latest release, currently Bevy 0.19 (June 19, 2026), for a new project. Follow the migration guide when you upgrade, because Bevy ships breaking changes on almost every minor version.
Bevy releases a new minor version roughly every three months. Recent ones (bevy.org/news):
| Version | Released | Notable direction |
|---|---|---|
| 0.19 | June 19, 2026 | Current release; continued renderer and UI work |
| 0.18 | January 13, 2026 | Atmosphere occlusion and PBR shading, Solari raytraced renderer improvements, variable fonts, first-party fly/pan camera controllers, scenario-driven Cargo feature collections (2D, 3D, UI) |
| 0.15 | late 2024 | Introduced required components, the pattern where declaring #[require(Transform)] on a component auto-inserts its dependencies on spawn |
The important practical point is churn. Every minor release comes with a migration guide, and non-trivial projects budget real time to move between versions. Renderer, UI, and asset APIs change the most; the ECS core (entities, components, systems, queries, Commands, states) has been stable since around 0.15, which is why the getting-started material in this guide holds across versions.
Required components are worth knowing because they remove a classic Bevy footgun. In older Bevy, spawning an entity with a Sprite but no Transform produced no error: the entity simply did not render, and you debugged by guessing what was missing.
#[derive(Component)]
#[require(Transform, Health)]
struct Player;
// Transform and Health are inserted automatically on spawn
commands.spawn(Player);Health here needs a Default impl (or a #[require(Health = starting_health())] initializer) so Bevy knows what value to insert.
How Do You Handle Game State and Scene Transitions?
Bevy's state system lets you organize game logic into discrete states : MainMenu, Loading, InGame, Paused : and only run systems when the appropriate state is active:
use bevy::prelude::*;
#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default)]
enum GameState {
#[default]
MainMenu,
Loading,
InGame,
Paused,
GameOver,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_state::<GameState>()
// Only run setup_game when entering InGame
.add_systems(OnEnter(GameState::InGame), setup_game)
// Only run update_game while in InGame state
.add_systems(Update, update_game.run_if(in_state(GameState::InGame)))
// Cleanup when leaving InGame
.add_systems(OnExit(GameState::InGame), cleanup_game)
.run();
}
// Transition to game on button press
fn handle_start_button(
mut next_state: ResMut<NextState<GameState>>,
keyboard: Res<ButtonInput<KeyCode>>,
) {
if keyboard.just_pressed(KeyCode::Enter) {
next_state.set(GameState::InGame);
}
}The state system integrates directly with Bevy's scheduler : you never need to write if game_state == InGame guards inside your systems. The scheduler itself filters system execution based on state. This keeps system code focused on its single responsibility rather than constantly checking global state.
What Common Mistakes Do Game Developers Make When Learning Bevy?
-
Spawning entities with missing components. Spawning something with a
Spritebut noTransformproduces no error, it just silently fails to render. Required components (#[require(...)]) fix this for your own component types, but you still need to read plugin documentation to know which components a given bundle or plugin expects. -
Mutating queries in conflicting ways. Bevy's parallel scheduler will panic at runtime if two systems both try to mutate the same component on the same entities without being ordered relative to each other. The error message ("system parameter conflict") can be confusing. The fix is to add explicit ordering with
.before()/.after(), or to restructure so the systems operate on non-overlapping component sets. -
Running expensive logic every frame without caching. A system that recalculates pathfinding for all enemies every frame at 60Hz will tank performance even in a small game. Use
Changed<T>query filters to only recalculate when the relevant data actually changed, or schedule expensive systems to run at a lower rate usingFixedUpdate. -
Ignoring the asset loading lifecycle. Assets load asynchronously: an
AssetServer::load()call returns aHandle<T>immediately, but the asset may not be ready for several frames. Accessing the asset data immediately after loading (viaassets.get(&handle)) will returnNoneuntil loading completes. Use Bevy's asset loading states or theAssetEvent::LoadedWithDependenciesevent to detect when assets are ready. -
Not understanding
Commandsdeferred execution.Commandsare not executed immediately: they are queued and applied at specific sync points in the frame. If you spawn an entity withcommands.spawn(Player)and then immediately try to query for it in the same system, it will not be found. This surprises developers coming from immediate-mode game frameworks where entity creation is instant. -
Underestimating compile times. Bevy's first compile on a new machine takes 3â7 minutes even with fast hardware. Engineers new to Bevy sometimes assume something is wrong and restart the build, making things worse. Set up the
[profile.dev.package."*"]optimization, usecargo build --bin gameinstead ofcargo runduring initial setup to verify the compile works, and budget significant CI time for Bevy projects.
Where Does This Leave You?
Bevy rewards people who already think in Rust. The ECS model is learnable in a weekend, but the parts that trip up newcomers, ownership across systems, lifetimes in queries, Commands deferral, async asset loading, are Rust fundamentals, not engine trivia. Getting solid on those first is what turns a stalled side project into a shipped prototype.
That is the gap Rustify's 9-week Fullstack Rust bootcamp is built to close: 1:1 coaching through ownership, traits, and async before you apply them to ECS design, performance work, and the patterns that keep a growing Bevy codebase maintainable. Book a call if you want a direct read on whether it fits where you are now.
Frequently Asked Questions
Bevy is a free, open-source, MIT/Apache-licensed game engine written entirely in Rust, built around an Entity Component System architecture. There are no runtime fees, royalties, or closed-source components. You write game logic in Rust as plain functions (systems) that query for entities holding specific components. Bevy targets 2D and 3D, desktop (Windows, macOS, Linux) and WebAssembly, and reached roughly 48K GitHub stars by 2026, making it the most-adopted Rust game engine by a wide margin.
Bevy is production-ready for indie games, game jams, and prototypes, and several commercial titles have shipped on it. The constraints are real: no official visual editor, APIs that break between minor versions (each with a migration guide), and 3D tooling that lags 2D in maturity. It suits small teams that are comfortable working in code and can absorb an engine upgrade every few months. For AAA-scale scope or an editor-driven pipeline, Unreal or Unity is still the practical choice.
Unity is faster to a first shipped game: it has a visual editor, a large asset store, and beginner tutorials, none of which Bevy matches. Bevy's advantages are runtime performance, Rust's compile-time safety, a royalty-free license, and a cleaner architecture. The deciding factor is Rust: if you already know it, Bevy is the stronger long-term choice; if you do not, Unity or Godot gets you to a playable game months sooner. The Bevy learning cost is front-loaded, stacking the Rust curve and the ECS curve together.
Bevy's first compile takes roughly 2 to 7 minutes on a typical laptop because it builds a large dependency tree covering rendering, audio, input, windowing, and reflection. Incremental builds after editing your own code drop to 10 to 40 seconds, and the [profile.dev.package."*"] optimization from the setup section is essential to keep the game playable in debug mode. Each release has trimmed compile time further. In CI, cache ~/.cargo and target/ keyed on a Cargo.lock hash so dependencies are not rebuilt every run.
Yes. Bevy compiles to the wasm32-unknown-unknown target, and trunk handles building and serving. The output runs in any WebGL2-capable browser (a WebGPU backend is also progressing). Expect somewhat lower performance than native because of WASM overhead and WebGL2's limits versus Vulkan, Metal, or DirectX, but 2D games hold 60 FPS in the browser comfortably; 3D is more variable. The bevy_web_asset crate handles asset loading where browser path resolution differs from the filesystem.
Start with the official Bevy Book and learn pages at bevy.org/learn/, then keep the unofficial bevy_cheatbook at bevy-cheatbook.github.io open alongside it, since many developers treat it as the practical reference. The community Discord is very active and its #help channel usually answers within hours. For video, the Bevy community YouTube channel and LogicProjects cover ECS fundamentals through advanced topics. The Bevy repository itself ships dozens of runnable examples, one per engine feature, and updating them to a new version is a fast way to learn what changed.
Bevy has no built-in save system. The standard approach is to serialize the relevant game state with serde (most Bevy component types accept #[derive(Serialize, Deserialize)]) and write it to disk with std::fs, using JSON or RON for readable jam-project saves. The bevy_save community crate adds a higher-level save/load layer with rollback support. For commercial games, move to a structured binary format or a SQLite-backed store via rusqlite so saves stay compact and versionable.
Keep Reading
- Tauri vs Electron: Which Should You Use for Desktop Apps?
- Rust Generics and Traits Explained
- Rust vs C++: Which Should You Choose?
- Rust Ownership and Borrowing Explained
Sources
- Bevy Engine (bevy.org): official site; the project domain moved from bevyengine.org to bevy.org
- Bevy release notes: 0.19 released June 19, 2026; 0.18 released January 13, 2026
- Bevy 0.18 announcement: atmosphere occlusion, Solari raytracing, variable fonts, first-party camera controllers
- Bevy GitHub (bevyengine/bevy): ~48K stars as of 2026
- Bevy Cheatbook: unofficial reference
- Catherine West: Using Rust for Game Development (RustConf 2018)

