Unofficial Bevy Cheat Book

Concise practical reference to the Bevy game engine.

Covers useful syntax, features, programming patterns, and solutions for common game development tasks.

Designed to be easy to read, straight to the point, using simple language to focus on the important information.

While rich in content, this book does not aim to be exhaustive or complete. It cannot feasibly cover every feature of bevy. It is my best attempt to teach the most practically-relevant aspects.

The book has several sections:

  • Quick Reference: alphabetical listing of various Bevy and game dev jargon and concepts, with quick definitions and links to relevant pages in the book
  • Bevy Setup Tips: Tips and tricks for your development tools (only some may be relevant to you)
  • Bevy Programming: overview of programming in Bevy
  • Bevy Features: how to use various Bevy game engine features
  • Common Pitfalls: common issues or surprises to watch out for
  • Advanced Patterns: expert topics; non-obvious tricks or techniques; may be controversial
  • Bevy Cookbook: miscellaneous examples for specific common tasks
  • Bevy on Different Platforms: information about using Bevy with different platforms and OSs

Not intended to be read in order. Jump to whatever is useful to you!

Welcome! May this book serve you well!

(don't forget to Star the book's GitHub repository 😉 and consider donating 🙂)

Bevy has a rich collection of official code examples.

Check out bevy-assets, for community-made resources.

Our community is very friendly and helpful. Feel welcome to join the Bevy Discord to chat, ask questions, or get involved in the project!

If you want to see some games made with Bevy, see itch.io or Bevy Assets.

Maintenance

This version of the book is for Bevy release 0.5.

I intend to keep this book up-to-date and relevant with every new Bevy release. I also try to regularly make improvements to it, when I can manage it.

Support Me

If you like this book, please consider supporting me via GitHub Sponsors. (Patreon WIP)

Sponsor

I'd like to keep improving and maintaining this book, to provide a high-quality independent learning resource for the Bevy community.

Your donation helps me work on such freely-available content. Thank you! ❤️

Support Bevy

If you like the Bevy Game Engine, you should consider donating to the official project.

Sponsor

License

Copyright © 2021 Ida Iyes.

All code in the book is provided under the MIT-0 License. At your option, you may also use it under the regular MIT License.

The text of the book is provided under the CC BY-NC-SA 4.0.

Exception: If used for the purpose of contribution to the Official Bevy Project and/or its Official Documentation, the entire content of the book may be used under the MIT-0 License.

Contributions

Development of this book is hosted on GitHub.

Please file GitHub Issues for any wrong/confusing/misleading information, as well as suggestions for new content you'd like to be added to the book.

Contributions are accepted, with some limitations.

See the Contributing section for all the details.

Stability Warning

Bevy is still a very new and experimental game engine! It has only been public since August 2020!

While improvements have been happening at an incredible pace, and development is active, Bevy simply hasn't yet had the time to mature.

There are no stability guarantees and breaking changes happen often!

Usually, it not hard to adapt to changes with new releases (or even track the main git development branch), but you have been warned!

Quick Reference: Alphabetical Index / Glossary

This page lists many common terms and concepts that you may come across while working with Bevy. It is in alphabetical order. Every entry has a brief definition, and links to relevant places in this book, where it is explained, if any.

It contains both bevy-specific jargon (including advanced topics that might not yet be covered by this book), as well as general game development or computer graphics topics that are relevant to working with Bevy.

The list is not exhaustive. I try my best to expand it to include anything that may be useful. If you notice any omissions / want something added, file an issue on GitHub or send me a message on Discord (Ida Iyes#0981).

Bookmark this page in your browser, for a quick reference when working with Bevy!

(some of the links may be broken, as they refer to planned or unpublished content. This book is WIP/incomplete; thank you for understanding! <3 )


Bevy Programming Jargon

Terms and concepts related to Bevy-specific features and APIs.

TopicDefinition
App / App BuilderBevy entry point; setup all the things to run.
AssetsThe data used by your game. Typically loaded from external files, or generated procedurally using code.
Asset LoaderImplementation for loading a specific asset type from a specific file format.
AssetServerBevy API for accessing and loading assets from external data files.
BundlesA "template" for easily spawning entities with a common set of component types.
Change DetectionWrite logic that responds to data being changed.
CommandsSpawn/despawn entities, manage components and resources. Deferred until they can be safely applied.
ComponentsPer-entity data. Basic data primitive in Bevy.
EntitiesID for a set of component values. Often conceptually represents an "object".
EventsCommunicate between systems. Send/receive data.
Exclusive SystemsSystems that have full access to the whole ECS world and do not run in parallel with any other systems.
Fixed TimestepRun some logic at a fixed time interval. Important for physics and simulations.
HandlesID for referring to an Asset in your game code. Reference counted (Bevy automatically unloads assets when there are no more handles).
Hot ReloadingAutomatically reloading assets from files if they are changed while the game is running.
LabelsNames for systems, stages, and other things.
Local ResourcesPer-system data.
Non-SendData that cannot be used in a multithreaded context, must be confined to a single thread.
Parent/Child HierarchyEntities in a hierarchical relationship.
PluginsBuild the App in a modular way.
QueriesFind matching entities and access their component data.
Query FiltersCriteria for narrowing down the entities to be accessed by a query.
Query SetsResolve query conflicts.
Removal DetectionWrite logic that responds to data being removed from the ECS.
ResourcesGlobal data for the whole app.
Run CriteriaLow-level primitive for controlling if systems run.
ScenesCollection of preconfigured entities that you can spawn into the world. Similar to "prefabs" in other game engines.
Schedule (systems)All the systems that Bevy runs every frame update, optimized to run on the task pool with maximum parallelism and performance.
StagesHard synchronization points for runtime scheduling.
StatesControl which systems run, by defining different "modes" your app can be in.
SystemsThe functions that contain your game logic.
SystemParamsThe acceptable Rust types to be used as function parameters in Bevy systems.
System ChainingCombine multiple Rust functions into one big system.
System OrderControl the runtime order of execution of systems.
System SetsGroup multiple systems together to apply common properties (labels, ordering, states, run criteria).
TextureAsset type, typically an image (but more generally any data) that can be sampled by the GPU during rendering (in a shader).
TransformsPosition and orientation of an object. May be relative to a parent object.
Untyped HandlesAsset Handle that can refer to an asset regardless of the asset type.
Weak HandlesAsset Handles that are not reference-counted (do not prevent the asset from being unloaded).
WorldThe underlying data structure / storage of the ECS. Contains all component and resource data.

Ecosystem Jargon

Terms and concepts related to the community and ecosystem around bevy and the development of the project, such as libraries and technologies that bevy uses.

TopicDefinition
Assets; Bevy AssetsPage on the Bevy Website listing community-made plugins and content (not to be confused with Assets, as in your game's data).
Data-drivenProgramming style / paradigm, where functionality is thought of in terms of the data it works with. Bevy is based on this philosophy.
ECSThe data storage / programming paradigm Bevy uses. Conceptually like a database. Bevy is often compared to other ECS implementations.
Features (cargo)A way to configure what optional functionality is included in your build.
FlexboxThe layout model used in Bevy UI (originates from CSS in web browsers).
GILRSThe library that Bevy uses for controller/joystick support.
GLTFFile format for 3D assets; can contain meshes, textures, materials, scenes.
Kira3rd-party audio library often used with Bevy (bevy_kira_audio plugin), much more feature-rich than Bevy's own audio.
main (bevy main)Development version of Bevy; git branch containing the latest unreleased changes.
MetalLow-level system API for accessing GPU hardware on Apple systems (macOS/iOS).
OpenGLLegacy GPU API for systems that do not support Vulkan. Not yet supported by Bevy.
Plugins (3rd-party crates)Libraries made by the community that can be added to your Bevy App.
VulkanLow-level system API for accessing GPU hardware. The interface to the graphics driver. Available on most platforms except Apple.
WebAssembly (WASM)New technology that allows running software like Bevy inside of a web browser.
WebGPU (WGPU)The cross-platform GPU API that Bevy uses. Allows using modern GPU features safely across different platforms (desktop/mobile/web).
WinitThe library that Bevy uses for windowing (managing the OS windows to display in)

Game Development Jargon

General game development concepts that are applicable to working with Bevy.

TopicDefinition
AssetsThe data used by your game. Typically loaded from external files, or generated procedurally using code.
CameraEntity representing a "view" into the game world, to be rendered.
Coordinate SystemOrientation of the X/Y/Z axes, defining the 3D space.
Fixed TimestepRun some logic at a fixed time interval. Important for physics and simulations.
MaterialThe shading properties for a surface to be drawn by the GPU, such as its color/texture, how shiny it is, ...
Mesh3D geometry data, consists of vertices, typically arranged into many triangles. Required for the GPU to draw an object.
Parent/Child HierarchyEntities in a hierarchical relationship.
Procedural GenerationCreating game assets using code/math/algorithms (often done at runtime, instead of loading asset files).
RaycastCalculating a simulated "ray" in the game world. Used, for example, for checking what object is under the mouse cursor.
ScenesCollection of preconfigured entities that you can spawn into the world. Similar to "prefabs" in other game engines.
ShadersCode that runs on the GPU. Typically for rendering graphics, but also for general computations.
SpritesGame object that is a 2D rectangle displaying an image. Most 2D games are made from these.
TextureTypically an image (but more generally any data) that can be sampled by the GPU during rendering (in a shader).
TransformsPosition and orientation of an object. May be relative to a parent object.
UIUser Interfaces like menus and in-game HUDs, typically displayed as overlays on top.

Rendering Jargon

Concepts that come up when working with computer graphics and the GPU, as applicable to Bevy.

TopicDefinition
Anisotropic FilteringMethod of sampling a texture that accounts for the angle between the surface and the camera, resulting in better visual quality.
Anti-AliasingTechniques to reduce aliasing artifacts (shimmering or jagged edges, when there is too much detail for the display resolution).
BatchingCombining compatible data from many meshes/entities together, so that it can be drawn by the GPU all at once.
CameraEntity representing a "view" into the game world, to be rendered.
Compute Shaders / GPU ComputeA way to use the GPU for general data processing.
Coordinate SystemOrientation of the X/Y/Z axes, defining the 3D space.
CullingFiguring out which parts of the scene don't need to be drawn and skipping them to improve performance.
Directional LightGlobal light source that illuminates the whole world, at a specified angle/direction. Typically models the sun in outdoor scenes.
Draw CallTelling the GPU to render something. Expensive; for best performance, aim to draw everything you need with the fewest draw calls.
FrustumThe space/volume that is visible from a camera. Everything that the camera can see.
Indices / Index BufferA way to make mesh data more compact (to save memory) by deduplicating vertices.
InstancingA way to tell the GPU to draw many copies of the same mesh efficiently. Useful for things like vegetation (lots of grass / trees).
MaterialThe shading properties for a surface to be drawn by the GPU, such as its color/texture, how shiny it is, ...
Mesh3D geometry data, consists of vertices, typically arranged into many triangles. Required for the GPU to draw an object.
MipmapsTextures with data available in many sizes (like 64x64, 32x32, 16x16, 8x8, ...). The GPU automatically uses the most appropriate.
MSAAMulti-Sample Anti-Aliasing: hardware AA method: for pixels along edges of meshes, the GPU will render multiple samples.
Normals / Normal VectorsThe direction perpendicular to a surface. Useful for shading, to compute how light interacts with the surface.
Normal MapsA way to add extra detail to a 3D object, using a texture that changes the normals to affect how light interacts with it.
Physically-Based RenderingModern realistic 3D graphics technique that uses materials that model physical properties, like how rough or metallic a surface is.
Point LightA light source that radiates in all directions from a given position, like a lamp or candle.
Post-ProcessingAfter rendering (but before displaying), apply some visual effects to the whole image.
Render GraphBevy's abstraction for modular configurable rendering. Connects "nodes" together to enable various graphical features.
Render PipelineThe procedure to be executed by the GPU to draw something. Includes the shaders to run, as well as setting fixed hardware parameters.
Sampler / Sampling (textures)How the GPU picks a value (like a color) from a specific position on a texture.
ShadersCode that runs on the GPU. Typically for rendering graphics, but also for general computations.
TextureTypically an image (but more generally any data) that can be sampled by the GPU during rendering (in a shader).
TexelA pixel that is part of a texture.
TransformsPosition and orientation of an object. May be relative to a parent object.
UniformsShader input data that is "global" for the whole mesh (unlike Vertex Attributes, which are per-vertex).
UVs / UV coordinatesTexture coordinates, used to map a texture to a mesh. The position on the texture that each vertex corresponds to.
Vertex / VerticesBuilding block of Meshes, typically a point in 3D space, with associated vertex attributes.
Vertex AttributesData associated with each vertex in a mesh, such as its position, color, UVs, normals, etc.

Bevy Setup Tips

This chapter is a collection of additional tips for configuring your project or development tools, collected from the Bevy community, beyond what is covered in Bevy's official setup documentation.

If you know of anything that would be nice to add, please file an issue on GitHub or ping me on Discord (@Ida Iyes#0981).


Also see the following other relevant content from this book:

Using bleeding-edge Bevy (bevy main)

Bevy development moves very fast, and there are often exciting new things that are yet unreleased. This page will give you advice about using development versions of bevy.

Quick Start

If you are not using any 3rd-party plugins and just want to use the bevy main development branch:

[dependencies]
bevy = "0.5"

[patch.crates-io]
bevy = { git = "https://github.com/bevyengine/bevy" }

For more info, read on.

Should you use bleeding-edge bevy?

Currently, bevy does not make patch releases, only major releases when there are enough exciting new changes for a flashy press release. The latest release is often missing the latest bug fixes, usability improvements, and features. It may be compelling to join in on the action!

If you are new to bevy, this might not be for you; you might be more comfortable using the released version. It will have the best compatibility with community plugins and documentation.

The biggest downside to using unreleased versions of bevy is 3rd-party plugin compatibility. Bevy is unstable and breaking changes happen often. However, many actively-maintained community plugins have branches for tracking the latest bevy main branch, although they might not be fully up-to-date. It's possible that a plugin you want to use does not work with the latest changes in bevy main, and you may have to fix it yourself.

The frequent breaking changes might not be a problem for you, though. Thanks to cargo, you can update bevy at your convenience, whenever you feel ready to handle any possible breaking changes.

If you choose to use bevy main, you are highly encouraged to interact with the Bevy community on Discord and GitHub, so you can keep track of what's going on, get help, or participate in discussions.

Common pitfall: mysterious compile errors

When changing between different versions of Bevy (say, transitioning an existing project from the released version to the git version), you might get lots of strange unexpected build errors.

You can typically fix them by removing Cargo.lock and the target directory:

rm -rf Cargo.lock target

See this page for more info. See this cargo issue about this bug.

If you are still getting errors, it is probably because cargo is trying to use multiple different versions of bevy in your dependency tree simultaneously. This can happen if some of the plugins you use have specified a different bevy version from your project. This is annoying, but easy to fix. Read the next section below for advice on how to configure your project in a way that minimizes the chances of this happening.

How to use bleeding-edge bevy?

The recommended way is using a cargo patch:

[dependencies]
# keep this as normal
bevy = "0.5"

[patch.crates-io]
# override it with bevy from git
bevy = { git = "https://github.com/bevyengine/bevy" }
# or if you have it cloned locally:
#bevy = { path = "../bevy" }

Doing it this way will tell cargo to replace the version of bevy in your entire dependency tree, including for 3rd-party plugins (assuming that they also list the crates-io version (bevy = "0.5") in their dependencies).

This works better than specifying the git or path directly in your [dependencies], because you avoid the risk of potentially having multiple different versions of bevy in your dependency tree, if any 3rd-party plugins you use have specified a different version.

Unfortunately, some plugin authors put the git version directly in their [dependencies], which breaks the above setup. This can be fixed by adding another cargo patch, to also override the git repository:

[patch."https://github.com/bevyengine/bevy"]
bevy = { path = "../bevy" }

Some 3rd-party plugins depend on specific bevy sub-crates, rather than the full bevy. You may additionally have to patch those individually:

[patch.crates-io]
bevy = { path = "../bevy" }
# specific crates as needed by the plugins you use (check their `Cargo.toml`)
bevy_ecs = { path = "../bevy/crates/bevy_ecs" }
bevy_math = { path = "../bevy/crates/bevy_math" }
# ... and so on

Updating Bevy

The Cargo.lock file keeps track of the exact version (including the git commit) you are working with. You will not be affected by new changes in upstream bevy, until you update manually.

To update, run:

cargo update

If you delete or lose the Cargo.lock file, cargo will have to regenerate it and update your bevy in the process. To prevent this, you should add it to your git repository along with your source code. Alternatively, you can be more explicit and require an exact git commit hash in your Cargo.toml:

bevy = { git = "https://github.com/bevyengine/bevy", rev = "7a1bd34e" }

Advice for plugin authors

If you are publishing a plugin for bevy, and you want to support bevy main, it is recommended that you:

  • Do it on a separate branch in your repository, to keep it separate from your main version for the released version of bevy.
  • Put information in your README to tell people how to find it.
  • Configure your Cargo.toml as shown above.
  • Set up CI to notify you if your plugin is broken by new changes in bevy.

Cargo.toml and dependencies

Publish your plugin with a Cargo.toml as shown on this page.

By specifying the released version of bevy, even in your bevy main tracking branch, you make life easier for your users. If they want to use a local clone, a specific commit, or their own fork (instead of the upstream repository), they can easily do it with a simple cargo patch in their project.

If you specify the bevy git repository directly in your dependencies, you are making such workflows more difficult.

You can safely include the cargo patch, too. It would apply when you are working on your plugin, so that you build against the correct version of bevy, but it would not affect your users, letting them use whatever bevy they want.

CI Setup

Here is an example for GitHub Actions. This will run at 8:00 AM (UTC) every day to verify that your code still compiles. GitHub will notify you when it fails.

name: check if code still compiles

on:
  schedule:
    - cron: '0 8 * * *'

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Install Dependencies
        run: sudo apt-get update && sudo apt-get install --no-install-recommends pkg-config libx11-dev libasound2-dev libudev-dev

      - uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          override: true

      - name: Check code
        run: cargo update && cargo check --lib --examples

Text Editor / IDE

This page contains tips for different text editors and IDEs.

Bevy is, for the most part, like any other Rust project. If your editor/IDE is set up for Rust, that might be all you need. This page contains additional information that may be useful for Bevy specifically.

If you have any suggestions for how to improve this page, file an issue on GitHub or ping me on Discord (@Ida Iyes#0981). Your help collecting useful information would be much appreciated! <3

CARGO_MANIFEST_DIR

When running your app/game, Bevy will search for the assets folder in the path specified in the CARGO_MANIFEST_DIR environment variable. This allows cargo run to work correctly from the terminal.

If you are using your editor/IDE to run your project in a non-standard way (say, inside a debugger), you have to be sure to set that correctly.

VSCode

Here is a snippet showing how to create a run configuration for debugging Bevy (with lldb):

(this is for development on Bevy itself, and testing with the breakout example)

(adapt to your needs if using for your project)

{
    "type": "lldb",
    "request": "launch",
    "name": "Debug example 'breakout'",
    "cargo": {
        "args": [
            "build",
            "--example=breakout",
            "--package=bevy"
        ],
        "filter": {
            "name": "breakout",
            "kind": "example"
        }
    },
    "args": [],
    "cwd": "${workspaceFolder}",
    "env": {
        "CARGO_MANIFEST_DIR": "${workspaceFolder}",
    }
}

Configuring Bevy

Bevy is very modular and configurable. It is implemented as many separate cargo crates, allowing you to remove the parts you don't need. Higher-level functionality is built on top of lower-level foundational crates, and can be disabled or replaced with alternatives.

The lower-level core crates (like the Bevy ECS) can also be used completely standalone, or integrated into otherwise non-Bevy projects.

Bevy Cargo Features

In Bevy projects, you can enable/disable various parts of Bevy using cargo features.

Here I will explain some of them and why you might want to change them.

Many common features are enabled by default. If you want to disable some of them, note that, unfortunately, Cargo does not let you disable individual default features, so you need to disable all default bevy features and re-enable the ones you need.

Here is how you might configure your Bevy:

[dependencies.bevy]
version = "0.5"
# Disable the default features if there are any that you do not want
default-features = false
features = [
  # These are the default features:
  # (keep whichever you like)
  "bevy_dynamic_plugin",
  "render",
  "bevy_wgpu",
  "bevy_winit",
  "bevy_gilrs",
  "bevy_gltf",
  "bevy_audio",
  "png",
  "hdr",
  "mp3",
  "x11",
  # These are other features that may be of interest:
  # (add any of these that you need)
  "bmp",
  "tga",
  "dds",
  "jpeg",
  "wav"
  "flac",
  "vorbis",
  "dynamic",
  "serialize",
  "trace",
  "wgpu_trace",
  "wayland"
]

(See here for a full list of Bevy's cargo features.)

Graphics / Rendering

For a standalone/desktop graphical application or game (most Bevy projects), you need render, bevy_winit, and bevy_wgpu.

If you don't need graphics (like for a dedicated game server, scientific simulation, etc.), you may remove these features.

For a web project (on bevy 0.5), remove bevy_wgpu, and instead use the bevy_webgl2 3rd-party plugin crate. This will no longer be required in the future Bevy 0.6 version.

There are also community-made alternative backend crates that could replace Bevy's default graphics, if you want that for any reason.

Audio

Bevy's audio is very limited in functionality and somewhat broken. It is recommended that you use the bevy_kira_audio plugin instead. Disable bevy_audio and mp3.

See this page for more information.

File Formats

You can use the relevant cargo features to enable/disable support for loading assets (images/textures and audio) with various different file formats.

See this page for more information.

If you do not need support for the GLTF file format for 3D models, you can disable bevy_gltf.

Input Devices

If you do not care about controller/joystick support, you can disable bevy_gilrs.

Linux Windowing Backend

On Linux, you can choose to support X11, Wayland, or both. Only x11 is enabled by default, as it is the legacy system that should be compatible with most/all distributions, to make your builds smaller and compile faster. You might want to additionally enable wayland, to fully and natively support modern Linux environments. This will add a few extra transitive dependencies to your project.

Development Features

While you are developing your project, these features might be useful:

Dynamic Linking

dynamic causes Bevy to be built and linked as a shared/dynamic library (*.so on Linux, *.dylib on macOS, *.DLL on Windows). This will make incremental builds much faster, which can reduce frustration when you are trying to test out changes to your code. On my machine, my projects recompile in ~2 sec without this option, and in ~0.5 sec with it enabled. This makes starting the game feel almost instant.

This is known to work very well on Linux, but you may encounter issues on other platforms. YMMV. I've heard people have issues on Windows.

Do not enable this for release builds you intend to publish to other people; it introduces unneeded complexity (you need to bundle extra files) and potential for things to not work correctly. Use this only during development.

For this reason, it may be convenient to specify the feature as a commandline option to cargo, instead of putting it in your Cargo.toml. Simply run your project like this:

cargo run --features bevy/dynamic

Tracing

The features trace and wgpu_trace may be useful for profiling and diagnosing performance issues.

Dev Tools and Editors for Bevy

Bevy does not yet have an official editor or other such tools. An official editor is planned as a long-term future goal. In the meantime, here are some community-made tools to help you.


Editor

bevy_inspector_egui gives you a simple editor-like property inspector window in-game. It lets you modify the values of your components and resources in real-time as the game is running.

bevy_editor_pls is an editor-like interface that you can embed into your game. It has even more features, like switching app states, fly camera, performance diagnostics, and inspector panels.

Diagnostics

bevy_mod_debugdump is a tool to help visualize your App Schedule (all of the registered systems with their ordering dependencies and stages), and the Bevy Render Graph.

bevy_lint is a linter (based on dylint) that can automatically check your Bevy code for some common issues.

If you are getting confusing/cryptic compiler error messages (like these) and you cannot figure them out, bevycheck is a tool you could use to help diagnose them. It tries to provide more user-friendly Bevy-specific error messages.

Using 3rd-party Plugins

There is a growing ecosystem of unofficial community-made plugins for Bevy, which provide a lot of functionality that is not officially included with the engine. For many kinds/genres of games, you might greatly benefit from using some of these.

To find such plugins, you should search the Bevy Assets page on the official Bevy website. This is the official registry of known community-made things for Bevy. If you publish your own plugins for Bevy, you should contribute a link to be added to that page.

Please beware that some 3rd-party plugins may use unusual licenses. Be sure to check the license before using a plugin in your project.


Other pages in this book with valuable information when using 3rd-party plugins:

Plugin Recommendations

This here is my personal, curated, opinionated list of recommendations, featuring the most important plugins (in my opinion) in the Bevy ecosystem.

My goal here is to help direct new Bevy users to some known-good resources, so you can start working on the kinds of games you want to make. :)

The plugins listed here are compatible with the latest Bevy release and use permissive licenses (like Bevy itself).

This page is very limited. I can only recommend plugins I know enough about. Please also check the Bevy Assets page to find even more things. :)

Development Tools and Editors

These are listed on a separate page.

Code Helpers

bevy_loading is a helper for state transitions. It lets you register systems that report their progress, tracks the progress, and transitions to the next state when they are all ready. Most useful for loading screens, but can be used more generally. Can also track the loading of assets.

bevy_asset_loader is a more flexible and opinionated helper for managing and loading game assets. Uses custom annotations to let you declare your assets more conveniently.

Audio

Use bevy_kira_audio instead of the built-in bevy_audio.

The built-in audio is very limited in features, and you are likely going to need this plugin for pretty much any game with audio.

See this page for help on how to set it up.

Camera

bevy_config_cam is a nice plugin for easily adding camera controls to your Bevy 3D project. It gives you a a choice of various common camera behaviors (like follow, top-view, FPS-style, free-roaming).

Cameras are something that can be very game-specific. As you progress with your project, you would probably want to implement your own custom camera control logic for your game. However, this plugin is amazing when you are starting out on a new project.

Tilemap

If you are making a 2D game based on a tile-map, there are plugins to help do it efficiently with high performance. It is better to use one of these plugins, instead of just spawning lots of individual Bevy Sprites for each tile.

  • bevy_ecs_tilemap
    • Uses one ECS Entity per tile, with efficient custom rendering.
    • Lets you work with the tilemap in an ECS-idiomatic way.
    • Can be a little complex to set up / configure / spawn the tilemap.
    • Lots of features: Square/Hexagon/Isometric grids, animation, layers, chunks, optional ldtk/tiled support, ...
  • bevy_tilemap
    • Another feature-rich plugin, but this one is not ECS-idiomatic (the whole map is one entity).
    • Designed to work well for infinite/endless or dynamically-generated maps.
    • API is quite refined and stable, has good documentation.
  • bevy_simple_tilemap
    • Limited in features, easy to use if you just need to efficiently render a grid of square tiles.

Shapes / Vector Graphics / Canvas

If you want to draw 2D shapes, use the bevy_prototype_lyon plugin.

Game AI

big-brain is a fantastic plugin for game AI behaviors (Utility AI).

GUI

If you want an alternative to Bevy UI, see bevy_egui. This integrates the egui toolkit into Bevy.

It is an immediate-mode GUI library (like the popular Dear Imgui, but in Rust).

It is very feature-rich and contains lots of widgets.

It was not really designed for making flashy gamey UIs (though it may very well be fine for your game). It's great for editor-like UIs, debug UIs, or non-game applications.

Physics

Bevy can integrate with the Rapier physics engine.

There are two plugins you can choose from:

  • bevy_rapier
    • Maintained officially by the Rapier project developers.
    • This is a "raw" plugin that gives you direct access to Rapier.
    • Gives you the most control, but is hard to use and not idiomatic-Bevy.
    • You will probably need to read a lot of documentation, hard to learn.
  • heron
    • An attempt to make a plugin that is more idiomatic to Bevy. More opinionated.
    • Likely to be easier to use and more intuitive than bevy_rapier.
    • May have more limited functionality.

Animation

For simple "smooth motion" (easing/tweening/interpolation), try bevy_easings. This might be good enough for moving 2D objects around, moving the camera, or other such transitions.

For animating 2D sprites, try benimator.

For 3D skeletal animation, unfortunately, there do not seem to be plugins yet.

Also, a long time ago, there was this PR with an attempt to contribute a full-featured animation system to Bevy. To my knowledge, it has not (yet) been made available as a separate plugin.

Introduction to Bevy Programming

Overview of programming in Bevy.

Includes concise explanations of each core concept, with code snippets to show how it might be used in a game. Care is taken to point out any important considerations for using each feature and to recommend known good practices.

Intended for people learning how to use Bevy.

This chapter is split into three sections:

  • Basics: Covers the absolute essentials you need to get started.
  • Next Steps: The next steps. Things you will likely need for non-trivial projects.
  • Advanced: advanced topics, may be useful for specialized scenarios in complex projects.

Bevy Programming: Basics

This sub-chapter covers the absolute essentials of Bevy programming -- the minimum concepts you need to get started.

Every Bevy project, no matter how simple, requires you to be familiar with these concepts.

You could conceivably make something like a simple game-jam game or prototype using just the concepts presented in this sub-chapter.

As your project grows, you will likely quickly run into the need for some of the topics in the following Next Steps sub-chapter.

ECS as a Data Structure

Relevant official examples: ecs_guide.


Bevy stores and manages all your data for you, using the Bevy ECS (Entity-Component System).

Conceptually, you can think of it by analogy with tables, like in a database or spreadsheet. Your different data types (Components) are like the "columns" of a table, and there can be arbitrarily many "rows" (Entities) containing values / instances of each component.

For example, you could create a Health component for your game. You could then have many entities representing different things in your game, such as the player, NPCs, or monsters, all of which can have a Health value (as well as other relevant components).

This makes it easy to write game logic (Systems) that can operate on any entity with the necessary components (such as a health/damage system for anything that has Health), regardless of whether that's the player, an NPC, or a monster (or anything else). This makes your game logic very flexible and reusable.

The set / combination of components that a given entity has, is called the entity's Archetype.

Note that entities aren't limited to just "objects in the game world". The ECS is a general-purpose data structure. You can create entities and components to store any data.

Performance

Bevy has a smart scheduling algorithm that runs your systems in parallel as much as possible. It does that automatically, when your functions don't require conflicting access to the same data. Your game will scale to run on multiple CPU cores "for free"; that is, without requiring extra development effort from you.

To improve the chances for parallelism, you can make your data and code more granular. Split your data into smaller types / structs. Split your logic into multiple smaller systems / functions. Have each system access only the data that is relevant to it. The fewer access conflicts, the faster your game will run.

The general rule of thumb for Bevy performance is: more granular is better.

Note for Programmers coming from Object-Oriented Languages

You may be used to thinking in terms of "object classes". For example, you might be tempted to define a big monolithic struct Player containing all the fields / properties of the player.

In Bevy, this is considered bad practice, because doing it that way can make it more difficult to work with your data, and limit performance.

Instead, you should make things granular, when different pieces of data may be accessed independently.

For example, represent the Player in your game as an entity, composed of separate component types (separate structs) for things like the health, XP, or whatever is relevant to your game. You can also attach standard Bevy components like Transform to it.

This will make it easier for you to develop your systems (game logic / behaviors), as well as make your game's runtime performance better.

However, something like a Transform, or a set of coordinates, still makes sense as a single struct, because its fields are not likely to be useful independently.

Relevant official examples: ecs_guide.


Entities

Entities are just a simple integer ID, that identifies a particular set of component values.

Components

Components are the data associated with entities.

Any Rust type (struct or enum) can be used as a component.

struct Health {
    hp: f32,
    extra: f32,
}

Types must be unique -- an entity can only have one component per Rust type.

Use wrapper (newtype) structs to make unique components out of simpler types:

struct PlayerXp(u32);

struct PlayerName(String);

You can use empty structs to mark specific entities. These are known as "marker components". Useful with query filters.

/// Add this to all menu ui entities to help identify them
struct MainMenuUI;
/// Marker to help identify the player
struct Player;
/// Marker for hostiles
struct Enemy;

Components can be accessed from systems, using queries.

Component Bundles

Bundles are like "templates", to make it easy to create entities with a common set of components.

#[derive(Bundle)]
struct PlayerBundle {
    xp: PlayerXp,
    name: PlayerName,
    health: Health,
    _p: Player,

    // We can nest/include another bundle.
    // Add the components for a standard Bevy Sprite:
    #[bundle]
    sprite: SpriteSheetBundle,
}

Bevy also considers arbitrary tuples of components as bundles:

(ComponentA, ComponentB, ComponentC)

Common Pitfalls

Because both bundles and individual components are regular Rust structs, Bevy / the Rust compiler often has no way to distinguish them.

If you accidentally use a bundle struct somewhere where Bevy expects a component, you will not get an error. Bevy will just treat it as a component of that struct type!

For example, this is why we needed the #[bundle] annotation to include a sub-bundle in the example above.

Other places where this issue may come up are: when using Commands, and when querying (queries only work with components!).

Resources

Relevant official examples: ecs_guide.


Resources allow you to store a single global instance of some data type, independently of entities.

Use them for data that is truly global for your app, such as configuration / settings.

Similar to components, any Rust type (struct or enum) can be used as a resource.

Types must be unique; there can only be one instance of a given type.

struct GoalsReached {
    main_goal: bool,
    bonus: bool,
}

Resources can be accessed from systems, using Res/ResMut.

Resource Initialization

Implement Default for simple resources:

#[derive(Default)]
struct StartingLevel(usize);

For resources that need complex initialization, implement FromWorld:

struct MyFancyResource { /* stuff */ }

impl FromWorld for MyFancyResource {
    fn from_world(world: &mut World) -> Self {
        // You have full access to anything in the ECS from here.
        // For instance, you can mutate other resources:
        let mut x = world.get_resource_mut::<MyOtherResource>().unwrap();
        x.do_mut_stuff();

        MyFancyResource { /* stuff */ }
    }
}

You can create your resources at App creation:

fn main() {
    App::build()
        // ...

        // if it implements `Default` or `FromWorld`
        .init_resource::<MyFancyResource>()
        // if not, or if you want to set a specific value
        .insert_resource(StartingLevel(3))

        // ...
        .run();
}

Or using Commands from inside a system:

commands.insert_resource(GoalsReached { main_goal: false, bonus: false });
commands.remove_resource::<MyResource>();

If you insert a resource of a type that already exists, it will be overwritten.

Usage Advice

The choice of when to use entities/components vs. resources is typically about how you want to access the data: globally from anywhere (resources), or using ECS patterns (entities/components).

Even if there is only one of a certain thing in your game (such as the player in a single-player game), it can be a good fit to use an entity instead of resources, because entities are composed of multiple components, some of which can be common with other entities. This can make your game logic more flexible. For example, you could have a "health/damage system" that works with both the player and enemies.

Systems

Relevant official examples: ecs_guide, startup_system, system_param.


Systems are functions you write, which are run by Bevy.

This is where you implement all your game logic.

These functions can only take special parameter types, to specify what you need access to. If you use unsupported parameter types in your function, you will get confusing compiler errors!

Some of the options are:

fn debug_start(
    // access resource
    start: Res<StartingLevel>
) {
    eprintln!("Starting on level {:?}", *start);
}

System parameters can be grouped into tuples (which can be nested). This is useful for organization.

fn complex_system(
    (a, mut b): (Res<ResourceA>, ResMut<ResourceB>),
    // this resource might not exist, so wrap it in an Option
    mut c: Option<ResMut<ResourceC>>,
) {
    if let Some(mut c) = c {
        // do something
    }
}

The maximum number of top-level system parameters, or tuple members, is 16. You can group/nest them into tuples, if you need to work around that limitation.

Runtime

To run your systems, you need to add them to Bevy via the app builder:

fn main() {
    App::build()
        // ...

        // run it only once at launch
        .add_startup_system(init_menu.system())
        .add_startup_system(debug_start.system())

        // run it every frame update
        .add_system(move_player.system())
        .add_system(enemies_ai.system())

        // ...
        .run();
}

The above is enough for simple projects.

As your project grows more complex, you might want to enhance your app builder with some of the powerful tools that Bevy offers for managing when/how your systems run, such as: explicit ordering with labels, system sets, states, run criteria, and stages.

Queries

Relevant official examples: ecs_guide.


Queries let you access components of entities.

fn check_zero_health(
    // access entities that have `Health` and `Transform` components
    // get read-only access to `Health` and mutable access to `Transform`
    // optional component: get access to `Player` if it exists
    mut query: Query<(&Health, &mut Transform, Option<&Player>)>,
) {
    // get all matching entities
    for (health, mut transform, player) in query.iter_mut() {
        eprintln!("Entity at {} has {} HP.", transform.translation, health.hp);

        // center if hp is zero
        if health.hp <= 0.0 {
            transform.translation = Vec3::ZERO;
        }

        if let Some(player) = player {
            // the current entity is the player!
            // do something special!
        }
    }
}

Get the components associated with a specific entity:

    if let Ok((health, mut transform)) = query.get_mut(entity) {
        // do something with the components
    } else {
        // the entity does not have the components from the query
    }

Get the IDs of the entities you access with your queries:

// add `Entity` to `Query` to get Entity IDs
fn query_entities(q: Query<(Entity, /* ... */)>) {
    for (e, /* ... */) in q.iter() {
        // `e` is the Entity ID of the entity we are accessing
    }
}

If you know that the query should only ever match a single entity, you can use single/single_mut (returns a Result), instead of iterating:

fn query_player(mut q: Query<(&Player, &mut Transform)>) {
    let (player, mut transform) = q.single_mut()
        .expect("There should always be exactly one player in the game!");

    // do something with the player and its transform
}

Bundles

Queries work with individual components. If you created an entity using a bundle, you need to query for the specific components from that bundle that you care about.

A common beginner mistake is to query for the bundle type!

Query Filters

Add query filters to narrow down the entities you get from the query.

Use With/Without to only get entities that have specific components.

fn debug_player_hp(
    // access the health, only for friendly players, optionally with name
    query: Query<(&Health, Option<&PlayerName>), (With<Player>, Without<Enemy>)>,
) {
    // get all matching entities
    for (health, name) in query.iter() {
        if let Some(name) = name {
            eprintln!("Player {} has {} HP.", name.0, health.hp);
        } else {
            eprintln!("Unknown player has {} HP.", health.hp);
        }
    }
}

Multiple filters can be combined:

  • in a tuple to apply all of them (AND logic)
  • using the Or<(...)> wrapper to detect any of them (OR logic).
    • (note the tuple inside)

Commands

Relevant official examples: ecs_guide.


Use Commands to spawn/despawn entities, add/remove components on existing entities, manage resources.

These actions do not take effect immediately; they are queued to be performed later when it is safe to do so. See: stages.

(if you are not using stages, that means your systems will see them on the next frame update)

fn spawn_player(
    mut commands: Commands,
) {
    // manage resources
    commands.insert_resource(GoalsReached { main_goal: false, bonus: false });
    commands.remove_resource::<MyResource>();

    // create a new entity using `spawn`
    let entity_id = commands.spawn()
        // add a component
        .insert(ComponentA)
        // add a bundle
        .insert_bundle(MyBundle::default())
        // get the Entity ID
        .id();

    // shorthand for creating an entity with a bundle
    commands.spawn_bundle(PlayerBundle {
        name: PlayerName("Henry".into()),
        xp: PlayerXp(1000),
        health: Health {
            hp: 100.0, extra: 20.0
        },
        _p: Player,
        sprite: Default::default(),
    });

    // spawn another entity
    // NOTE: tuples of arbitrary components are valid bundles
    let other = commands.spawn_bundle((
        ComponentA::default(),
        ComponentB::default(),
        ComponentC::default(),
    )).id();

    // add/remove components of an existing entity
    commands.entity(entity_id)
        .insert(ComponentB)
        .remove::<ComponentA>()
        .remove_bundle::<MyBundle>();

    // despawn an entity
    commands.entity(other).despawn();
}

fn make_all_players_hostile(
    mut commands: Commands,
    query: Query<Entity, With<Player>>,
) {
    for entity in query.iter() {
        // add an `Enemy` component to the entity
        commands.entity(entity).insert(Enemy);
    }
}

Be careful not to confuse components and bundles. For example: .insert_bundle is correct: it will add all the components from the bundle; if you instead use .insert with a bundle type, the bundle struct will be added as a single component!

Events

Relevant official examples: event.


Send data between systems! Let your systems communicate with each other!

To send events, use an EventWriter<T>. To receive events, use an EventReader<T>.

Every reader tracks the events it has read independently, so you can handle the same events from multiple systems.

struct LevelUpEvent(Entity);

fn player_level_up(
    mut ev_levelup: EventWriter<LevelUpEvent>,
    query: Query<(Entity, &PlayerXp)>,
) {
    for (entity, xp) in query.iter() {
        if xp.0 > 1000 {
            ev_levelup.send(LevelUpEvent(entity));
        }
    }
}

fn debug_levelups(
    mut ev_levelup: EventReader<LevelUpEvent>,
) {
    for ev in ev_levelup.iter() {
        eprintln!("Entity {:?} leveled up!", ev.0);
    }
}

You need to add your custom event types via the app builder:

fn main() {
    App::build()
        // ...
        .add_event::<LevelUpEvent>()
        .add_system(player_level_up.system())
        .add_system(debug_levelups.system())
        // ...
        .run();
}

Events should be your go-to data flow tool. As events can be sent from any system and received by multiple systems, they are extremely versatile.

Possible Pitfalls

Beware of frame delay / 1-frame-lag. This can occur if Bevy runs the receiving system before the sending system. The receiving system will only get a chance to receive the events on the next frame update. If you need to ensure that events are handled immediately / during the same frame, you can use explicit system ordering.

Events don't persist. They are stored until the end of the next frame, after which they are lost. If your systems do not handle events every frame, you could miss some.

The advantage of this design is that you don't have to worry about excessive memory use from unhandled events.

If you don't like this, you can have manual control over when events are cleared (at the risk of leaking / wasting memory if you forget to clear them).

App Builder (main function)

Relevant official examples: ecs_guide, hello_world, empty, empty_defaults, headless.


To enter the bevy runtime, you need to configure an App, composed of all the systems, plugins, event types, and resources, that you want to use.

Everything must be registered in the App, or it will not work.

Component types and local resources do not need to be registered.

Resources can also be created later, using Commands.

You also need to add Bevy's built-in functionality: either DefaultPlugins if you are making a full game/app, or MinimalPlugins for something like a headless server.

fn main() {
    App::build()
        // bevy
        .add_plugins(DefaultPlugins)

        // resources:
        .insert_resource(StartingLevel(3))
        // if it implements `Default` or `FromWorld`
        .init_resource::<MyFancyResource>()

        // events:
        .add_event::<LevelUpEvent>()

        // systems to run once at startup:
        .add_startup_system(spawn_player.system())

        // systems to run each frame:
        .add_system(player_level_up.system())
        .add_system(debug_levelups.system())
        .add_system(debug_stats_change.system())
        // ...

        // launch the app!
        .run();
}

Bevy Programming: Next Steps

This sub-chapter covers aspects of Bevy programming beyond the absolute basics.

You could get started building simple things with Bevy before you have familiarized yourself with these concepts.

However, they would probably become useful to you pretty quickly -- you will likely need at least some of these features as your project grows in complexity.

Local Resources

Relevant official examples: ecs_guide.


Local<T> is a system parameter similar to ResMut<T>, which gives you full mutable access to an instance of some data type, that is independent from entities and components.

Res<T>/ResMut<T> refer to a single global instance of the type, shared between all systems. On the other hand, every Local<T> parameter is a separate instance, exclusively for that system.

#[derive(Default)]
struct MyState;

fn my_system1(mut local: Local<MyState>) {
    // you can do anything you want with the local here
}

fn my_system2(mut local: Local<MyState>) {
    // the local in this system is a different instance
}

The type must implement Default or FromWorld. It is automatically initialized.

A system can have multiple Locals of the same type.

Specify an initial value

You can initialize a Local to a value other than the type's default, using .config, when adding your system to your App.

.config is Bevy's API for "configuring" specific system parameters. Most other types of system parameters do not support configuration, but Locals let you specify the initial value.

/// Configuration for `my_system`.
///
/// The system will access it using `Local<MyConfig>`.
/// It will be initialized with the correct value at App build time.
///
/// Must still impl `Default`, because of requirement for `Local`.
#[derive(Default)]
struct MyConfig {
    magic: usize,
}

fn my_system(
    mut cmd: Commands,
    my_res: Res<MyStuff>,
    config: Local<MyConfig>,
) {
    // TODO: do stuff
}

fn main() {
    App::build()
        .add_system(my_system.system().config(|params| {
            // our config is the third parameter in the system fn,
            // hence `.2`
            params.2 = Some(MyConfig {
                magic: 420,
            });
        }))
        .run();
}

Plugins

Relevant official examples: plugin, plugin_group.


As your project grows, it can be useful to make it more modular. You can split it into plugins.

Plugins are simply collections of things to be added to the App Builder.

struct MyPlugin;

impl Plugin for MyPlugin {
    fn build(&self, app: &mut AppBuilder) {
        app
            .init_resource::<MyOtherResource>()
            .add_event::<MyEvent>()
            .add_startup_system(plugin_init.system())
            .add_system(my_system.system());
    }
}

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_plugin(MyPlugin)
        .run();
}

For internal organization in your own project, the main value of plugins comes from not having to declare all your Rust types and functions as pub, just so they can be accessible from main to be added to the app builder. Plugins let you add things to your app from multiple different places, like separate Rust files / modules.

You can decide how plugins fit into the architecture of your game.

Some suggestions:

  • Create plugins for different states.
  • Create plugins for various sub-systems, like physics or input handling.

Plugin groups

Plugin groups register multiple plugins at once. Bevy's DefaultPlugins and MinimalPlugins are examples of this. To create your own plugin group:

struct MyPluginGroup;

impl PluginGroup for MyPluginGroup {
    fn build(&mut self, group: &mut PluginGroupBuilder) {
        group
            .add(FooPlugin)
            .add(BarPlugin);
    }
}

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_plugins(MyPluginGroup)
        .run();
}

When adding a plugin group, you can disable some plugins while keeping the rest.

For example, if you want to manually set up logging (with your own tracing subscriber), you can disable LogPlugin:

App::build()
    .add_plugins_with(DefaultPlugins, |plugins| {
        plugins.disable::<LogPlugin>()
    })
    .run();

Note that this simply disables the functionality, but it cannot actually remove the code to avoid binary bloat. The disabled plugins still have to be compiled into your program.

If you want to slim down your build, you should look at disabling Bevy's default cargo features, or depending on the various Bevy sub-crates individually.

Publishing Crates

Plugins give you a nice way to publish Bevy-based libraries for other people to easily include into their projects.

If you intend to publish plugins as crates for public use, you should read the official guidelines for plugin authors.

Don't forget to submit an entry to Bevy Assets on the official website, so that people can find your plugin more easily. You can do this by making a PR in the Github repo.

If you are interested in supporting bleeding-edge Bevy (main), see here for advice.

System Order of Execution

Bevy's scheduling algorithm is designed to deliver maximum performance by running as many systems as possible in parallel across the available CPU threads.

This is possible when the systems do not conflict over the data they need to access. However, when a system needs to have mutable (exclusive) access to a piece of data, other systems that need to access the same data cannot be run at the same time. Bevy determines all of this information from the system's function signature (the types of the parameters it takes).

In such situations, the order is nondeterministic by default. Bevy takes no regard for when each system will run, and the order could even change every frame!

Does it even matter?

In many cases, you don't need to worry about this.

However, sometimes you need to rely on specific systems to run in a particular order. For example:

  • Maybe the logic you wrote in one of your systems needs any modifications done to that data by another system to always happen first?
  • One system needs to receive events sent by another system.
  • You are using change detection.

In such situations, systems running in the wrong order typically causes their behavior to be delayed until the next frame. In rare cases, depending on your game logic, it may even result in more serious logic bugs!

It is up to you to decide if this is important.

With many things in typical games, such as juicy visual effects, it probably doesn't matter if they get delayed by a frame. It might not be worthwhile to bother with it. If you don't care, leaving the order ambiguous may also result in better performance.

On the other hand, for things like handling the player input controls, this would result in annoying lag, so you should probably fix it.

Explicit System Ordering

The solution is to use system labels to explicitly specify the order you want:

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)

        // order doesn't matter for these systems:
        .add_system(particle_effects.system())
        .add_system(npc_behaviors.system())
        .add_system(enemy_movement.system())

        // create labels, because we need to order other systems around these:
        .add_system(map_player_input.system().label("input"))
        .add_system(update_map.system().label("map"))

        // this will always run before anything labeled "input"
        .add_system(input_parameters.system().before("input"))

        // this will always run after anything labeled "input" and "map"
        // also label it just in case
        .add_system(
            player_movement.system()
                .label("player_movement")
                .after("input")
                .after("map")
        )
        .run();
}

Each label is a reference point that other systems can be ordered around.

.label/.before/.after may be used as many times as you need on one system.

You can place multiple labels on one system.

You can also use the same label on multiple systems.

When you have multiple systems with common labels or ordering, it may be convenient to use system sets.

Circular Dependencies

If you have multiple systems mutually depending on each other, then it is clearly impossible to resolve the situation completely like that.

You should try to redesign your game to avoid such situations, or just accept the consequences. You can at least make it behave predictably, using explicit ordering to specify the order you prefer.

System Sets

System Sets allow you to easily apply common properties to multiple systems, for purposes such as labeling, ordering, run criteria, and states.

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)

        // group our input handling systems into a set
        .add_system_set(
            SystemSet::new()
                .label("input")
                .with_system(keyboard_input.system())
                .with_system(gamepad_input.system())
        )

        // our "net" systems should run before "input"
        .add_system_set(
            SystemSet::new()
                .label("net")
                .before("input")
                // individual systems can still have
                // their own labels (and ordering)
                .with_system(server_session.system().label("session"))
                .with_system(server_updates.system().after("session"))
        )

        // some ungrouped systems
        .add_system(player_movement.system().after("input"))
        .add_system(session_ui.system().after("session"))
        .add_system(smoke_particles.system())

        .run();
}

Hierarchical (Parent/Child) Entities

Relevant official examples: hierarchy, parenting.


Technically, the Entities/Components themselves cannot form a hierarchy (it is a flat data structure). However, logical hierarchies are a common pattern in games.

Bevy supports creating such a logical link between entities, to form a virtual "hierarchy", by simply adding Parent and Children components on the respective entities.

Commands has methods for adding children to entities, which automatically add the correct components:

// spawn the parent and get its Entity id
let parent = commands.spawn_bundle(MyParentBundle::default())
    .id();

// do the same for the child
let child = commands.spawn_bundle(MyChildBundle::default())
    .id();

// add the child to the parent
commands.entity(parent).push_children(&[child]);

// you can also use `with_children`:
commands.spawn_bundle(MyParentBundle::default())
    .with_children(|parent| {
        parent.spawn_bundle(MyChildBundle::default());
    });

You can despawn an entire hierarchy with a single command:

fn close_menu(
    mut commands: Commands,
    query: Query<Entity, With<MainMenuUI>>,
) {
    for entity in query.iter() {
        // despawn the entity and its children
        commands.entity(entity).despawn_recursive();
    }
}

Accessing the Parent or Children

To make a system that works with the hierarchy, you typically need two queries:

  • one with the components you need from the child entities
  • one with the components you need from the parent entities

One of the two queries should include the appropriate component, to obtain the entity ids to use with the other one:

  • Parent in the child query, if you want to iterate entities and look up their parents, or
  • Children in the parent query, if you want to iterate entities and look up their children

For example, if we want to get the Transform of cameras that have a parent, and the GlobalTransform of their parent:

fn camera_with_parent(
    q_child: Query<(&Parent, &Transform), With<Camera>>,
    q_parent: Query<&GlobalTransform>,
) {
    for (parent, child_transform) in q_child.iter() {
        // `parent` contains the Entity ID we can use
        // to query components from the parent:
        let parent_global_transform = q_parent.get(parent.0);

        // do something with the components
    }
}

As another example, say we are making a strategy game, and we have Units that are children of a Squad. Say we need to make a system that works on each Squad, and it needs some information about the children:

fn process_squad_damage(
    q_parent: Query<(&MySquadDamage, &Children)>,
    q_child: Query<&MyUnitHealth>,
) {
    // get the properties of each squad
    for (squad_dmg, children) in q_parent.iter() {
        // `children` is a collection of Entity IDs
        for &child in children.iter() {
            // get the health of each child unit
            let health = q_child.get(child);

            // do something
        }
    }
}

Relative Transforms

If your entities represent "objects in the game world", you probably expect the child to be positioned relative to the parent and move with it.

All Bundles that come with Bevy provide this behavior automatically.

If you are not using such a bundle, you need to make sure to add these components to both the parent and the child: GlobalTransform and Transform.

The Transform represents the relative position. You can manipulate it directly.

The GlobalTransform represents the absolute position. It is managed by bevy internally; do not manipulate it yourself.

For more info, see the dedicated page about transforms.

Change Detection

Relevant official examples: change_detection.


Bevy allows you to easily detect when data is changed. You can use this to perform actions in response to changes.

Components

Use query filters:

  • Added<T>: detect new component instances
    • if the component was added to an existing entity
    • if a new entity with the component was spawned
  • Changed<T>: detect component instances that have been changed
    • triggers when the component is accessed mutably
    • also triggers if the component is newly-added (as per Added)

(If you want to react to removals, see the page on removal detection. It works differently and is much trickier to use.)

/// Print the stats of friendly players when they change
fn debug_stats_change(
    query: Query<
        // components
        (&Health, &PlayerXp),
        // filters
        (Without<Enemy>, Or<(Changed<Health>, Changed<PlayerXp>)>), 
    >,
) {
    for (health, xp) in query.iter() {
        eprintln!(
            "hp: {}+{}, xp: {}",
            health.hp, health.extra, xp.0
        );
    }
}

/// detect new enemies and print their health
fn debug_new_hostiles(
    query: Query<(Entity, &Health), Added<Enemy>>,
) {
    for (entity, health) in query.iter() {
        eprintln!("Entity {:?} is now an enemy! HP: {}", entity, health.hp);
    }
}

Changed detection is triggered by DerefMut. Simply accessing components via a mutable query, without actually performing a &mut access, will not trigger it.

This makes change detection quite accurate. You can rely on it to optimize your game's performance, or to otherwise trigger things to happen.

Also note that when you mutate a component, Bevy does not track if the new value is actually different from the old value. It will always trigger the change detection. If you want to avoid that, simply check it yourself:

fn update_player_xp(
    mut query: Query<&mut PlayerXp>,
) {
    for mut xp in query.iter_mut() {
        let new_xp = maybe_lvl_up(&xp);

        // avoid triggering change detection if the value is the same
        if new_xp != *xp {
            *xp = new_xp;
        }
    }
}

Change detection is reliable -- it will detect any changes that have occured since the last time your detecting system ran. If your system only runs sometimes (such as with states or run criteria), you do not have to worry about missing changes.

Resources

For resources, change detection is provided via methods on the Res/ResMut system parameters.

fn check_res_changed(
    my_res: Res<MyResource>,
) {
    if my_res.is_changed() {
        // do something
    }
}

fn check_res_added(
    // use Option, not to panic if the resource doesn't exist yet
    my_res: Option<Res<MyResource>>,
) {
    if let Some(my_res) = my_res {
        // the resource exists

        if my_res.is_added() {
            // it was just added
            // do something
        }
    }
}

This works the same way as change detection for components.

Possible Pitfalls

Beware of frame delay / 1-frame-lag. This can occur if Bevy runs the detecting system before the changing system. The detecting system will see the change the next time it runs, typically on the next frame update.

If you need to ensure that changes are handled immediately / during the same frame, you can use explicit system ordering.

However, when detecting component additions with Added<T> (which are typically done using Commands), this is not enough; you need stages.

System Chaining

Relevant official examples: system_chaining.


Systems can take an input and produce an output, and be connected together to run as a single larger system (chain).

Think of this as "glue", for constructing a larger system out of multiple Rust functions.

One useful application is to be able to return errors from systems (allowing the use of Rust's ? operator) and handle them elsewhere:

fn net_receive(mut netcode: ResMut<MyNetProto>) -> std::io::Result<()> {
    netcode.receive_updates()?;

    Ok(())
}

fn handle_io_errors(In(result): In<std::io::Result<()>>) {
    if let Err(e) = result {
        eprintln!("I/O error occurred: {}", e);
    }
}

Such systems cannot be registered individually (Bevy doesn't know what to do with the input/output). You have to connect them in a chain:

fn main() {
    App::build()
        // ...
        .add_system(net_receive.system().chain(handle_io_errors.system()))
        // ...
        .run();
}

Chaining effectively constructs a single large system out of modular parts. It is not a channel for passing data around. If you want to pass data between systems, you probably want to use Events instead.

Performance Warning

Beware that Bevy treats the whole chain as if it was a single big system, with all the combined resources and queries. This implies that parallelism could be limited, affecting performance.

Avoid adding a system that requires mutable access to anything, as part of multiple chains. It would block all affected chains (and other systems accessing the same data) from running in parallel.

Query Sets

For safety reasons, a system cannot have multiple queries with mutability conflicts on the same components.

Bevy provides a solution: wrap them in a QuerySet:

fn reset_health(
    // access the health of enemies and the health of players
    // (note: some entities could be both!)
    mut q: QuerySet<(
        Query<&mut Health, With<Enemy>>,
        Query<&mut Health, With<Player>>
    )>,
) {
    // set health of enemies
    for mut health in q.q0_mut().iter_mut() {
        health.hp = 50.0;
    }

    // set health of players
    for mut health in q.q1_mut().iter_mut() {
        health.hp = 100.0;
    }
}

This ensures only one of the conflicting queries can be used at the same time.

The maximum number of queries in a query set is 4.

States

Relevant official examples: state.


States allow you to structure the runtime "flow" of your app.

This is how you can implement things like:

  • A menu screen or a loading screen
  • Pausing / unpausing the game
  • Different game modes
  • ...

In every state, you can have different systems running. You can also add one-shot setup and cleanup systems to run when entering or exiting a state.

To use states, define an enum type and add system sets to your app builder:

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum AppState {
    MainMenu,
    InGame,
    Paused,
}

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)

        // add the app state type
        .add_state(AppState::MainMenu)

        // add systems to run regardless of state, as usual
        .add_system(play_music.system())

        // systems to run only in the main menu
        .add_system_set(
            SystemSet::on_update(AppState::MainMenu)
                .with_system(handle_ui_buttons.system())
        )

        // setup when entering the state
        .add_system_set(
            SystemSet::on_enter(AppState::MainMenu)
                .with_system(setup_menu.system())
        )

        // cleanup when exiting the state
        .add_system_set(
            SystemSet::on_exit(AppState::MainMenu)
                .with_system(close_menu.system())
        )
        .run();
}

It is OK to have multiple system sets for the same state.

This is useful when you want to place labels and use explicit system ordering.

This can also be useful with Plugins. Each plugin can add its own set of systems to the same state.

States are implemented using run criteria under the hood. These special system set constructors are really just helpers to automatically add the state management run criteria.

Controlling States

Inside of systems, you can check and control the state using the State<T> resource:

fn play_music(
    app_state: Res<State<AppState>>,
    // ...
) {
    match app_state.current() {
        AppState::MainMenu => {
            // TODO: play menu music
        }
        AppState::InGame => {
            // TODO: play game music
        }
        AppState::Paused => {
            // TODO: play pause screen music
        }
    }
}

To change to another state:

fn enter_game(mut app_state: ResMut<State<AppState>>) {
    app_state.set(AppState::InGame).unwrap();
    // ^ this can fail if we are already in the target state
    // or if another state change is already queued
}

After the systems of the current state complete, Bevy will transition to the next state you set.

You can do arbitrarily many state transitions in a single frame update. Bevy will handle all of them and execute all the relevant systems (before moving on to the next stage).

State Stack

Instead of completely transitioning from one state to another, you can also overlay states, forming a stack.

This is how you can implement things like a "game paused" screen, or an overlay menu, with the game world still visible / running in the background.

You can have some systems that are still running even when the state is "inactive" (that is, in the background, with other states running on top). You can also add one-shot systems to run when "pausing" or "resuming" the state.

In your app builder:

        // player movement only when actively playing
        .add_system_set(
            SystemSet::on_update(AppState::InGame)
                .with_system(player_movement.system())
        )
        // player idle animation while paused
        .add_system_set(
            SystemSet::on_inactive_update(AppState::InGame)
                .with_system(player_idle.system())
        )
        // animations both while paused and while active
        .add_system_set(
            SystemSet::on_in_stack_update(AppState::InGame)
                .with_system(animate_trees.system())
                .with_system(animate_water.system())
        )
        // things to do when becoming inactive
        .add_system_set(
            SystemSet::on_pause(AppState::InGame)
                .with_system(hide_enemies.system())
        )
        // things to do when becoming active again
        .add_system_set(
            SystemSet::on_resume(AppState::InGame)
                .with_system(reset_player.system())
        )
        // setup when first entering the game
        .add_system_set(
            SystemSet::on_enter(AppState::InGame)
                .with_system(setup_player.system())
                .with_system(setup_map.system())
        )
        // cleanup when finally exiting the game
        .add_system_set(
            SystemSet::on_exit(AppState::InGame)
                .with_system(despawn_player.system())
                .with_system(despawn_map.system())
        )

To manage states like this, use push/pop:

    // to go into the pause screen
    app_state.push(AppState::Paused).unwrap();
    // to go back into the game
    app_state.pop().unwrap();

(using .set as shown before replaces the active state at the top of the stack)

Known Pitfalls and Limitations

Events

When receiving events in systems that don't run all the time, such as during a pause state, you will miss any events that are sent during the frames when the receiving systems are not running!

To mitigate this, you could implement a custom cleanup strategy, to manually manage the lifetime of the relevant event types.

Combining with Other Run Criteria

Because states are implemented using run criteria, it is tricky to combine them with other uses of run criteria, such as fixed timestep.

If you try to add another run criteria to your system set, it would replace Bevy's state-management run criteria! This would make the system set no longer constrained to run as part of a state!

It may still be possible to accomplish such use cases using some trickery.

(TODO) show an example of how it could be done.

With Input

If you want to use Input<T> to trigger state transitions using a button/key press, you need to clear the input manually by calling .reset:

fn esc_to_menu(
    mut keys: ResMut<Input<KeyCode>>,
    mut app_state: ResMut<State<AppState>>,
) {
    if keys.just_pressed(KeyCode::Escape) {
        app_state.set(AppState::MainMenu).unwrap();
        keys.reset(KeyCode::Escape);
    }
}

(note that this requires ResMut)

Not doing this can cause issues.

Multiple Stages

(TODO) move this under the Advanced Patterns chapter.

If you need state-dependent systems in multiple stages, a workaround is required.

You must add the state to one stage only, and then call .get_driver() and add that to the other stages before any state-dependent system sets in those stages.

(TODO) example

Run Criteria

Run Criteria are a mechanism for controlling if Bevy should run specific systems, at runtime. This is how you can make functionality that only runs under certain conditions.

Run Criteria are a lower-level primitive. Bevy provides higher-level abstractions on top, such as States. You can use Run Criteria without such abstractions, if you really need more direct control.

Run Criteria can be applied to individual systems, system sets, and stages.

Run Criteria are Bevy systems that return a value of type enum ShouldRun. They can accept any system parameters, like a normal system.

This example shows how run criteria might be used to implement different multiplayer modes:

use bevy::ecs::schedule::ShouldRun;

#[derive(Debug, PartialEq, Eq)]
enum MultiplayerKind {
    Client,
    Host,
    Local,
}

fn run_if_connected(
    mode: Res<MultiplayerKind>,
    session: Res<MyNetworkSession>,
) -> ShouldRun
{
    if *mode == MultiplayerKind::Client && session.is_connected() {
        ShouldRun::Yes
    } else {
        ShouldRun::No
    }
}

fn run_if_host(
    mode: Res<MultiplayerKind>,
) -> ShouldRun
{
    if *mode == MultiplayerKind::Host || *mode == MultiplayerKind::Local {
        ShouldRun::Yes
    } else {
        ShouldRun::No
    }
}

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)

        // if we are currently connected to a server,
        // activate our client systems
        .add_system_set(
            SystemSet::new()
                .with_run_criteria(run_if_connected.system())
                .before("input")
                .with_system(server_session.system())
                .with_system(fetch_server_updates.system())
        )

        // if we are hosting the game,
        // activate our game hosting systems
        .add_system_set(
            SystemSet::new()
                .with_run_criteria(run_if_host.system())
                .before("input")
                .with_system(host_session.system())
                .with_system(host_player_movement.system())
                .with_system(host_enemy_ai.system())
        )

        // other systems in our game
        .add_system(smoke_particles.system())
        .add_system(water_animation.system())
        .add_system_set(
            SystemSet::new()
                .label("input")
                .with_system(keyboard_input.system())
                .with_system(gamepad_input.system())
        )
        .run();
}

Run Criteria Labels

If you have multiple systems or system sets that you want to share the same run criteria, you can give it a label.

When you use a label, Bevy will only execute the run criteria once, remember its output, and apply it to everything with the label.

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(RunCriteriaLabel)]
enum MyRunCriteria {
    Client,
    Host,
}

fn main() {
    App::build()
        // ...
        .add_system_set(
            SystemSet::new()
                .with_run_criteria(
                    // assign it a label
                    run_if_host.system()
                        .label(MyRunCriteria::Host)
                )
                .with_system(host_session.system())
                .with_system(host_player_movement.system())
                .with_system(host_enemy_ai.system())
        )

        // extra system for debugging the host
        // it can share our previously-registered run criteria
        .add_system(host_debug.system()
            .with_run_criteria(MyRunCriteria::Host)
        )
        .run();
}

The run-once property is especially important if you have a complex run criteria system that performs mutations or is otherwise non-idempotent.

Known Pitfalls

When receiving events in systems that don't run every frame, you will miss any events that are sent during the frames when the receiving systems are not running!

To mitigate this, you could implement a custom cleanup strategy, to manually manage the lifetime of the relevant event types.


Bevy's fixed timestep is also implemented using run criteria under the hood.

Labels

You need labels to name various things in your app, such as systems, run criteria, stages, and ambiguity sets.

Bevy uses some clever Rust type system magic, to allow you to use strings as well as your own custom types for labels, and even mix them!

You may use a value of any type as a label, as long as it has the following standard Rust traits: Clone + Eq + Hash + Debug (and the implied + Send + Sync + 'static).

You need to derive the appropriate trait: StageLabel, SystemLabel, RunCriteriaLabel, or AmbiguitySetLabel.

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(SystemLabel)]
enum MySystems {
    InputSet,
    Movement,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(StageLabel)]
enum MyStages {
    Prepare,
    Cleanup,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(StageLabel)]
struct DebugStage;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)

        // Add our game systems:
        .add_system_set(
            SystemSet::new()
                .label(MySystems::InputSet)
                .with_system(keyboard_input.system())
                .with_system(gamepad_input.system())
        )
        .add_system(player_movement.system().label(MySystems::Movement))

        // temporary debug system, let's just use a string label
        .add_system(debug_movement.system().label("temp-debug"))

        // Add our custom stages:
        // note that Bevy's `CoreStage` is an enum just like ours!
        .add_stage_before(CoreStage::Update, MyStages::Prepare, SystemStage::parallel())
        .add_stage_after(CoreStage::Update, MyStages::Cleanup, SystemStage::parallel())

        .add_stage_after(CoreStage::Update, DebugStage, SystemStage::parallel())

        // we can just use a string for this one:
        .add_stage_before(CoreStage::PostUpdate, "temp-debug-hack", SystemStage::parallel())

        .run();
}

For quick prototyping, it is convenient to just use strings as labels.

However, by defining your labels as custom types, the Rust compiler can check them for you, and your IDE can auto-complete them. It is the recommended way, as it prevents mistakes, and helps you stay organized in larger projects.

Bevy Programming: Advanced

This sub-chapter covers advanced Bevy programming.

These topics may be useful for specialized scenarios in complex projects.

Most typical Bevy users are unlikely to need these features.

Also see the Advanced Patterns chapter, for further information on various advanced techniques and use cases in Bevy.

Stages

All systems to be run by Bevy are contained in stages. Every frame update, Bevy executes each stage, in order. Within each stage, Bevy's scheduling algorithm can run many systems in parallel, using multiple CPU cores for good performance.

The boundaries between stages are effectively hard synchronization points. They ensure that all systems of the previous stage have completed before any systems of the next stage begin, and that there is a moment in time when no systems are in-progress.

This makes it possible/safe to apply Commands. Any operations performed by systems using Commands are applied at the end of each stage.

Internally, Bevy has at least these built-in stages: First, PreUpdate, Update, PostUpdate, Last.

(labeled with enum CoreStage)

By default, when you add your systems, they are added to CoreStage::Update.

Bevy's internal systems are in the other stages, to ensure they are ordered correctly relative to your game logic.

If you want to add your own systems to any of Bevy's internal stages, you need to beware of potential unexpected interactions with Bevy's own internal systems. Remember: Bevy's internals are implemented using ordinary systems and ECS, just like your own stuff!

You can add your own additional stages. For example, if we want our debug systems to run after our game logic:

fn main() {
    // label for our debug stage
    static DEBUG: &str = "debug";

    App::build()
        .add_plugins(DefaultPlugins)

        // add DEBUG stage after Bevy's Update
        // also make it single-threaded
        .add_stage_after(CoreStage::Update, DEBUG, SystemStage::single_threaded())

        // systems are added to the `CoreStage::Update` stage by default
        .add_system(player_gather_xp.system())
        .add_system(player_take_damage.system())

        // add our debug systems
        .add_system_to_stage(DEBUG, debug_player_hp.system())
        .add_system_to_stage(DEBUG, debug_stats_change.system())
        .add_system_to_stage(DEBUG, debug_new_hostiles.system())

        .run();
}

If you need to manage when your systems run, relative to one another, it is generally preferable to avoid using stages, and to use explicit system ordering instead. Stages limit parallel execution and the performance of your game.

However, stages can make it easier to organize things, when you really want to be sure that all previous systems have completed. Stages are also the only way to apply Commands.

If you have systems that need to rely on the actions that other systems have performed by using Commands, and need to do so during the same frame, placing those systems into separate stages is the only way to accomplish that.

More information on avoiding frame delays / 1-frame-lag, when using Commands.

Removal Detection

Relevant official examples: removal_detection.


Removal detection is special. This is because, unlike with change detection, the data does not exist in the ECS anymore (obviously), so Bevy cannot keep tracking metadata for it.

Nevertheless, being able to respond to removals is important for some applications, so Bevy offers a limited form of it.

Components

You can check for components that have been removed during the current frame. The data is cleared at the end of every frame update. Note that this makes this feature tricky to use, and requires you to use multiple stages.

When you remove a component (using Commands), the operation is applied at the end of the stage. The system that checks for the removal must run in a later stage during the same frame update. Otherwise, it will not detect the removal.

Use the RemovedComponents<T> special system parameter type, to get an iterator for the Entity IDs of all the entities that had a component of type T that was removed earlier this frame.

/// Some component type for the sake of this example.
struct Seen;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        // we could add our system to Bevy's `PreUpdate` stage
        // (alternatively, you could create your own stage)
        .add_system_to_stage(CoreStage::PreUpdate, remove_components.system())
        // our detection system runs in a later stage
        // (in this case: Bevy's default `Update` stage)
        .add_system(detect_removals.system())
        .run();
}

fn remove_components(
    mut commands: Commands,
    q: Query<(Entity, &Transform), With<Seen>>,
) {
    for (e, transform) in q.iter() {
        if transform.translation.y < -10.0 {
            // remove the `Seen` component from the entity
            commands.entity(e)
                .remove::<Seen>();
        }
    }
}

fn detect_removals(
    removals: RemovedComponents<Seen>,
    // ... (maybe Commands or a Query ?) ...
) {
    for entity in removals.iter() {
        // do something with the entity
    }
}

(To do things with these entities, you can just use the Entity IDs with Commands::entity() or Query::get().)

Resources

Bevy does not provide any API for detecting when resources are removed.

You can work around this using Option and a separate Local system parameter, effectively implementing your own detection.

fn detect_removed_res(
    my_res: Option<Res<MyResource>>,
    mut my_res_existed: Local<bool>,
) {
    if let Some(my_res) = my_res {
        // the resource exists!

        // remember that!
        *my_res_existed = true;

        // (... you can do something with the resource here if you want ...)
    } else if *my_res_existed {
        // the resource does not exist, but we remember it existed!
        // (it was removed)

        // forget about it!
        *my_res_existed = false;

        // ... do something now that it is gone ...
    }
}

Note that, since this detection is local to your system, it does not have to happen during the same frame update.

Direct World Access

(This page hasn't been written yet...)

Writing Tests for Systems

You might want to write and run automated tests for your systems.

You can use the regular Rust testing features (cargo test) with Bevy.

To do this, you can create an ECS World in your tests, and then, using direct World access, insert whatever entities and resources you need for testing. Create a standalone stage with the systems you want to run, and manually run it on the World.

Bevy's official repository has a fantastic example of how to do this.

Non-Send Resources

(This page hasn't been written yet...)

Bevy Features

This chapter covers the various features of Bevy as a Game Engine. Here you will learn how various common Game Development concepts work in Bevy.

Intended for people learning how to use Bevy.

Transforms and Coordinates

Relevant official examples: parenting.


First, a quick definition, if you are new to game development:

a Transform is what allows you to place an object in the game world. It is a combination of the object's "translation" (position/coordinates), "rotation", and "scale" (size adjustment).

You move objects around by modifying the translation, rotate them by modifying the rotation, and make them larger or smaller by modifying the scale.

Bevy's Coordinate System

Bevy uses the same coordinate system for 3D, 2D, and UI, for consistency.

It is easiest to explain in terms of 2D:

  • The X axis goes from left to right (+X points right).
  • The Y axis goes from bottom to top (+Y points up).
  • The Z axis goes from far to near (+Z points towards you, out of the screen).
  • For 2D, the origin (X=0.0; Y=0.0) is at the center of the screen by default.
    • For UI, the origin is at the bottom left corner.

When you are working with 2D sprites, you can put the background on Z=0.0, and place other sprites at increasing positive Z coordinates to layer them on top.

In 3D, the axes are oriented the same way.

This is a right-handed coordinate system.

It is the same as Godot, Maya, and OpenGL. Compared to Unity, the Z axis is inverted.

Note: In Bevy, the Y axis always points UP.

This may feel unintuitive when working with UI (as it is the opposite from web pages), or if you are used to working with 2D libraries where the Y axis points down.

Also beware of a common pitfall when working in 2D: the camera must be positioned at a far away Z coordinate (=999.9 by default), or you might not be able to see your sprites!

Bevy's Transforms

In Bevy, transforms are represented by two components: Transform and GlobalTransform. Any Entity that represents an object in the game world needs to have both.

Transform is what you typically work with. It is a struct containing the translation, rotation, and scale. To read or manipulate these values, access them from your systems using a query.

If the entity has a parent, the Transform component is relative to the parent. This means that the child object will move/rotate/scale along with the parent.

GlobalTransform represents the absolute global position in the world. If the entity does not have a parent, then this will have the same value as the Transform. The value of GlobalTransform is calculated/managed internally by Bevy. You should treat it as read-only; do not mutate it.

Beware: The two components are synchronized by a bevy-internal system (the "transform propagation system"), which runs in the PostUpdate stage. This is somewhat finnicky and can result in tricky pitfalls if you are trying to do advanced things that rely on both the relative/local and the absolute/global transforms of entities. When you mutate the Transform, the GlobalTransform is not updated immediately. They will be out-of-sync until the transform propagation system runs.

Assets

Relevant official examples: asset_loading.


Bevy has a flexible system for loading and managing your game assets asynchronously (in the background, without causing lag spikes in your game).

The data of your loaded assets is stored in Assets<T> resources.

Assets are tracked using handles. Handles are just lightweight IDs for specific assets.

Loading using AssetServer

To load assets from files, use the AssetServer resource.

struct UiFont(Handle<Font>);

fn load_ui_font(
    mut commands: Commands,
    server: Res<AssetServer>
) {
    let handle: Handle<Font> = server.load("font.ttf");

    // we can store the handle in a resource:
    //  - to prevent the asset from being unloaded
    //  - if we want to use it to access the asset later
    commands.insert_resource(UiFont(handle));
}

This queues the asset loading to happen in the background. The asset will take some time to become available. You cannot access the actual data immediately in the same system, but you can use the handle.

You can spawn your 2D sprites, 3D models, and UI, using the handle, even before the asset has loaded. They will just "pop in" later, when the asset becomes ready.

Note that it is OK to call asset_server.load as many times as you want, even if the asset is currently loading, or already loaded. It will just provide you with the same handle. If the asset is unavailable, it will begin loading.

Creating your own assets

You can also add assets to Assets<T> manually.

This is useful if you want to create them using code (such as for procedural generation), or if you have gotten the data in some other way.

fn add_material(
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let new_mat = StandardMaterial {
        base_color: Color::rgba(0.25, 0.50, 0.75, 1.0),
        unlit: true,
        ..Default::default()
    };

    materials.add(new_mat);
}

Hot-Reloading

Bevy can detect when you modify your asset files and reload them live while the game is running. See this page for more info.

Handles

Handles are the typical way to refer to a particular asset. When you spawn things into your game, such as 2D sprites, 3D models, or UI, their respective components will need handles for the assets they use.

You could store your handles somewhere that is convenient for you (such as in resources).

If you don't have your handle stored anywhere, you can always generate one from a path by calling asset_server.load. You could simply do that whenever you need, and not bother storing handles.

Accessing the Assets

To access the actual asset data from systems, use the Assets<T> resource.

You can identify your desired asset, using either the handle or the asset path:

struct SpriteSheets {
    map_tiles: Handle<TextureAtlas>,
}

fn use_sprites(
    handles: Res<SpriteSheets>,
    atlases: Res<Assets<TextureAtlas>>,
    textures: Res<Assets<Texture>>,
) {
    // Could be `None` if the asset isn't loaded yet
    if let Some(atlas) = atlases.get(&handles.map_tiles) {
        // do something with the texture atlas
    }

    // Can use a path instead of a handle
    if let Some(map_tex) = textures.get("map.png") {
        // if "map.png" was loaded, we can use it!
    }
}

AssetPath and Labels

Assets from the filesystem can be identified by an AssetPath, which consists of the file path + a label. Labels are used in situations where multiple assets are contained in the same file. An example of this are GLTF files, which can contain meshes, scenes, textures, materials, etc.

Asset paths can be created from a string, with the label (if any) attached after a # symbol.

fn load_gltf_things(
    mut commands: Commands,
    server: Res<AssetServer>
) {
    // get a specific mesh
    let my_mesh: Handle<Mesh> = server.load("my_scene.gltf#Mesh0/Primitive0");

    // spawn a whole scene
    let my_scene: Handle<Scene> = server.load("my_scene.gltf#Scene0");
    commands.spawn_scene(my_scene);
}

See the GLTF page for more info about working with 3D models.

Handles and Asset Lifetime (Garbage Collection)

Handles have built-in reference counting (similar to Rc/Arc in Rust). This allows Bevy to track if an asset is still needed, and automatically unload it when it no longer is.

You can use .clone() to create multiple handles to the same asset. The clone is a cheap operation, but it is explicit, to ensure that you are aware of the places in your code that create additional handles and may affect the lifetime of assets.

Weak Handles

Handles can be "strong" (the default) or "weak". Only strong handles are counted and cause the asset to remain loaded. Weak handles let you refer to an asset, while allowing it to be garbage-collected when no more strong handles remain.

You can create weak handles using .clone_weak() instead of .clone().

Untyped Handles

Bevy also has a HandleUntyped type. Use this type of handle if you need to be able to refer to any asset, regardless of the asset type.

This allows you to store a collection (such as Vec or HashMap) containing assets of mixed types.

You can create an untyped handle using .clone_untyped().

Untyped Loading

Conveniently, the AssetServer supports untyped loading, if you don't know what asset type the files are. You can also load an entire folder:

struct ExtraAssets(Vec<HandleUntyped>);

fn load_extra_assets(
    mut commands: Commands,
    server: Res<AssetServer>,
) {
    if let Ok(handles) = server.load_folder("extra") {
        commands.insert_resource(ExtraAssets(handles));
    }
}

It will try to detect the format of each asset based on the file extension.

AssetEvent

If you need to perform specific actions when an asset is created, modified, or removed, you can react to AssetEvents.

struct MyMapTexture {
    handle: Handle<Texture>,
}

fn fixup_textures(
    mut ev_asset: EventReader<AssetEvent<Texture>>,
    mut assets: ResMut<Assets<Texture>>,
    map_tex: Res<MyMapTexture>,
) {
    for ev in ev_asset.iter() {
        match ev {
            AssetEvent::Created { handle } |
            AssetEvent::Modified { handle } => {
                // a texture was just loaded or changed!

                let texture = assets.get_mut(handle).unwrap();
                // ^ unwrap is OK, because we know it is loaded now

                if *handle == map_tex.handle {
                    // it is our special map texture!
                } else {
                    // it is some other texture
                }
            }
            AssetEvent::Removed { handle } => {
                // a texture was unloaded
            }
        }
    }
}

Hot-Reloading Assets

Relevant official examples: hot_asset_reloading, hot_shader_reloading.


At runtime, if you modify the file of an asset that is loaded into the game (via the AssetServer), Bevy will detect that and reload the asset automatically. This is very useful for quick iteration. You can edit your assets while the game is running and see the changes instantly in-game.

Not all file formats and use cases are supported equally well. Typical asset types like textures / images should work without issues, but complex GLTF or scene files, or assets involving custom logic, might not.

If you need to run custom logic as part of your hot-reloading workflow, you could implement it in a system, using AssetEvent.

Hot reloading is opt-in and has to be enabled in order to work. You can do this in a startup system:

    asset_server.watch_for_changes().unwrap();

Shaders

Bevy also supports hot-reloading for shaders. You can edit your custom shader code and see the changes immediately. This only works if you are loading your shaders through the bevy asset system (via the AssetServer). See the official example.

Input Handling

Relevant official examples: everything in the "Input" category.


Click here to download the example code.

This is a complete example that you can run. It will print all input activity to the console.


Bevy supports the following inputs:

  • Keyboard
  • Mouse (relative motion, buttons, scrolling)
  • Cursor (absolute pointer position)
  • Touchscreen (with multi-touch)
  • Most controllers/gamepads/joysticks (via the gilrs library)

Sensors like accelerometers, gyroscopes, VR head tracking, are not supported yet.

For most input types (where it makes sense), Bevy provides two ways of dealing with them:

Some inputs are only provided as events.

Checking state is done using resources such as Input (for binary inputs like keys or buttons), Axis (for analog inputs), Touches (for fingers on a touchscreen), etc. This way of handling input is very convenient for implementing game logic. In these scenarios, you typically only care about the specific inputs mapped to actions in your game. You can check specific buttons/keys to see when they get pressed/released, or what their current state is.

Events are a lower-level, more all-encompassing approach. Use them if you want to get all activity from that class of input device, rather than only checking for specific inputs. For example, if you are implementing text input / typing, you probably want to receive all keyboard activity.

Since bevy resources and events are global and accessible from any system, you don't need to centralize your input handling code in one place. You can check any inputs you care about, from any system.

Input Mapping

Bevy does not yet offer a built-in way to do input mapping (configure key bindings, etc). You need to come up with your own way of translating the inputs into logical actions in your game/app.

There are some community-made plugins that may help with that: see the input-section on bevy-assets.

It may be a good idea to build your own abstractions specific to your game. For example, if you need to handle player movement, you might want to have a system for reading inputs and converting them to your own internal "movement intent/action events", and then another system acting on those internal events, to actually move the player. Make sure to use explicit system ordering to avoid lag / frame delays.

How to work with different input devices

Keyboard

Keyboard keys can be identified by Scan Code or Key Code.

Scan Codes represent the physical key on the keyboard, regardless of the system layout. Key Codes represent the symbol/letter on each key and are dependent on the keyboard layout.

Unfortunately, support for using Scan Codes in Bevy is limited. This can be annoying for people with non-QWERTY keyboard layouts.

Checking the state of specific keys can currently only be done by Key Code, using the Input<KeyCode> resource:

fn keyboard_input(
    keys: Res<Input<KeyCode>>,
) {
    if keys.just_pressed(KeyCode::Space) {
        // Space was pressed
    }
    if keys.just_released(KeyCode::LControl) {
        // Left Ctrl was released
    }
    if keys.pressed(KeyCode::W) {
        // W is being held down
    }
}

To get all keyboard activity, you can use KeyboardInput events:

fn keyboard_events(
    mut key_evr: EventReader<KeyboardInput>,
) {
    use bevy::input::ElementState;

    for ev in key_evr.iter() {
        match ev.state {
            ElementState::Pressed => {
                println!("Key press: {:?} ({})", ev.key_code, ev.scan_code);
            }
            ElementState::Released => {
                println!("Key release: {:?} ({})", ev.key_code, ev.scan_code);
            }
        }
    }
}

These events give you both the Key Code and Scan Code, but unfortunately, the Scan Code is just represented as an arbitrary u32 integer ID (which means it is difficult to figure out what actual physical key it represents).

Mouse Buttons

Similar to keyboard input, mouse buttons are available as an Input state resource, as well as events.

You can check the state of specific mouse buttons using Input<MouseButton>:

fn mouse_button_input(
    buttons: Res<Input<MouseButton>>,
) {
    if buttons.just_pressed(MouseButton::Left) {
        // Left button was pressed
    }
    if buttons.just_released(MouseButton::Left) {
        // Left Button was released
    }
    if buttons.pressed(MouseButton::Right) {
        // Right Button is being held down
    }
}

To get all press/release activity, use MouseButtonInput events:

fn mouse_button_events(
    mut mousebtn_evr: EventReader<MouseButtonInput>,
) {
    use bevy::input::ElementState;

    for ev in mousebtn_evr.iter() {
        match ev.state {
            ElementState::Pressed => {
                println!("Mouse button press: {:?}", ev.button);
            }
            ElementState::Released => {
                println!("Mouse button release: {:?}", ev.button);
            }
        }
    }
}

Mouse Motion

If you need to detect relative mouse motion, you can use MouseMotion events. Whenever the mouse is moved, you will get an event with the delta.

fn mouse_motion(
    mut motion_evr: EventReader<MouseMotion>,
) {
    for ev in motion_evr.iter() {
        println!("Mouse moved: X: {} px, Y: {} px", ev.delta.x, ev.delta.y);
    }
}

You might want to grab/lock the mouse inside the game window.

Mouse Cursor

You can get the current coordinates of the mouse pointer, from the respective Window:

fn cursor_position(
    windows: Res<Windows>,
) {
    // Games typically only have one window (the primary window).
    // For multi-window applications, you need to use a specific window ID here.
    let window = windows.get_primary().unwrap();

    if let Some(_position) = window.cursor_position() {
        // cursor is inside the window, position given
    } else {
        // cursor is not inside the window
    }
}

To detect when the pointer is moved, use CursorMoved events to get the updated coordinates:

fn cursor_events(
    mut cursor_evr: EventReader<CursorMoved>,
) {
    for ev in cursor_evr.iter() {
        println!(
            "New cursor position: X: {}, Y: {}, in Window ID: {:?}",
            ev.position.x, ev.position.y, ev.id
        );
    }
}

Note that you can only get the position of the mouse inside a window; you cannot get the global position of the mouse in the whole OS Desktop / on the screen as a whole.

Scrolling / Mouse Wheel

To detect scrolling input, use MouseWheel events:

fn scroll_events(
    mut scroll_evr: EventReader<MouseWheel>,
) {
    use bevy::input::mouse::MouseScrollUnit;
    for ev in scroll_evr.iter() {
        match ev.unit {
            MouseScrollUnit::Line => {
                println!("Scroll (line units): vertical: {}, horizontal: {}", ev.y, ev.x);
            }
            MouseScrollUnit::Pixel => {
                println!("Scroll (pixel units): vertical: {}, horizontal: {}", ev.y, ev.x);
            }
        }
    }
}

The MouseScrollUnit enum is important: it tells you the type of scroll input. Line is for hardware with fixed steps, like the wheel on desktop mice. Pixel is for hardware with smooth (fine-grained) scrolling, like laptop touchpads.

You should probably handle each of these differently (with different sensitivity settings), to provide a good experience on both types of hardware.

Controller / Gamepad / Joystick

You can detect when controllers/gamepads are connected or disconnected using GamepadEvent. Do this to get the ID of the device and assign it to a "player" in your game. You can easily support local multiplayer.

/// Simple resource to store the ID of the connected gamepad.
/// We need to know which gamepad to use for player input.
struct MyGamepad(Gamepad);

fn gamepad_connections(
    mut commands: Commands,
    my_gamepad: Option<Res<MyGamepad>>,
    mut gamepad_evr: EventReader<GamepadEvent>,
) {
    for GamepadEvent(id, kind) in gamepad_evr.iter() {
        match kind {
            GamepadEventType::Connected => {
                println!("New gamepad connected with ID: {:?}", id);

                // if we don't have any gamepad yet, use this one
                if my_gamepad.is_none() {
                    commands.insert_resource(MyGamepad(*id));
                }
            }
            GamepadEventType::Disconnected => {
                println!("Lost gamepad connection with ID: {:?}", id);

                // if it's the one we previously associated with the player,
                // disassociate it:
                if let Some(MyGamepad(old_id)) = my_gamepad.as_deref() {
                    if old_id == id {
                        commands.remove_resource::<MyGamepad>();
                    }
                }
            }
            // other events are irrelevant
            _ => {}
        }
    }
}

The other event types are for changes to axes and buttons. They are not shown above, because handling gamepad input via events is inconvenient.

You can handle the analog sticks with Axis<GamepadAxis>. Buttons can be handled with Input<GamepadButton> (similar to mouse buttons or keyboard keys).

fn gamepad_input(
    axes: Res<Axis<GamepadAxis>>,
    buttons: Res<Input<GamepadButton>>,
    my_gamepad: Option<Res<MyGamepad>>,
) {
    let gamepad = if let Some(gp) = my_gamepad {
        // a gamepad is connected, we have the id
        gp.0
    } else {
        // no gamepad is connected
        return;
    };

    // The joysticks are represented using a separate axis for X and Y

    let axis_lx = GamepadAxis(gamepad, GamepadAxisType::LeftStickX);
    let axis_ly = GamepadAxis(gamepad, GamepadAxisType::LeftStickY);

    if let (Some(x), Some(y)) = (axes.get(axis_lx), axes.get(axis_ly)) {
        // combine X and Y into one vector
        let left_stick_pos = Vec2::new(x, y);

        // implement a dead-zone to ignore small inputs
        if left_stick_pos.length() > 0.1 {
            // do something with the position of the left stick
        }
    }

    let jump_button = GamepadButton(gamepad, GamepadButtonType::South);
    let heal_button = GamepadButton(gamepad, GamepadButtonType::East);

    if buttons.just_pressed(jump_button) {
        // button pressed: make the player jump
    }

    if buttons.pressed(heal_button) {
        // button being held down: heal the player
    }
}

Notice that the names of buttons in the enum are vendor-neutral (like South and East instead of X/O or A/B). Many different kinds of hardware should work, but if your device is not supported, you should file an issue with the gilrs project.

Touchscreen

Multi-touch touchscreens are supported. You can track multiple fingers on the screen, with position and pressure/force information. Bevy does not offer gesture recognition.

The Touches resource allows you to track any fingers currently on the screen:

fn touches(
    touches: Res<Touches>,
) {
    // There is a lot more information available, see the API docs.
    // This example only shows some very basic things.

    for finger in touches.iter() {
        if touches.just_pressed(finger.id()) {
            println!("A new touch with ID {} just began.", finger.id());
        }
        println!(
            "Finger {} is at position ({},{}), started from ({},{}).",
            finger.id(),
            finger.position().x,
            finger.position().y,
            finger.start_position().x,
            finger.start_position().y,
        );
    }
}

Alternatively, you can use TouchInput events, but this is often harder to use:

fn touch_events(
    mut touch_evr: EventReader<TouchInput>,
) {
    use bevy::input::touch::TouchPhase;
    for ev in touch_evr.iter() {
        match ev.phase {
            TouchPhase::Started => {
                println!("Touch {} started at: {:?}", ev.id, ev.position);
            }
            TouchPhase::Moved => {
                println!("Touch {} moved to: {:?}", ev.id, ev.position);
            }
            TouchPhase::Ended => {
                println!("Touch {} ended at: {:?}", ev.id, ev.position);
            }
            TouchPhase::Cancelled => {
                println!("Touch {} cancelled at: {:?}", ev.id, ev.position);
            }
        }
    }
}

Window Management

Relevant official examples: window_settings, multiple_windows.


(This page is not published yet ... coming soon!)

Cameras

Relevant official examples: sprite (2D), 3d_scene (3D), orthographic (3D).


(This page is not published yet ... coming soon!)

Scenes

Relevant official examples: scene.


(This page is not published yet ... coming soon!)

3D Models (GLTF)

Relevant official examples: load_gltf, update_gltf_scene.


Bevy uses the GLTF 2.0 file format for 3D assets.

(other formats such as Wavefront OBJ may be unofficially available via 3rd-party plugins)

Quick-Start: Spawning 3D Models into your World

The simplest use case is to just load a "3D model" and spawn it into the game world.

"3D models" can often be complex, consisting of multiple parts. Think of a house: the windows, roof, doors, etc., are separate pieces, that are likely made of multiple meshes, materials, and textures. Bevy would technically need multiple ECS Entities to represent and render the whole thing.

This is why your GLTF "model" is represented by Bevy as a Scene. This way, you can easily spawn it, and Bevy will create all the relevant child entities and configure them correctly.

So that you can treat the whole thing as "a single object" and position it in the world, you can just spawn it under a parent entity, and use its Transform.

fn spawn_gltf(
    mut commands: Commands,
    ass: Res<AssetServer>,
) {
    // note that we have to include the `Scene0` label
    let my_gltf = ass.load("my.glb#Scene0");

    // to be able to position our 3d model:
    // spawn a parent entity with a Transform and GlobalTransform
    // and spawn our gltf as a scene under it
    commands.spawn_bundle((
        Transform::from_xyz(2.0, 0.0, -5.0),
        GlobalTransform::identity(),
    )).with_children(|parent| {
        parent.spawn_scene(my_gltf);
    });
}

If your GLTF Scene represents "a whole level/map", rather than "an individual 3d model", and you don't need to move it around, you can just spawn the scene directly, without creating a parent entity.

Also, this example assumes that you have a simple GLTF file containing only one "default scene". GLTF is a very flexible file format. A single file can contain many "models" or more complex "scenes". To get a better understanding of GLTF and possible workflows, read the rest of this page. :)

Introduction to GLTF

GLTF is a modern open standard for exchanging 3D assets between different 3D software applications, like game engines and 3D modeling software.

The GLTF file format has two variants: human-readable ascii/text (*.gltf) and binary (*.glb). The binary format is more compact and preferable for packaging the assets with your game. The text format may be useful for development, as it can be easier to manually inspect using a text editor.

A GLTF file can contain many objects (sub-assets): meshes, materials, textures, scenes. When loading a GLTF file, Bevy will load all of the assets contained inside. They will be mapped to the appropriate Bevy-internal asset types.

The GLTF sub-assets

GLTF terminology can be confusing, as it sometimes uses the same words to refer to different things, compared to Bevy. This section will try explain the various GLTF terms.

To understand everything, it helps to mentally consider how these concepts are represented in different places: in your 3D modeling software (like Blender), in the GLTF file itself, and in Bevy.

GLTF Scenes are what you spawn into your game world. This is typically what you see on the screen in your 3D modeling software. Scenes combine all of the data needed for the game engine to create all the needed entities to represent what you want. Conceptually, think of a scene as one "unit". Depending on your use case, this could be one "3d model", or even a whole map or game level. In Bevy, these are represented as Bevy Scenes with all the child ECS entities.

GLTF Scenes are composed of GLTF Nodes. These describe the "objects" in the scene, typically GLTF Meshes, but can also be other things like Cameras and Lights. Each GLTF Node has a transform for positioning it in the scene. GLTF Nodes do not have a core Bevy equivalent; Bevy just uses this data to create the ECS Entities inside of a Scene. Bevy has a special GltfNode asset type, if you need access to this data.

GLTF Meshes represent one conceptual "3D object". These correspond to the "objects" in your 3D modeling software. GLTF Meshes may be complex and composed of multiple smaller pieces, called GLTF Primitives, each of which may use a different Material. GLTF Meshes do not have a core Bevy equivalent, but there is a special GltfMesh asset type, which describes the primitives.

GLTF Primitives are individual "units of 3D geometry", for the purposes of rendering. They contain the actual geometry / vertex data, and reference the Material to be used when drawing. In Bevy, each GLTF Primitive is represented as a Bevy Mesh asset, and must be spawned as a separate PBR ECS Entity to be rendered.

GLTF Materials describe the shading parameters for the surfaces of your 3D models. They have full support for Physically-Based Rendering (PBR). They also reference the textures to use. In Bevy, they are represented as StandardMaterial assets, as used by the Bevy PBR 3D renderer.

GLTF Textures (images) can be embedded inside the GLTF file, or stored externally in separate image files alongside it. For example, you can have your textures as separate PNG or JPEG files for ease of development, or package them all inside the GLTF file for ease of distribution. In Bevy, GLTF textures are loaded as Bevy Texture assets.

GLTF Samplers describe the settings for how the GPU should use a given Texture. Bevy does not keep these separate; this data is stored inside the Bevy Texture asset (the sampler field of type SamplerDescriptor).

GLTF Usage Patterns

Because GLTF is so flexible, it is up to you how to structure your assets.

A single GLTF file might be used:

  • To represent a single "3D model", containing a single GLTF Scene with the model, so you can spawn it into your game.
  • To represent a whole level, as a GLTF Scene, possibly also including the camera. This lets you load and spawn a whole level/map at once.
  • To represent a piece of a level/map, such as a room.
  • To contain a set of many different "3D models", each as a separate GLTF Scene. This lets you load and manage the whole collection at once and spawn them individually as needed.
  • ... others?

Tools for Creating GLTF Assets

If you are using a recent version of Blender (2.8+) for 3D modeling, GLTF is supported out of the box. Just export and choose GLTF as the format.

For other tools, you can try these exporter plugins:

Using GLTF Sub-Assets in Bevy

The various sub-assets contained in a GLTF file can be addressed in two ways:

  • by index (integer id, in the order they appear in the file)
  • by name (text string, the names you set in your 3D modeling software when creating the asset, which can be exported into the GLTF)

To get handles to the respective assets in Bevy, you can use the Gltf "master asset", or alternatively, AssetPath with Labels.

Gltf master asset

If you have a complex GLTF file, this is likely the most flexible and useful way of navigating its contents and using the different things inside.

You have to wait for the GLTF file to load, and then use the Gltf asset.

use bevy::gltf::Gltf;

/// Helper resource for tracking our asset
struct MyAssetPack(Handle<Gltf>);

fn load_gltf(
    mut commands: Commands,
    ass: Res<AssetServer>,
) {
    let gltf = ass.load("my_asset_pack.glb");
    commands.insert_resource(MyAssetPack(gltf));
}

fn spawn_gltf_objects(
    mut commands: Commands,
    my: Res<MyAssetPack>,
    assets_gltf: Res<Assets<Gltf>>,
) {
    // if the GLTF has loaded, we can navigate its contents
    if let Some(gltf) = assets_gltf.get(&my.0) {
        // spawn the first scene in the file
        commands.spawn_scene(gltf.scenes[0].clone());

        // spawn the scene named "YellowCar"
        // do it under a parent entity, to position it in the world
        commands.spawn_bundle((
            Transform::from_xyz(1.0, 2.0, 3.0),
            GlobalTransform::identity(),
        )).with_children(|parent| {
            parent.spawn_scene(gltf.named_scenes["YellowCar"].clone());
        });

        // PERF: the `.clone()`s are just for asset handles, don't worry :)
    }
}

For a more convoluted example, say we want to directly create a 3D PBR entity, for whatever reason. (This is not recommended; you should probably just use scenes)

use bevy::gltf::GltfMesh;

fn gltf_manual_entity(
    mut commands: Commands,
    my: Res<MyAssetPack>,
    assets_gltf: Res<Assets<Gltf>>,
    assets_gltfmesh: Res<Assets<GltfMesh>>,
) {
    if let Some(gltf) = assets_gltf.get(&my.0) {
        // Get the GLTF Mesh named "CarWheel"
        // (unwrap safety: we know the GLTF has loaded already)
        let carwheel = assets_gltfmesh.get(&gltf.named_meshes["CarWheel"]).unwrap();

        // Spawn a PBR entity with the mesh and material of the first GLTF Primitive
        commands.spawn_bundle(PbrBundle {
            mesh: carwheel.primitives[0].mesh.clone(),
            // (unwrap: material is optional, we assume this primitive has one)
            material: carwheel.primitives[0].material.clone().unwrap(),
            ..Default::default()
        });
    }
}

AssetPath with Labels

This is another way to access specific sub-assets. It is less reliable, but may be easier to use in some cases.

Use the AssetServer to convert a path string into a Handle.

The advantage is that you can get handles to your sub-assets immediately, even if your GLTF file hasn't loaded yet.

The disadvantage is that it is more error-prone. If you specify a sub-asset that doesn't actually exist in the file, or mis-type the label, or use the wrong label, it will just silently not work. Also, currently only using a numerial index is supported. You cannot address sub-assets by name.

fn use_gltf_things(
    mut commands: Commands,
    ass: Res<AssetServer>,
) {
    // spawn the first scene in the file
    let scene0 = ass.load("my_asset_pack.glb#Scene0");
    commands.spawn_scene(scene0);

    // spawn the second scene under a parent entity
    // (to move it)
    let scene1 = ass.load("my_asset_pack.glb#Scene1");
    commands.spawn_bundle((
        Transform::from_xyz(1.0, 2.0, 3.0),
        GlobalTransform::identity(),
    )).with_children(|parent| {
        parent.spawn_scene(scene1);
    });
}

The following asset labels are supported ({} is the numerical index):

  • Scene{}: GLTF Scene as Bevy Scene
  • Node{}: GLTF Node as GltfNode
  • Mesh{}: GLTF Mesh as GltfMesh
  • Mesh{}/Primitive{}: GLTF Primitive as Bevy Mesh
  • Material{}: GLTF Material as Bevy StandardMaterial
  • DefaultMaterial: as above, if the GLTF file contains a default material with no index
  • Texture{}: GLTF Texture as Bevy Texture

The GltfNode and GltfMesh asset types are only useful to help you navigate the contents of your GLTF file. They are not core Bevy renderer types, and not used by Bevy in any other way. The Bevy renderer expects Entities with PbrBundle; for that you need the Mesh and StandardMaterial.

Bevy Limitations

Bevy does not fully support all features of the GLTF format and has some specific requirements about the data. Not all GLTF files can be loaded and rendered in Bevy. Unfortunately, in many of these cases, you will not get any error or diagnostic message.

Commonly-encountered limitations:

  • Textures embedded in ascii (*.gltf) files (base64 encoding) cannot be loaded. Put your textures in external files, or use the binary (*.glb) format.
  • Mipmaps are not supported. Your asset should still load/render, but without mipmapping.
  • Bevy's renderer requires all meshes/primitives to have per-vertex positions, UVs, and normals. Make sure all of this data is included.
  • Meshes/primitives without textures (if the material is just a solid color) must still include UVs regardless. Bevy will not render meshes without UVs.
  • When using normal maps in your material, tangents must also be included in the mesh. Meshes with normal maps but without tangents are valid; other software would typically autogenerate the tangents if they are missing, but Bevy does not support this yet. Be sure to tick the checkbox for including tangents when exporting.
  • Bevy does not have built-in skeletal animation support yet. Animations are completely ignored.
  • Bevy does not support loading GLTF Lights. GLTF Scenes containing Light Nodes will be spawned without the lights.

This list is not exhaustive. There may be other unsupported scenarios that I did not know of or forgot to include here. :)

Sprites

Relevant official examples: sprite, sprite_sheet, sprite_flipping, texture_atlas.


(This page is not published yet ... coming soon!)

Textures

Relevant official examples: texture.


(This page is not published yet ... coming soon!)

Materials

(This page is not published yet ... coming soon!)

Meshes

Relevant official examples: mesh_custom_attribute, wireframe.


(This page is not published yet ... coming soon!)

Physically-Based Rendering (PBR)

Relevant official examples: pbr.


(This page is not published yet ... coming soon!)

Fixed Timestep

Relevant official examples: fixed_timestep.


If you need something to happen at fixed time intervals (a common use case is Physics updates), you can use Bevy's FixedTimestep Run Criteria.

use bevy::core::FixedTimestep;

// The timestep says how many times to run the SystemSet every second
// For TIMESTEP_1, it's once every second
// For TIMESTEP_2, it's twice every second

const TIMESTEP_1_PER_SECOND: f64 = 60.0 / 60.0;
const TIMESTEP_2_PER_SECOND: f64 = 30.0 / 60.0;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_system_set(
            SystemSet::new()
                // This prints out "hello world" once every second
                .with_run_criteria(FixedTimestep::step(TIMESTEP_1_PER_SECOND))
                .with_system(slow_timestep.system())
        )
        .add_system_set(
            SystemSet::new()
                // This prints out "goodbye world" twice every second
                .with_run_criteria(FixedTimestep::step(TIMESTEP_2_PER_SECOND))
                .with_system(fast_timestep.system())
        )
        .run();
}

fn slow_timestep() {
    println!("hello world");
}

fn fast_timestep() {
    println!("goodbye world");
}

(thanks @billyb2 for contributing this example)

State

You can check the current state of the fixed timestep trackers, by accessing the FixedTimesteps resource. This lets you know how much time remains until the next time it triggers, or how much it has overstepped. You need to label your fixed timesteps.

See the [official example], which illustrates this.

Caveats

Note that, as this feature is implemented using Run Criteria, the systems are still called as part of the regular frame-update cycle, along with all of the normal systems. So, the timing is not exact.

The FixedTimestep run criteria simply checks how much time passed since the last time your systems were ran, and decides whether to run them during the current frame, or not, or run them multiple times, as needed.

Danger! Lost events!

By default, Bevy's events are not reliable! They only persist for 2 frames, after which they are lost. If your fixed-timestep systems receive events, beware that you may miss some events if the framerate is higher than 2x the fixed timestep.

One way around that is to use events with manual clearing. This gives you control over how long events persist, but can also leak / waste memory if you forget to clear them.

Audio

Bevy's own built-in audio support is extremely barebones and limited. It can play sounds, but that's about it. It doesn't even have volume control.

Instead, we recommend that you try the bevy_kira_audio community plugin, which integrates the Kira sound library with bevy. Kira is much more feature-rich, including support for managing many audio tracks (like background music and sound effects), with volume control, stereo panning, playback rate, and streaming. It also has web support.

The community largely considers Bevy's audio to be obsolete and useless; it will probably be removed and replaced with something else (maybe bevy_kira_audio).

Using bevy_kira_audio in your project requires some extra configuration, because you need to disable Bevy's own audio. Bevy's audio is a cargo feature that is enabled by default, but must be disabled. Cargo does not let you disable individual default features, so you need to disable all default bevy features and re-enable the ones you need.

You must not include the bevy_audio feature, or any of the audio file formats (such as the default mp3). Enable the file formats you care about on bevy_kira_audio instead of Bevy.

[dependencies.bevy]
version = "0.5"
default-features = false
# These are the remaining default features other than `bevy_audio` and `mp3`
features = [
  "bevy_dynamic_plugin",
  "render",
  "bevy_wgpu",
  "bevy_winit",
  "bevy_gilrs",
  "bevy_gltf",
  "png",
  "hdr",
  "x11"
]

[dependencies.bevy_kira_audio]
version = "0.5.0"
# `ogg` format support is enabled by default, disable if you don't want it
default-features = false
# enable the features you care about
features = [
  "wav",
  "flac",
  "mp3",
  "ogg",
]

See this page for more information about Bevy's cargo features.

Common Pitfalls

This chapter covers some common issues or surprises that you might be likely to encounter when working with Bevy, with specific advice about how to address them.

Strange Build Errors

Sometimes, you can get strange and confusing build errors when trying to compile your project.

Update your Rust

First, make sure your Rust is up-to-date. When using Bevy, you must use at least the latest stable version of Rust (or nightly).

If you are using rustup to manage your Rust installation, you can run

rustup update

Fix: Clear the cargo state

Many kinds of build errors can often be fixed by forcing cargo to regenerate its internal state (recompute dependencies, etc.). You can do this by deleting the Cargo.lock file and the target directory.

rm -rf target Cargo.lock

Try building your project again after doing this. It is likely that the mysterious errors will go away.

This trick often fixes the broken build, but if it doesn't help you, your issue might require further investigation. Reach out to the Bevy community via GitHub or Discord, and ask for help.

If you are using bleeding-edge Bevy ("main"), and the above does not solve the problem, your errors might be caused by 3rd-party plugins. See this page for solutions.

What errors?

One common example is the "failed to select a version" error, which can look something like this:

error: failed to select a version for `web-sys`.
    ... required by package `wgpu v0.9.0`
    ... which is depended on by `bevy_wgpu v0.5.0 (https://github.com/bevyengine/bevy#6a8a8c9d)`
    ... which is depended on by `bevy_internal v0.5.0 (https://github.com/bevyengine/bevy#6a8a8c9d)`
    ... which is depended on by `bevy v0.5.0 (https://github.com/bevyengine/bevy#6a8a8c9d)`
    ... which is depended on by `bevy-scratchpad v0.1.0 (C:\Users\Alice\Documents\bevy-scratchpad)`
versions that meet the requirements `=0.3.50` are: 0.3.50

all possible versions conflict with previously selected packages.

  previously selected package `web-sys v0.3.46`
    ... which is depended on by `bevy_app v0.5.0 (https://github.com/bevyengine/bevy#6a8a8c9d)`
    ... which is depended on by `bevy_asset v0.5.0 (https://github.com/bevyengine/bevy#6a8a8c9d)`
    ... which is depended on by `bevy_audio v0.5.0 (https://github.com/bevyengine/bevy#6a8a8c9d)`
    ... which is depended on by `bevy_internal v0.5.0 (https://github.com/bevyengine/bevy#6a8a8c9d)`
    ... which is depended on by `bevy v0.5.0 (https://github.com/bevyengine/bevy#6a8a8c9d)`
    ... which is depended on by `bevy-scratchpad v0.1.0 (C:\Users\Alice\Documents\bevy-scratchpad)`

failed to select a version for `web-sys` which could resolve this conflict

(there are many variations, yours might not be identical to the example above)

Another related error are seemingly-nonsensical compiler messages about conflicts with Bevy's internal types (like "expected type Transform, found type Transform").

Why does this happen?

Such errors are often caused by cargo's internal state being broken. Usually, it is because of dependencies not being resolved properly, causing cargo to try to link multiple versions of Bevy into your project. This often occurs when transitioning your project between the release and the git version of Bevy. Cargo remembers the versions it was previously using, and gets confused.

See this cargo issue about this bug. If you have any interesting information to add, you can help by contributing to that issue.

Error adding function as system

You can sometimes get confusing arcane compiler errors when you try to add systems to your Bevy app.

The errors can look like this:

no method named `system` found for fn item `for<'r, 's> fn(...) {my_system}` in the current scope
`my_system` is a function, perhaps you wish to call it

This is caused by your function having incompatible parameters. Bevy can only accept special types as system parameters.

You might also errors that look like this:

the trait bound `Component: WorldQuery` is not satisfied
the trait `WorldQuery` is not implemented for `Component`
this struct takes at most 2 type arguments but 3 type arguments were supplied

These errors are caused by a malformed query.

Common beginner mistakes

  • Using &mut Commands (bevy 0.4 syntax) instead of Commands.
  • Using Query<MyStuff> instead of Query<&MyStuff> or Query<&mut MyStuff>.
  • Using Query<&ComponentA, &ComponentB> instead of Query<(&ComponentA, &ComponentB)> (forgetting the tuple)
  • Using your resource types directly without Res or ResMut.
  • Using your component types directly without putting them in a Query.
  • Using other arbitrary types in your function.

Note that Query<Entity> is correct, because the Entity ID is special; it is not a component.

Supported types

It can be difficult to figure out what types are supported from the API docs, so here is a list:

Only the following types are supported as system parameters:

  • Commands
  • Res<T> / ResMut<T>
  • Option<Res<T>> / Option<ResMut<T>>
  • Local<T>
  • EventReader<T>
  • EventWriter<T>
  • Query<T, F = ()>; can contain tuples of up to 15 types
  • QuerySet with up to 4 queries
  • NonSend<T> / NonSendMut<T>
  • Entities
  • Components
  • Bundles
  • Archetypes
  • RemovedComponents<T>
  • Arc<parking_lot::Mutex<Commands>>
  • DrawContext
  • tuples containing any of these types, with up to 16 members

Your function can have a maximum of 16 total parameters. If you need more, group them into tuples to work around the limit.

You can nest tuples as much as you want, to avoid running into the limits on the maximum numbers of parameters, or simply to organize your parameters into groups.

Combining Query Filters

Query Filters like With<T>/Without<T> take a single component type T. If you want to filter based on multiple components, you can create a Query that has multiple filters in a tuple:

Query<&Stuff, (With<ComponentA>, With<ComponentB>)>

It is a common beginner mistake to instead put multiple components in a tuple inside a single filter. This is incorrect:

Query<&Stuff, With<(ComponentA, ComponentB)>>

The above would compile and run, but treat the whole tuple as a single component type. Since it is looking for entities that have the tuple as a component, it will not give the correct results as you would expect.

Borrow multiple fields from struct

When you have a component or resource, that is larger struct with multiple fields, sometimes you want to borrow several of the fields at the same time, possibly mutably. This can result in a compiler error about conflicting borrows:

struct MyThing {
    a: Foo,
    b: Bar,
}

fn helper_func(foo: &Foo, bar: &mut Bar) {
    // do something
}

fn my_system(mut q: Query<&mut MyThing>) {
    for thing in q.iter_mut() {
        helper_func(&thing.a, &mut thing.b); // ERROR!
    }
}

The error:

error[E0502]: cannot borrow `thing` as mutable because it is also borrowed as immutable
    |
    |         helper_func(&thing.a, &mut thing.b); // ERROR!
    |         -----------  -----         ^^^^^ mutable borrow occurs here
    |         |            |
    |         |            immutable borrow occurs here
    |         immutable borrow later used by call

The solution is to use the "reborrow" idiom, a common but non-obvious trick in Rust programming:

        // add this at the start of the for loop, before using `thing`:
        let thing = &mut *thing;

Note that this line triggers change detection. Even if you don't modify the data afterwards, the component gets marked as changed.

Explanation

Bevy typically gives you access to your data via special wrapper types (like Res<T>, ResMut<T>, and Mut<T> (when querying for components mutably)). This lets Bevy track access to the data.

These are "smart pointer" types that use the Rust Deref trait to dereference to your data. They usually work seamlessly and you don't even notice them.

However, in a sense, they are opaque to the compiler. The Rust language allows fields of a struct to be borrowed individually, when you have direct access to the struct, but this does not work when it is wrapped in another type.

The "reborrow" trick shown above, effectively converts the wrapper into a regular Rust reference. *thing dereferences the wrapper via Deref, and then &mut borrows it mutably. You now have &mut MyStuff instead of Mut<MyStuff>.

Performance

This page is a summary of performance issues when working with Bevy.

(WIP)

Unoptimized debug builds

Rust without compiler optimizations is very slow. With Bevy in particular, the default debug build settings will lead to awful runtime performance. Assets are slow to load and FPS is low.

Common symptoms:

  • Loading high-res 3D models with a lot of large textures, from GLTF files, can take over 20 seconds! This can trick you into thinking that your code is not working, because you will not see anything on the screen until it is ready.
  • After spawning some 2D sprites or 3D models, framerate may drop to unplayable levels.

However, fully-optimized release builds can be slow to compile.

Solutions:

# in `Cargo.toml` or `.cargo/config.toml`

# Enable optimizations for dependencies (incl. Bevy), but not for our code:
[profile.dev.package."*"]
opt-level = 3

# Maybe also enable only a small amount of optimization for our code:
[profile.dev]
opt-level = 1

Avoiding Frame Delays / 1-frame-lag

(This page hasn't been written yet...)

I can't see my UI!

If you are trying to build a UI, but it is not showing on the screen, you probably forgot to spawn a UI Camera. The UI Camera is required for Bevy to render UI.

commands.spawn_bundle(UiCameraBundle::default());

Cannot see sprites in 2D

Bevy's 2D coordinate space is set up so that your background can be at Z=0.0, and other sprite layers can be at positive +Z coordinates above that.

However, this means that, to see your scene, the camera needs to be placed far away, at a large +Z coordinate, looking towards -Z.

If you are overriding the camera transform / creating your own transform, you need to do this! The default transform (with Z=0.0) will place the camera so that your sprites (at positive +Z coordinates) would be behind the camera, and you wouldn't see them! You need to either set a large Z coordinate, or preserve/copy the Z value from the Transform that is generated by Bevy's builtin Bundle constructor (OrthographicCameraBundle::new_2d()).

By default, when you create a 2D camera using Bevy's built-in Bundle constructor (OrthographicCameraBundle::new_2d()), Bevy sets the camera Transform to have Z=999.9. This is close to the default clipping plane (visible range of Z axis), which is set to 1000.0.

Cannot see 3D model

This page will list some common issues that you may encounter, if you are trying to spawn a 3D object, but cannot see it on the screen.

Incorrect usage of Bevy GLTF assets

Refer to the GLTF page to learn how to correctly use GLTF with Bevy.

GLTF files are complex. They contain many sub-assets, represented by different Bevy types. Make sure you are using the correct thing.

Make sure you are spawning a GLTF Scene, or using the correct Mesh and StandardMaterial associated with the correct GLTF Primitive.

If you are using an asset path, be sure to include a label for the sub-asset you want:

asset_server.load("my.gltf#Scene0");

If you are spawning the top-level Gltf master asset, it won't work.

If you are spawning a GLTF Mesh, it won't work.

If you are using the top-level Gltf master asset or a GltfMesh on a Bevy PbrBundle entity that you have created yourself, it won't work. You need a specific GLTF Primitive. Or just use Scenes. :)

Unsupported GLTF

Bevy's GLTF support is still limited in features. See the Limitations section in the GLTF page for some considerations.

Unoptimized / Debug builds

Maybe your asset just takes a while to load? Bevy (and Rust in general) is very slow without compiler optimizations. It's actually possible that complex GLTF files with big textures can take over a minute to load and show up on the screen. It would be almost instant in optimized builds. See here.

Vertex Order and Culling

By default, the Bevy renderer assumes Counter-Clockwise vertex order and has back-face culling enabled.

If you are generating your Mesh from code, make sure your vertices are in the correct order.

Support for loading different file formats

By default, only a few asset file formats are enabled:

  • Images: PNG and HDR
  • Audio: MP3

You can enable more formats with cargo features:

  • Images: JPEG, TGA, BMP, DDS
  • Audio: FLAC, OGG, WAV
[dependencies.bevy]
version = "0.5"
features = ["jpeg", "tga", "bmp", "dds", "flac", "vorbis", "wav"]

Bevy Time vs. Rust/OS time

Do not use std::time::Instant::now() to get the current time. Use Bevy's Res<Time>.

Rust (and the OS) give you the precise time of the moment you call that function. However, that's not what you want.

Your game systems are run by Bevy's parallel scheduler, which means that they could be called at vastly different instants every frame! This will result in inconsistent / jittery timings and make your game misbehave or look stuttery.

Bevy's Time gives you timing information that is correct and consistent. It's designed to be used for game logic.

This is not Bevy-specific, but applies to game development in general. Always get your time from your game engine, not from your programming language or operating system.

UI layout is inverted

In bevy, the Y axis always points UP. When working with UI, the origin is at the bottom left corner of the screen.

This means that UI is laid out from bottom to top.

This is the opposite of the typical behavior of web pages and other UI toolkits, where layout works from top to bottom.

Bevy uses the Flexbox layout model for UI, but unlike in web pages / CSS, the vertical axis is inverted.

Unintuitively, this means that to build UIs that flow from top to bottom, you need to use FlexDirection::ColumnReverse.

UV coordinates in Bevy

In Bevy, the vertical axis for the pixels of textures / images, and when sampling textures in a shader, points downwards, from top to bottom. The origin is at the top left.

This is consistent with how most image file formats store pixel data, and with how most graphics APIs work (including DirectX, Vulkan, Metal, WebGPU, but not OpenGL).

This is different from OpenGL (and frameworks based on it). If your prior experience is with these, you may find that the textures on your meshes are flipped vertically. You will have to reexport / regenerate your meshes in the correct UV format.

This is also inconsistent with the World-coordinate system used everywhere else in Bevy, where the Y axis points up.

If the images of your 2D sprites are flipped (for whatever reason), you can correct that using Bevy's sprite-flipping feature:

commands.spawn_bundle(SpriteBundle {
    sprite: Sprite {
        flip_y: true,
        flip_x: false,
        ..Default::default()
    },
    ..Default::default()
});

Advanced Patterns

This chapter is about any non-obvious tricks, programming techniques, or other advanced uses of Bevy.

These topics are intended for people already comfortable with Bevy Programming.

They may or may not be applicable to your project. The things covered in this chapter may be controversial or not universally good practice. Form your own opinions and use at your own discretion.

Generic Systems

Bevy systems are just plain rust functions, which means they can be generic. You can add the same system multiple times, parametrized to work on different Rust types or values.

Generic over Component types

You can use the generic type parameter to specify what compnent types (and hence what entities) your system should operate on.

This can be useful when combined with Bevy states. You can do the same thing to different sets of entities depending on state.

Example: Cleanup

One straightforward use-case is for cleanup. We can make a generic cleanup system that just despawns all entities that have a certain component type. Then, trivially run it on exiting different states.

use bevy::ecs::component::Component;

fn cleanup_system<T: Component>(
    mut commands: Commands,
    q: Query<Entity, With<T>>,
) {
    for e in q.iter() {
        commands.entity(e).despawn_recursive();
    }
}

Menu entities can be tagged with cleanup::MenuExit, entities from the game map can be tagged with cleanup::LevelUnload.

We can add the generic cleanup system to our state transitions, to take care of the respective entities:

/// Marker components to group entities for cleanup
mod cleanup {
    pub struct LevelUnload;
    pub struct MenuClose;
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum AppState {
    MainMenu,
    InGame,
}

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_state(AppState::MainMenu)
        // add the cleanup systems
        .add_system_set(SystemSet::on_exit(AppState::MainMenu)
            .with_system(cleanup_system::<cleanup::MenuClose>.system()))
        .add_system_set(SystemSet::on_exit(AppState::InGame)
            .with_system(cleanup_system::<cleanup::LevelUnload>.system()))
        .run();
}

Using Traits

You can use this in combination with Traits, for when you need some sort of varying implementation/functionality for each type.

Example: Bevy's Camera Projections

(this is a use-case within Bevy itself)

Bevy has a CameraProjection trait. Different projection types like PerspectiveProjection and OrthographicProjection implement that trait, providing the correct logic for how to respond to resizing the window, calculating the projection matrix, etc.

There is a generic system fn camera_system::<T: CameraProjection + Component>, which handles all the cameras with a given projection type. It will call the trait methods when appropriate (like on window resize events).

The Bevy Cookbook Custom Camera Projection Example shows this API in action.

Using Const Generics

Now that Rust has support for Const Generics, functions can also be parametrized by values, not just types.

fn process_layer<const LAYER_ID: usize>(
    // system params
) {
    // do something for this `LAYER_ID`
}

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_system(process_layer::<1>.system())
        .add_system(process_layer::<2>.system())
        .add_system(process_layer::<3>.system())
        .run();
}

Note that these values are static / constant at compile-time. This can be a severe limitation. In some cases, when you might suspect that you could use const generics, you might realize that you actually want a runtime value.

If you need to "configure" your system by passing in some data, you could, instead, use a Resource or Local.

Note: As of Rust 1.53, support for using enum values as const generics is not yet stable. To use enums, you need Rust Nightly, and to enable the experimental/unstable feature (put this at the top of your main.rs or lib.rs):

#![feature(const_generics)]

Manual Event Clearing

Click here to download a full example file with the code from this page.


The event queue needs to be cleared periodically, so that it does not grow indefinitely and waste unbounded memory.

Bevy's default cleanup strategy is to clear events every frame, but with double buffering, so that events from the previous frame update stay available. This means that you can handle the events only until the end of the next frame after the one when they are sent.

This default works well for systems that run every frame and check for events every time, which is the typical usage pattern.

However, if you have systems that do not read events every frame, they might miss some events. Some common scenarios where this occurs are:

  • systems with an early-return, that don't read events every time they run
  • when using fixed timestep
  • systems that only run in specific states, such as if your game has a pause state
  • when using custom run criteria to control your systems

To be able to reliably manage events in such circumstances, you might want to have manual control over how long the events are held in memory.

You can replace Bevy's default cleanup strategy with your own.

To do this, simply add your event type (wrapped as Events<T>) to the app builder using .init_resource, instead of .add_event.

(.add_event is actually just a convenience method that initializes the resource and adds Bevy's built-in system (generic over your event type) for the default cleanup strategy)

You must then clear the events at your discretion. If you don't do this often enough, your events might pile up and waste memory.

use bevy::app::Events;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)

        // add the `Events<T>` resource manually
        // these events will not have automatic cleanup
        .init_resource::<Events<MySpecialEvent>>()

        // this is a regular event type with automatic cleanup
        .add_event::<MyRegularEvent>()

        // add the cleanup systems
        .add_system(my_event_manager.system())
        .run();
}

fn my_event_manager(
    mut events: ResMut<Events<MySpecialEvent>>,
) {
    // TODO: implement your custom logic
    // for deciding when to clear the events

    // clear all events like this:
    events.clear();

    // or with double-buffering
    // (this is what Bevy's default strategy does)
    events.update();

    // or drain them, if you want to iterate,
    // to access the values:
    for event in events.drain() {
        // TODO do something with each event
    }
}

Hint: generic systems could be very useful for this. You could implement your custom event management strategy as a system generic over the event type, and then add that system to your App as many times as you need, for each event type where you want to use your custom behavior.

Bevy Cookbook

This chapter shows you how to do various practical things using Bevy.

Indended as a supplement to Bevy's official examples.

The examples are written to only focus on the relevant information for the task at hand.

Only the relevant parts of the code are shown. Full compilable example files are available and linked on each page.

It is assumed that you are already familiar with Bevy Programming.

Quitting the App

Click here for the full example code.


To cleanly shut down bevy, send an AppExit event from any system:

use bevy::app::AppExit;

fn exit_system(mut exit: EventWriter<AppExit>) {
    exit.send(AppExit);
}

For prototyping, bevy provides a system you can add, to exit on pressing the Esc key:

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_system(bevy::input::system::exit_on_esc_system.system())
        .run();
}

Changing the Background Color

Relevant official examples: clear_color.


Click here for the full example code.


Use the ClearColor resource to choose the background color.

fn main() {
    App::build()
        .insert_resource(ClearColor(Color::rgb(0.4, 0.4, 0.4)))
        .add_plugins(DefaultPlugins)
        .run();
}

Show Framerate in Console

Click here for the full example code.


You can use bevy's builtin diagnostics system to print framerate (FPS) to the console, for monitoring performance.

use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_plugin(LogDiagnosticsPlugin::default())
        .add_plugin(FrameTimeDiagnosticsPlugin::default())
        .run();
}

Grabbing the Mouse

Click here for the full example code.


You can lock/release the mouse cursor using bevy's window settings API.

Here is an example that locks and hides the cursor in the primary window on mouse click and releases it when pressing Esc:

fn cursor_grab_system(
    mut windows: ResMut<Windows>,
    btn: Res<Input<MouseButton>>,
    key: Res<Input<KeyCode>>,
) {
    let window = windows.get_primary_mut().unwrap();

    if btn.just_pressed(MouseButton::Left) {
        window.set_cursor_lock_mode(true);
        window.set_cursor_visibility(false);
    }

    if key.just_pressed(KeyCode::Escape) {
        window.set_cursor_lock_mode(false);
        window.set_cursor_visibility(true);
    }
}

Setting the Window Icon

Click here for the full example code.


You might want to set a custom Window Icon. On Windows and Linux, this is the icon image shown in the window title bar (if any) and task bar (if any).

Unfortunately, Bevy does not yet provide an easy and ergonomic built-in way to do this. However, it can be done via the winit APIs.

The way shown here is quite hacky. To save on code complexity, instead of using Bevy's asset system to load the image in the background, we bypass the assets system and directly load the file using the image library.

There is some WIP on adding a proper API for this to Bevy; see PR #2268 and Issue #1031.

This example shows how to set the icon for the primary/main window, from a Bevy startup system.

use bevy::window::WindowId;
use bevy::winit::WinitWindows;
use winit::window::Icon;

fn set_window_icon(
    windows: Res<WinitWindows>,
) {
    let primary = windows.get_window(WindowId::primary()).unwrap();

    // here we use the `image` crate to load our icon data from a png file
    // this is not a very bevy-native solution, but it will do
    let (icon_rgba, icon_width, icon_height) = {
        let image = image::open("my_icon.png")
            .expect("Failed to open icon path")
            .into_rgba8();
        let (width, height) = image.dimensions();
        let rgba = image.into_raw();
        (rgba, width, height)
    };

    let icon = Icon::from_rgba(icon_rgba, icon_width, icon_height).unwrap();

    primary.set_window_icon(Some(icon));
}

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(set_window_icon.system())
        .run();
}

Track Assets Loading

Click here for the full example code.


You might want to use a 3rd-party library for this. See my recommendations for helper crates that can do this for you. Otherwise, this page shows you how to roll your own.


You might want to know when your various assets have all finished loading, and take some action, such as exiting your loading screen and starting gameplay.

To do this, we can convert our various handles into HandleUntypeds, so we can add them all into a single collection.

We can then ask the AssetServer about the loading state of the collection.

struct AssetsLoading(Vec<HandleUntyped>);

fn setup(server: Res<AssetServer>, mut loading: ResMut<AssetsLoading>) {
    // we can have different asset types
    let font: Handle<Font> = server.load("my_font.ttf");
    let menu_bg: Handle<Texture> = server.load("menu.png");
    let scene: Handle<Scene> = server.load("level01.gltf#Scene0");

    // add them all to our collection for tracking
    loading.0.push(font.clone_untyped());
    loading.0.push(menu_bg.clone_untyped());
    loading.0.push(scene.clone_untyped());
}

fn check_assets_ready(
    mut commands: Commands,
    server: Res<AssetServer>,
    loading: Res<AssetsLoading>
) {
    use bevy::asset::LoadState;

    match server.get_group_load_state(loading.0.iter().map(|h| h.id)) {
        LoadState::Failed => {
            // one of our assets had an error
        }
        LoadState::Loaded => {
            // all assets are now ready

            // this might be a good place to transition into your in-game state

            // remove the resource to drop the tracking handles
            commands.remove_resource::<AssetsLoading>();
            // (note: if you don't have any other handles to the assets
            // elsewhere, they will get unloaded after this)
        }
        _ => {
            // NotLoaded/Loading: not fully ready yet
        }
    }
}

It is also possible to query the load state of a single asset, by passing an individual handle to AssetServer.get_load_state().

Convert cursor to world coordinates

Click here for the full example code.


Bevy does not yet provide built-in functions to help with finding out what the cursor is pointing at.

3D games

There is a good (unofficial) plugin: bevy_mod_picking.

2D games

Simple hacky solution, for a game using the default Bevy 2d orthographic camera:

/// Used to help identify our main camera
struct MainCamera;

fn setup(mut commands: Commands) {
    commands.spawn()
        .insert_bundle(OrthographicCameraBundle::new_2d())
        .insert(MainCamera);
}

fn my_cursor_system(
    // need to get window dimensions
    wnds: Res<Windows>,
    // query to get camera transform
    q_camera: Query<&Transform, With<MainCamera>>
) {
    // get the primary window
    let wnd = wnds.get_primary().unwrap();

    // check if the cursor is in the primary window
    if let Some(pos) = wnd.cursor_position() {
        // get the size of the window
        let size = Vec2::new(wnd.width() as f32, wnd.height() as f32);

        // the default orthographic projection is in pixels from the center;
        // just undo the translation
        let p = pos - size / 2.0;

        // assuming there is exactly one main camera entity, so this is OK
        let camera_transform = q_camera.single().unwrap();

        // apply the camera transform
        let pos_wld = camera_transform.compute_matrix() * p.extend(0.0).extend(1.0);
        eprintln!("World coords: {}/{}", pos_wld.x, pos_wld.y);
    }
}

Custom Camera Projection

Click here for the full example code.


Camera with a custom projection (not using one of Bevy's standard perspective or orthographic projections).

You could also use this to change the coordinate system, if you insist on using something other than Bevy's default coordinate system, for whatever reason.

Here we implement a simple orthographic projection that maps -1.0 to 1.0 to the vertical axis of the window, and respects the window's aspect ratio for the horizontal axis:

use bevy::render::camera::{Camera, CameraProjection, DepthCalculation, VisibleEntities};

struct SimpleOrthoProjection {
    far: f32,
    aspect: f32,
}

impl CameraProjection for SimpleOrthoProjection {
    fn get_projection_matrix(&self) -> Mat4 {
        Mat4::orthographic_rh(
            -self.aspect, self.aspect, -1.0, 1.0, 0.0, self.far
        )
    }

    // what to do on window resize
    fn update(&mut self, width: f32, height: f32) {
        self.aspect = width / height;
    }

    fn depth_calculation(&self) -> DepthCalculation {
        // for 2D (camera doesn't rotate)
        //DepthCalculation::ZDifference

        // otherwise
        DepthCalculation::Distance
    }
}

impl Default for SimpleOrthoProjection {
    fn default() -> Self {
        Self { far: 1000.0, aspect: 1.0 }
    }
}

fn setup(mut commands: Commands) {
    // same components as bevy's Camera2dBundle,
    // but with our custom projection

    let projection = SimpleOrthoProjection::default();

    // Need to set the camera name to one of the bevy-internal magic constants,
    // depending on which camera we are implementing (2D, 3D, or UI).
    // Bevy uses this name to find the camera and configure the rendering.
    // Since this example is a 2d camera:

    let cam_name = bevy::render::render_graph::base::camera::CAMERA_2D;

    let mut camera = Camera::default();
    camera.name = Some(cam_name.to_string());

    commands.spawn_bundle((
        // position the camera like bevy would do by default for 2D:
        Transform::from_translation(Vec3::new(0.0, 0.0, projection.far - 0.1)),
        GlobalTransform::default(),
        VisibleEntities::default(),
        camera,
        projection,
    ));
}

fn main() {
    // need to add a bevy-internal camera system to update
    // the projection on window resizing

    use bevy::render::camera::camera_system;

    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup.system())
        .add_system_to_stage(
            CoreStage::PostUpdate,
            camera_system::<SimpleOrthoProjection>.system(),
        )
        .run();
}

Pan + Orbit Camera

Click here for the full example code.


This code is a community contribution.

Current version developed by @mirenbharta. Initial work by @skairunner.


This is a camera controller similar to the ones in 3D editors like Blender.

Use the right mouse button to rotate, middle button to pan, scroll wheel to move inwards/outwards.

/// Tags an entity as capable of panning and orbiting.
struct PanOrbitCamera {
    /// The "focus point" to orbit around. It is automatically updated when panning the camera
    pub focus: Vec3,
    pub radius: f32,
    pub upside_down: bool,
}

impl Default for PanOrbitCamera {
    fn default() -> Self {
        PanOrbitCamera {
            focus: Vec3::ZERO,
            radius: 5.0,
            upside_down: false,
        }
    }
}

/// Pan the camera with middle mouse click, zoom with scroll wheel, orbit with right mouse click.
fn pan_orbit_camera(
    windows: Res<Windows>,
    mut ev_motion: EventReader<MouseMotion>,
    mut ev_scroll: EventReader<MouseWheel>,
    input_mouse: Res<Input<MouseButton>>,
    mut query: Query<(&mut PanOrbitCamera, &mut Transform, &PerspectiveProjection)>,
) {
    // change input mapping for orbit and panning here
    let orbit_button = MouseButton::Right;
    let pan_button = MouseButton::Middle;

    let mut pan = Vec2::ZERO;
    let mut rotation_move = Vec2::ZERO;
    let mut scroll = 0.0;
    let mut orbit_button_changed = false;

    if input_mouse.pressed(orbit_button) {
        for ev in ev_motion.iter() {
            rotation_move += ev.delta;
        }
    } else if input_mouse.pressed(pan_button) {
        // Pan only if we're not rotating at the moment
        for ev in ev_motion.iter() {
            pan += ev.delta;
        }
    }
    for ev in ev_scroll.iter() {
        scroll += ev.y;
    }
    if input_mouse.just_released(orbit_button) || input_mouse.just_pressed(orbit_button) {
        orbit_button_changed = true;
    }

    for (mut pan_orbit, mut transform, projection) in query.iter_mut() {
        if orbit_button_changed {
            // only check for upside down when orbiting started or ended this frame
            // if the camera is "upside" down, panning horizontally would be inverted, so invert the input to make it correct
            let up = transform.rotation * Vec3::Y;
            pan_orbit.upside_down = up.y <= 0.0;
        }

        let mut any = false;
        if rotation_move.length_squared() > 0.0 {
            any = true;
            let window = get_primary_window_size(&windows);
            let delta_x = {
                let delta = rotation_move.x / window.x * std::f32::consts::PI * 2.0;
                if pan_orbit.upside_down { -delta } else { delta }
            };
            let delta_y = rotation_move.y / window.y * std::f32::consts::PI;
            let yaw = Quat::from_rotation_y(-delta_x);
            let pitch = Quat::from_rotation_x(-delta_y);
            transform.rotation = yaw * transform.rotation; // rotate around global y axis
            transform.rotation = transform.rotation * pitch; // rotate around local x axis
        } else if pan.length_squared() > 0.0 {
            any = true;
            // make panning distance independent of resolution and FOV,
            let window = get_primary_window_size(&windows);
            pan *= Vec2::new(projection.fov * projection.aspect_ratio, projection.fov) / window;
            // translate by local axes
            let right = transform.rotation * Vec3::X * -pan.x;
            let up = transform.rotation * Vec3::Y * pan.y;
            // make panning proportional to distance away from focus point
            let translation = (right + up) * pan_orbit.radius;
            pan_orbit.focus += translation;
        } else if scroll.abs() > 0.0 {
            any = true;
            pan_orbit.radius -= scroll * pan_orbit.radius * 0.2;
            // dont allow zoom to reach zero or you get stuck
            pan_orbit.radius = f32::max(pan_orbit.radius, 0.05);
        }

        if any {
            // emulating parent/child to make the yaw/y-axis rotation behave like a turntable
            // parent = x and y rotation
            // child = z-offset
            let rot_matrix = Mat3::from_quat(transform.rotation);
            transform.translation = pan_orbit.focus + rot_matrix.mul_vec3(Vec3::new(0.0, 0.0, pan_orbit.radius));
        }
    }
}

fn get_primary_window_size(windows: &Res<Windows>) -> Vec2 {
    let window = windows.get_primary().unwrap();
    let window = Vec2::new(window.width() as f32, window.height() as f32);
    window
}

/// Spawn a camera like this
fn spawn_camera(mut commands: Commands) {
    let translation = Vec3::new(-2.0, 2.5, 5.0);
    let radius = translation.length();

    commands.spawn_bundle(PerspectiveCameraBundle {
        transform: Transform::from_translation(translation)
            .looking_at(Vec3::ZERO, Vec3::Y),
        ..Default::default()
    }).insert(PanOrbitCamera {
        radius,
        ..Default::default()
    });
}

List All Resource Types

Click here for the full example code.


We can access the metadata stored inside Bevy ECS to learn about the types of things currently stored.

This example shows how to print a list of all types that have been added as resources.

fn print_resources(archetypes: &Archetypes, components: &Components) {
    let mut r: Vec<String> = archetypes
        .resource()
        .components()
        .map(|id| components.get_info(id).unwrap())
        // get_short_name removes the path information
        // i.e. `bevy_audio::audio::Audio` -> `Audio`
        // if you want to see the path info replace
        // `TypeRegistration::get_short_name` with `String::from`
        .map(|info| TypeRegistration::get_short_name(info.name()))
        .collect();

    // sort list alphebetically
    r.sort();
    r.iter().for_each(|name| println!("{}", name));
}

Note that this does not give you a comprehensive list of every Bevy-provided type that is useful as a resource. It lists the types of all the resources currently added to the app (by all registered plugins, your own, etc.).

Bevy on Different Platforms

This chapter is a collection of platform-specific information, about using Bevy with different operating systems or environments.

If you know of anything that would be nice to add, please file an issue on GitHub or ping me on Discord (@Ida Iyes#0981).


Bevy trivially works out-of-the-box on the major desktop operating systems: Linux, macOS, Windows. No special configuration is required.

See the following pages for specific tips/advice when developing for the desktop platforms:

Bevy aims to also make it easy to target other platforms, such as web browsers (via WebAssembly), mobile (Android and iOS), and game consoles. Your Bevy code can be the same for all platforms, with differences only in the build process and environment setup.

However, that vision is not fully met yet. Currently, support for non-desktop platforms is limited, and requires more complex configuration:

  • Web Browsers: It is possible to make a web game, with some caveats.
  • Mobile: support is minimal and broken. It will build, but may or may not run. Expect to immediately encounter major issues.
  • Game consoles: support is still completely non-existent yet.

If you are interested in these other platforms and you'd like to help improve Bevy's cross-platform support, your contributions would be greatly welcomed!

Linux Desktop

If you have any additional Linux-specific knowledge, please help improve this page!

Create Issues or PRs on GitHub.


Desktop Linux is one of the best-supported platforms by Bevy.

GPU Drivers

Support for the Vulkan graphics API is required to run Bevy apps. You (and your users) must ensure that you have compatible hardware and drivers installed.

On most modern distributions and computers, this should be no problem.

If Bevy apps refuse to run and print an error to the console about not being able to find a compatible GPU, the problem is most likely with the Vulkan components of your graphics driver not being installed correctly. You may need to install some extra packages or reinstall your graphics drivers. Check with your Linux distribution for what to do.

X11 and Wayland

As of the year 2021, the Linux desktop ecosystem is fragmented between the legacy X11 stack and the modern Wayland stack. Many distributions are switching to Wayland-based desktop environments by default.

Bevy supports both, but only X11 support is enabled by default. If you are running a Wayland-based desktop, this means your Bevy app will run in the XWayland compatibility layer.

To enable native Wayland support for Bevy, enable the wayland cargo feature:

[dependencies]
bevy = { version = "0.5", features = ["wayland"] }

Now your app will be built with support for both X11 and Wayland.

If you want to remove X11 support for whatever reason, disable the x11 cargo default feature.

You can override which display protocol to use at runtime, using an environment variable:

export WINIT_UNIX_BACKEND=x11

(to run using X11/XWayland on a Wayland desktop)

or

export WINIT_UNIX_BACKEND=wayland

(to require the use of Wayland)

macOS Desktop

If you have any additional macOS-specific knowledge, please help improve this page!

Create Issues or PRs on GitHub.


(this page is currently empty; please help!)

Windows Desktop

If you have any additional Windows-specific knowledge, please help improve this page!

Create Issues or PRs on GitHub.


Windows is one of the best-supported platforms by Bevy.

As of Bevy 0.5, both the MSVC and the GNU compiler toolchains should work.

Distributing Your App

The EXE built with cargo build can work standalone without any extra files or DLLs.

Your assets folder needs be distributed alongside it. Bevy will search for it in the same directory as the EXE on the user's computer.

The easiest way to give your game to other people to play is to put them together in a ZIP file. If you use some other method of installation, install the assets folder and the EXE to the same path.

Creating an icon for your app

There are two places where you might want to put your application icon:

  • The EXE file (how it looks in the file explorer)
  • The window at runtime (how it looks in the taskbar and the window title bar)

Setting the EXE icon

(adapted from here)

The EXE icon can be set using a cargo build script.

Add a build dependency of embed_resources to your Cargo.toml allow embedding assets into your compiled executables

[build-dependencies]
embed-resource = "1.6.3"

Create a build.rs file in your project folder:

extern crate embed_resource;

fn main() {
    let target = std::env::var("TARGET").unwrap();
    if target.contains("windows") {
        embed_resource::compile("icon.rc");
    }
}

Create a icon.rc file in your project folder:

app_icon ICON "icon.ico"

Create your icon as icon.ico in your project folder.

Setting the Window Icon

See Bevy Cookbook: Setting the Window Icon.

Browser (WebAssembly)

(big thanks to @Zaszi for providing most of the information and writing the draft for this chapter)


Introduction

You can make web browser games using Bevy. This chapter will help you with the things you need to know to do it. This page gives an overview of Bevy's Web support.

Your Bevy app will be compiled for WebAssembly (WASM), which allows it to be embedded in a web page and run inside the browser.

Performance will be limited, as WebAssembly is slower than native code and does not support multithreading.

Most Bevy features work fine. A few things need 3rd-party replacements. Some additional configuration is required.

Not all 3rd-party plugins are compatible. If you need extra unofficial plugins, you will have to check if they are compatible with WASM.

Project Setup

You can configure your project so that it can be compiled for both web and desktop.

For WASM to work, it needs to be built in a special way, with accompanying JavaScript code for interfacing with the browser. This can be handled and auto-generated using some extra tools alongside cargo.

This chapter demonstrates two alternative ways for how to set up your project: using wasm-pack or using Cargo Make. You can choose whichever way you like better (or come up with your own).

You will need a website with some HTML and JavaScript to load and run your game. This could be just a minimal shim. It is shown as part of the setup guides.

To deploy, you will need a server to host your website for other people to access. You could use GitHub's hosting service: GitHub Pages.

When users load your site to play your game, their browser will need to download the files. Optimizing for size is important, so that your game can load fast and not waste data bandwidth.

Rendering

Bevy's official rendering is based on WebGPU. This is a new, modern graphics API, similar to Metal and Vulkan, but designed for browsers and to be cross-platform. Ironically, WebGPU is not yet supported in web browsers, only for native apps.

This means that the official bevy_wgpu crate needs to be replaced. bevy_webgl2 is an unofficial drop-in replacement that works in current browsers, using WebGL2.

Audio

Bevy's built-in audio does not work in the web browser. However, it is very limited in functionality anyway; it is not very useful for desktop games either.

Instead, you could use the unofficial bevy_kira_audio plugin, which is an integration with the Kira sound engine. It is a more feature-rich replacement for Bevy's audio. It works both on the web and the desktop platforms.

Input Devices

Gamepads/joysticks/controllers are not supported in Web browsers.

Additional Caveats

Some minor extra configuration is needed to be able to:

Project Setup (wasm-pack)

Make sure to first read the overview page. It contains important general information.


wasm-pack is a simple and lightweight way to build your application for WASM. Compared to the Cargo Make approach, it is not as automated, but you may prefer the simpler setup and configuration.

This page will show you how to configure a single Bevy project that can be compiled for both Web and Desktop (Linux/Mac/Windows). If you only care about Web, you could simplify the configuration shown here.

Prerequisites

You need to have support for the WASM target for the Rust compiler. If you are using rustup to manage your Rust installation, you can install it like this:

rustup target add wasm32-unknown-unknown

You also need to install wasm-pack. You can do that using cargo:

cargo install wasm-pack

Cargo

The cross-platform setup requires the new cargo resolver, which was added in Rust 1.51.

Add the following options to your Cargo.toml:

[package]
resolver = "2"

[lib]
crate-type = ["cdylib", "rlib"]

# Dependencies for all targets go here.
[dependencies]
wasm-bindgen = "0.2"

# Dependencies for native only.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
bevy = {version = "0.5", default-features = false, features = ["bevy_wgpu", "bevy_winit", "render", "x11"]}

# Dependencies for WASM only.
[target.'cfg(target_arch = "wasm32")'.dependencies]
bevy = {version = "0.5", default-features = false, features = ["bevy_winit", "render"]}
bevy_webgl2 = "0.5"

Main Source File

When using wasm-pack, it is treating the project as a "library" rather than an "application". This means that your main file is lib.rs instead of main.rs.

In src/lib.rs, write your "main function" as follows:

use bevy::prelude::*;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn run() {
    let mut app = App::build();
    
    app.add_plugins(DefaultPlugins);

    // when building for Web, use WebGL2 rendering
    #[cfg(target_arch = "wasm32")]
    app.add_plugin(bevy_webgl2::WebGL2Plugin);
    
    // TODO: add all your other stuff to `app` as usual

    app.run();
}

To be able to also build the game as a normal desktop application, we also need to add a trivial main.rs. It just calls the function we defined above:

fn main() {
    myproject::run();
}

Web Page

To display and run your game, you need to create a website. As the bare minimum, you can create a simple HTML file (call it index.html) containing:

<html>
<head>
<title>My Bevy Project</title>
</head>
<body>
<script type="module">
  import init from "./myproject.js";
  init("./myproject_bg.wasm").then(function (wasm) {
    wasm.run();
  });
</script>
</body>
</html>

(make sure to rename the myproject.js and myproject_bg.wasm to the correct file names for your project; see below)

The above HTML is enough for you to test your game. You can enhance and extend it as much as you want, to make a fancy website.

Building and Running

You can build your project like this:

wasm-pack build --target web --release

This will produce output files in pkg/:

  • myproject_bg.wasm
  • myproject.js

To create the website that will display your game, you need to place the above files, your assets, and the HTML file, together in the same folder.

You can then open the HTML file in a web browser, and enjoy your game!

Project Setup (Cargo Make)

Make sure to first read the overview page. It contains important general information.


Cargo Make is a cargo extension that allows defining custom tasks to automate whatever you need. Compared to the wasm-pack approach, it is more automated and convenient for development, but requires more complicated configuration.

This page will show you how to set up a project that supports these features:

  • Can be built for both Web and Desktop (Linux/Mac/Windows).
  • Can easily run either version with a single command.
    • For the web version, it runs a local server and opens in your browser.

To do this easily, we will use bevy_webgl2_app_template. Remember that Cargo Make is a flexible tool, and templates are opinionated. You could write your own configuration, but that is outside the scope of this guide.

The only reason why this page is so short, is because we rely on the template to give us the complicated configuration that Cargo Make requires.

Prerequisites

You need to have support for the WASM target for the Rust compiler. If you are using rustup to manage your Rust installation, you can install it like this:

rustup target add wasm32-unknown-unknown

You also need to install cargo-make. You can do that using cargo:

cargo install cargo-make

Supporting Files

Copy Makefile.toml and index.html from bevy_webgl2_app_template into your project.

Cargo

In your Cargo.toml, we must configure the features and dependencies, in the way that is expected by the template:

[features]
default = [
  "bevy/bevy_gltf",
  "bevy/bevy_winit",
  "bevy/render",
  "bevy/png",
]

native = [
  "bevy/bevy_wgpu",
]

web = [
  "bevy_webgl2"
]

[dependencies]
bevy = {version="0.5.0", default-features=false}
bevy_webgl2 = {version="0.5.0", optional=true}

Main Source File

In src/main.rs, write your main function as follows:

use bevy::prelude::*;

fn main() {
    let mut app = App::build();
    
    app.add_plugins(DefaultPlugins);

    // when building for Web, use WebGL2 rendering
    #[cfg(target_arch = "wasm32")]
    app.add_plugin(bevy_webgl2::WebGL2Plugin);
    
    // TODO: add all your other stuff to `app` as usual

    app.run();
}

Building and Running

To run the web version of your game, you can just run:

cargo make serve

It will start a web server on localhost:4000 and open the game in your browser.

To run the native desktop version of your game:

cargo make run

To simply build the game:

cargo make --profile release build-web
cargo make --profile release build-native

Deploying

To publish your game, you need to create a website. As the bare minimum, you can create a simple HTML file (call it index.html) containing:

<html>
<head>
<title>My Bevy Project</title>
</head>
<body>
<script type="module">
  import init from "./wasm.js";
  init();
</script>
</body>
</html>

The above HTML is just enough to display your game. You can enhance and extend it as much as you want, to make a fancy website.

Upload the following files in the same directory tree as they're generated in, to your web server:

|_ target
|  |_ wasm32-unknown-unknown
|  |  |_release
|  |    |_ myproject.d
|  |    |_ myproject.wasm
|  |_ wasm.js
|  |_ wasm_bg.wasm
|_ index.html

Panic Messages

Unless we do something about it, you will not be able to see Rust panic messages when running in a web browser. This means that, if your game crashes, you will not know why.

To fix this, we can set up a panic hook that will cause the messages to appear in the browser console, using the console_error_panic_hook crate.

Add the crate to your dependencies in Cargo.toml:

[dependencies]
console_error_panic_hook = "0.1"

At the start of your main function (either fn run() in lib.rs when using wasm-pack, or fn main() in main.rs), before doing anything else, add this:

    // When building for WASM, print panics to the browser console
    #[cfg(target_arch = "wasm32")]
    console_error_panic_hook::set_once();

Random Number Generation

The typical way to generate random numbers in Rust is using the rand crate. However, it requires some extra configuration to work with WASM. This is because, typically, randomness comes from the operating system. In a web app, it needs to come from the browser.

The rand developers decided not to support this directly, but instead require us to set a feature flag on the getrandom crate, that they depend on.

To use random numbers in your project, add this to your dependencies in Cargo.toml:

[dependencies]
rand = "0.8"
getrandom = { version = "0.2", features = ["js"] }

Note that you must ensure that all the versions are compatible. getrandom 0.2 is the version that is needed for both rand 0.8 and bevy 0.5. Using other versions may result in you having multiple instances of the getrandom crate in your dependency tree!

Optimize for Size

When serving a WASM binary, the smaller it is, the faster the browser can download it. Faster downloads means faster page load times and less data bandwidth use, and that means happier users.

This page gives some suggestions for how to make your WASM files smaller.

Do not prematurely optimize! You probably don't need small WASM files during development, and many of these techniques can get in the way of your workflow! They may come at the cost of longer compile times or less debuggability.

Depending on the nature of your application, your mileage may vary, and performing measurements of binary size and execution speed is recommended.

Twiggy is a code size profiler for WASM binaries, which you can use to make measurements.

For additional information and more techniques, refer to the Code Size chapter in the Rust WASM book.

In Cargo.toml, add the following:

[profile.release]
lto = "thin"

LTO tells the compiler to optimize all code together, considering all crates as if they were one. It may be able to inline and prune functions much more aggressively.

This typically results in smaller size and better performance, but do measure to confirm. Sometimes, the size can actually be larger.

The downside here is that compilation will take much longer.

Compiling for size instead of speed

You can change the optimization profile of the compiler, to tell it to prioritize small output size, rather than performance.

(although in some rare cases, optimizing for size can actually improve speed)

In Cargo.toml, add one of the following:

[profile.release]
opt-level = 's'
[profile.release]
opt-level = 'z'

These are two different profiles for size optimization. Usually, z produces smaller files than s, but sometimes it can be the opposite. Measure to confirm which one works better for you.

Use the wasm-opt tool

The binaryen toolkit is a set of extra tools for working with WASM. One of them is wasm-opt. It goes much further than what the compiler can do, and can be used to further optimize for either speed or size:

# Optimize for size (s profile).
wasm-opt -Os -o output.wasm input.wasm

# Optimize for size (z profile).
wasm-opt -Oz -o output.wasm input.wasm

# Optimize aggressively for speed.
wasm-opt -O3 -o output.wasm input.wasm

Note: wasm-pack performs this partially by default.

Use the wee-alloc memory allocator

You can replace Rust's default memory allocator with wee-alloc, which is slower, but is less than a single kilobyte in size.

This may result in a significant performance hit. If your game runs fast enough with it, the smaller download size may be more important.

In Cargo.toml, add the following:

[dependencies]
wee_alloc = "0.4"

And now add the following to main.rs:


#![allow(unused)]
fn main() {
#[cfg(target_arch = "wasm32")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
}

Do you know of more WASM size-optimization techniques? Post about them in the GitHub Issue Tracker so that they can be added to this page!

Hosting on GitHub Pages

GitHub Pages is a hosting service that allows you to publish your website on GitHub's servers.

For more details, visit the official GitHub Pages documentation.

Deploying a website (like your WASM game) to GitHub pages is done by putting the files in a special branch in a GitHub repository. You could create a separate repository for this, but you could also do it from the same repository as your source code.

You will need the final website files for deployment. See the wasm-pack setup or the Cargo Make setup page, whichever is applicable to your project, for more information about that.

Create an empty branch in your git repository:

git checkout --orphan web
git reset --hard

You should now be in an empty working directory.

Put all files necessary for hosting, including your HTML, WASM, JavaScript, and assets files, and commit them into git:

git add *
git commit

(or better, manually list your files in the above command, in place of the * wildcard)

Push your new branch to GitHub:

git push -u origin web --force

In the GitHub Web UI, go to the repository settings, go to the "GitHub Pages" section, then under "Source" pick the branch "web" and the / (root) folder. Then click "Save".

Wait a little bit, and your site should become available at https://your-name.github.io/your-repo.

Contributing

Be civil. If you need a code of conduct, have a look at Bevy's.

If you have any suggestions for the book, such as ideas for new content, or if you notice anything that is incorrect or misleading, please file issues in the GitHub repository!

Contributing Code

If you simply want to contribute code examples to the book, feel free to make a PR. I can take care of writing the book text / page that your code will be displayed on.

Cookbook Examples

The code for cookbook examples should be provided as a full, runnable, example file, under src/code/examples. The book page will only show the relevant parts of the code, without unnecessary boilerplate.

Always use mdbook anchor syntax, not line numbers, to denote the parts of the code to be shown on the page.

Credits

If you contribute a cookbook example, I will credit you in the book by your github username with a link to the PR. Please let me know if you prefer not to be credited, or if you would like to be credited in another way (but no commercial self-promotion allowed).

Contributing Book Text

I do not directly merge book text written by other people. This is because I want the book to follow a consistent editorial style.

If you would like to write new content for the book, feel free to make a PR with the content to be included, but note that it will likely not be preserved exactly as you wrote it.

I will likely merge it into a temporary branch and then edit or rewrite it as I see fit, for publishing into the book.

Licensing

To avoid complications with copyright and licensing, you agree to provide any contributions you make to the project under the MIT-0 No Attribution License.

Note that this allows your work to be relicensed without preserving your copyright.

As described previously, the actual published content in the book will be my own derivative work based on your contributions. I will license it consistently with the rest of the book; see: License.

Bevy version

Content written for the current Bevy release, is accepted for the main branch of the book.

Content written for new developments in Bevy's main branch, is accepted for the next branch of the book, in preparation for the next upcoming Bevy release.

Style Guidelines

Aim for simplicity and minimalism. Do not include things irrelevant to getting the point across.

"Perfection is achieved not when there is nothing more to add, but when there is nothing more to remove."

Don't forget to point out potential gotchas and other relevant practical considerations.

Try to use the most common/standard terminology and keywords, to make things easy to find. Don't come up with new/extra terminology of your own.

Avoid repeating information found elsewhere in the book, prefer linking to it instead.

Code Style

Code snippets in the Cheatsheet should be as concise as possible. Feel free to use meaningless placeholder names; don't try to make them "realistic".

Code in other sections, however, should aim to "look realistic", as to illustrate what the feature might actually be used for.

Avoid long lines of code, to keep it readable on small screens.

Use reasonable formatting that does not deviate much from the common conventions used by the Rust language community. I don't enforce it strictly; there is no need to use rustfmt. If deviating from those standards allows for the code to be presented better in the context of the book, then doing so is preferable.

Text Style

Make it easy to read.

  • Be brief. Try to cover all important information without verbose explanations.
  • Prefer simple English with short sentences.
  • Avoid information overload:
    • Split things into short paragraphs.
    • Avoid introducing many (even if related) topics at the same time.
    • Cover advanced usage separately from the basics.