Migration Guides

Migration Guide: 0.7 to 0.8

Before migrating make sure to run rustup update

Bevy relies heavily on improvements in the Rust language and compiler. As a result, the Minimum Supported Rust Version (MSRV) is "the latest stable release" of Rust.

Camera Driven Rendering #

This is a very complicated change and it is recommended to read the linked PRs for more details

// old 3d perspective camera

// new 3d perspective camera
// old 2d orthographic camera

// new 2d orthographic camera
// old 3d orthographic camera

// new 3d orthographic camera
commands.spawn_bundle(Camera3dBundle {
    projection: OrthographicProjection {
        scale: 3.0,
        scaling_mode: ScalingMode::FixedVertical(5.0),

UI no longer requires a dedicated camera. UiCameraBundle has been removed. Camera2dBundle and Camera3dBundle now both default to rendering UI as part of their own render graphs. To disable UI rendering for a camera, disable it using the UiCameraConfig component:

    .insert(UiCameraConfig {
        show_ui: false,
// 0.7
camera.world_to_screen(transform, world_position);

// 0.8
camera.world_to_viewport(transform, world_position);

Visibilty Inheritance, universal ComputedVisibility and RenderLayers support #

Visibility is now propagated into children in a similar way to Transform. Root elements of a hierarchy must now contain Visibility and ComputedVisibility for visibility propagation to work.

SpatialBundle and VisibilityBundle have been added for convenience. If you were using a TransformBundle you should probably be using a SpatialBundle now.

If you were previously reading Visibility::is_visible as the "actual visibility" for sprites or lights, use ComputedVisibility::is_visible() instead:

// 0.7
fn system(query: Query<&Visibility>) {
  for visibility in query.iter() {
    if visibility.is_visible {
       info!("found visible entity");

// 0.8
fn system(query: Query<&ComputedVisibility>) {
  for visibility in query.iter() {
    if visibility.is_visible() {
       info!("found visible entity");

Use Affine3A for GlobalTransform to allow any affine transformation #

GlobalTransform fields have changed

  • Replace global_transform.translation by global_transform.translation() (For other fields, use the compute_transform method)
  • GlobalTransform do not support non-linear scales anymore, we'd like to hear from you if it is an inconvenience for you
  • If you need the scale, rotation or translation property you can now use global_transform.to_scale_rotation_translation()
// 0.7
let transform = Transform::from(*global_transform);

// 0.8
let (scale, rotation, translation) = global_transform.to_scale_rotation_translation();

Add a SceneBundle to spawn a scene #

// 0.7

// 0.8
commands.spawn_bundle(SceneBundle {
    scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"),

The scene will be spawned as a child of the entity with the SceneBundle

Make ScalingMode more flexible #

Adds ability to specify scaling factor for WindowSize, size of the fixed axis for FixedVertical and FixedHorizontal and a new ScalingMode that is a mix of FixedVertical and FixedHorizontal

Allow closing windows at runtime #

bevy::input::system::exit_on_esc_system has been removed. Use bevy::window::close_on_esc instead.

CloseWindow has been removed. Use Window::close instead. The Close variant has been added to WindowCommand. Handle this by closing the relevant window.

Make RunOnce a non-manual System impl #

The run criterion RunOnce, which would make the controlled systems run only once, has been replaced with a new run criterion function ShouldRun::once. Replace all instances of RunOnce with ShouldRun::once.

Move system_param fetch struct into anonymous scope to avoid name collisions #

For code that was using a system param's fetch struct, such as EventReader's EventReaderState, the fetch struct can now be identified via the SystemParam trait associated type Fetch, e.g. for EventReader<T> it can be identified as <EventReader<'static, 'static, T> as SystemParam>::Fetch

Task doesn't impl Component #

If you need a Task to be a Component you should use a wrapper type.

// 0.7
fn system(mut commands: Commands, thread_pool: Res<AsyncComputeTaskPool>) {
    let task = thread_pool.spawn(async move {
        // Complicated async work

// 0.8
struct ComputeVec2(Task<Vec2>);

fn system(mut commands: Commands) {
    let thread_pool = AsyncComputeTaskPool::get();
    let task = thread_pool.spawn(async move {
        // Complicated async work

Split time functionality into bevy_time #

  • Time related types (e.g. Time, Timer, Stopwatch, FixedTimestep, etc.) should be imported from bevy::time::* rather than bevy::core::*.
  • If you were adding CorePlugin manually, you'll also want to add TimePlugin from bevy::time.
  • The bevy::core::CorePlugin::Time system label is replaced with bevy::time::TimeSystem.

Move float_ord from bevy_core to bevy_utils #

Replace imports of bevy::core::FloatOrd with bevy::utils::FloatOrd.

Move Rect to bevy_ui and rename it to UiRect #

The Rect type has been renamed to UiRect.

Rename ElementState to ButtonState #

The ElementState type has been renamed to ButtonState.

Improve docs and naming for RawWindowHandle functionality #

Renamed HasRawWindowHandleWrapper to ThreadLockedRawWindowHandleWrapper.

Migrate to encase from crevice #

Use ShaderType instead of AsStd140 and AsStd430 #

// old
struct Foo {
    a: Vec4,
    b: Mat4,

// new
struct Foo {
    a: Vec4,
    b: Mat4,

StorageBuffer #

  • removed set_body(), values(), values_mut(), clear(), push(), append()
  • added set(), get(), get_mut()

UniformVec -> UniformBuffer #

  • renamed uniform_buffer() to buffer()
  • removed len(), is_empty(), capacity(), push(), reserve(), clear(), values()
  • added set(), get()

DynamicUniformVec -> DynamicUniformBuffer #

  • renamed uniform_buffer() to buffer()
  • removed capacity(), reserve()

Make paused timers update just_finished on tick #

Timer::times_finished has been renamed to Timer::times_finished_this_tick for clarity.

Change default Image FilterMode to Linear #

Default Image filtering changed from Nearest to Linear.

// 0.7

// Nothing, nearest was the default

// 0.8

Image.sampler_descriptor has been changed to use ImageSampler instead of SamplerDescriptor.

// 0.7
texture.sampler_descriptor = SamplerDescriptor {
    address_mode_u: AddressMode::Repeat,
    address_mode_v: AddressMode::Repeat,

// 0.8
texture.sampler_descriptor = ImageSampler::Descriptor(SamplerDescriptor {
    address_mode_u: AddressMode::Repeat,
    address_mode_v: AddressMode::Repeat,

Remove .system() #

You can no longer use .system(). It was deprecated in 0.7.0. You can just remove the method call.

If you needed this for tests purposes, you can use bevy_ecs::system::assert_is_system instead.

Change gamepad.rs tuples to normal structs #

The Gamepad, GamepadButton, GamepadAxis, GamepadEvent and GamepadEventRaw types are now normal structs instead of tuple structs and have a new function. To migrate change every instantiation to use the new() function instead and use the appropriate field names instead of .0 and .1.

Remove EntityMut::get_unchecked #

Replace calls to EntityMut::get_unchecked with calls to EntityMut::get.

Replace ReadOnlyFetch with ReadOnlyWorldQuery #

The trait ReadOnlyFetch has been replaced with ReadOnlyWorldQuery along with the WorldQueryGats::ReadOnlyFetch assoc type which has been replaced with <WorldQuery::ReadOnly as WorldQueryGats>::Fetch

The trait ReadOnlyFetch has been replaced with ReadOnlyWorldQuery along with the WorldQueryGats::ReadOnlyFetch assoc type which has been replaced with <WorldQuery::ReadOnly as WorldQueryGats>::Fetch

  • Any where clauses such as QueryFetch<Q>: ReadOnlyFetch should be replaced with Q: ReadOnlyWorldQuery.
  • Any custom world query impls should implement ReadOnlyWorldQuery instead of ReadOnlyFetch

Functions update_component_access and update_archetype_component_access have been moved from the FetchState trait to WorldQuery

  • Any callers should now call Q::update_component_access(state instead of state.update_component_access (and update_archetype_component_access respectively)
  • Any custom world query impls should move the functions from the FetchState impl to WorldQuery impl

WorldQuery has been made an unsafe trait, FetchState has been made a safe trait. (I think this is how it should have always been, but regardless this is definitely necessary now that the two functions have been moved to WorldQuery)

  • If you have a custom FetchState impl make it a normal impl instead of unsafe impl
  • If you have a custom WorldQuery impl make it an unsafe impl, if your code was sound before it is going to still be sound

Fix unsoundness with Or/AnyOf/Option component access' #

Query conflicts from Or/AnyOf/Option have been fixed, and made stricter to avoid undefined behaviour. If you have new query conflicts due to this you must refactor your systems; consider using ParamSet.

Remove task_pool parameter from par_for_each(_mut) #

The task_pool parameter for Query(State)::par_for_each(_mut) has been removed. Remove these parameters from all calls to these functions.

// 0.7
fn parallel_system(
   task_pool: Res<ComputeTaskPool>,
   query: Query<&MyComponent>,
) {
   query.par_for_each(&task_pool, 32, |comp| {
        // ...

// 0.8
fn parallel_system(query: Query<&MyComponent>) {
   query.par_for_each(32, |comp| {
        // ...

If using Query or QueryState outside of a system run by the scheduler, you may need to manually configure and initialize a ComputeTaskPool as a resource in the World.

Fail to compile on 16-bit platforms #

bevy_ecs will now explicitly fail to compile on 16-bit platforms, because it is unsound on those platforms due to various internal assumptions.

There is currently no alternative, but we're open to adding support. Please file an issue to help detail your use case.

Enforce type safe usage of Assets::get #

Assets::<T>::get and Assets::<T>::get_mut now require that the passed handles are Handle<T>, improving the type safety of handles. If you were previously passing in:

  • a HandleId, use &Handle::weak(id) instead, to create a weak handle. You may have been able to store a type safe Handle instead.
  • a HandleUntyped, use &handle_untyped.typed_weak() to create a weak handle of the specified type. This is most likely to be the useful when using load_folder
  • a &str or anything not previously mentioned: assets.get(&assets.get_handle("asset/path.ron"))
  • a Handle<U> of of a different type, consider whether this is the correct handle type to store. If it is (i.e. the same handle id is used for multiple different Asset types) use Handle::weak(handle.id) to cast to a different type.

Allow higher order systems #

SystemParamFunction has changed. It was not previously part of the public API, so no migration instructions are provided. (It is now included in the public API, although you still should not implement this trait for your own types).

If possible, any custom System implementations should be migrated to use higher order systems, which are significantly less error-prone.

Research is needed into allowing this to work for more cases.

Added offset parameter to TextureAtlas::from_grid_with_padding #

Calls to TextureAtlas::from_grid_with_padding should be modified to include a new parameter, which can be set to Vec2::ZERO to retain old behaviour.

// 0.7
from_grid_with_padding(texture, tile_size, columns, rows, padding)

// 0.8
from_grid_with_padding(texture, tile_size, columns, rows, padding, Vec2::ZERO)

Split mesh shader files #

In shaders for 3D meshes:

  • #import bevy_pbr::mesh_view_bind_group -> #import bevy_pbr::mesh_view_bindings
  • #import bevy_pbr::mesh_struct -> #import bevy_pbr::mesh_types
    • NOTE: If you are using the mesh bind group at bind group index 2, you can remove those binding statements in your shader and just use #import bevy_pbr::mesh_bindings which itself imports the mesh types needed for the bindings.

In shaders for 2D meshes:

  • #import bevy_sprite::mesh2d_view_bind_group -> #import bevy_sprite::mesh2d_view_bindings
  • #import bevy_sprite::mesh2d_struct -> #import bevy_sprite::mesh2d_types
    • NOTE: If you are using the mesh2d bind group at bind group index 2, you can remove those binding statements in your shader and just use #import bevy_sprite::mesh2d_bindings which itself imports the mesh2d types needed for the bindings.

Camera Driven Viewports #

Camera::projection_matrix is no longer a public field. Use the new Camera::projection_matrix() method instead:

// 0.7
let projection = camera.projection_matrix;

// 0.8
let projection = camera.projection_matrix();

Diagnostics: meaningful error when graph node has wrong number of inputs #

Exhaustive matches on RenderGraphRunnerError will need to add a branch to handle the new MismatchedInputCount variant.

Make Reflect safe to implement #

  • Reflect derives should not have to change anything
  • Manual reflect impls will need to remove the unsafe keyword, add any() implementations, and rename the old any and any_mut to as_any and as_mut_any.
  • Calls to any/any_mut must be changed to as_any/as_mut_any

Mark mutable APIs under ECS storage as pub(crate) #

If you experienced any problems caused by this change, please create an issue explaining in detail what you were doing with those apis.

Add global init and get accessors for all newtyped TaskPools #

Thread pools don't need to be stored in a resource anymore since they are now stored globally. You can now use get() to access it.

// 0.7
fn spawn_tasks(thread_pool: Res<AsyncComputeTaskPool>) {
    // Do something with thread_pool

// 0.8
fn spawn_tasks() {
    let thread_pool = AsyncComputeTaskPool::get();
    // Do something with thread_pool

Simplify design for *Labels #

  • Any previous use of Box<dyn SystemLabel> should be replaced with SystemLabelId.
  • AsSystemLabel trait has been modified.
    • No more output generics.
    • Method as_system_label now returns SystemLabelId, removing an unnecessary level of indirection.
  • If you need a label that is determined at runtime, you can use Box::leak. Not recommended.

Move get_short_name utility method from bevy_reflect into bevy_utils #

  • added bevy_utils::get_short_name, which strips the path from a type name for convenient display.
  • removed the TypeRegistry::get_short_name method. Use the function in bevy_utils instead.

Remove dead SystemLabelMarker struct #

This struct had no internal use, docs, or intuitable external use.

It has been removed.

Add reflection for resources #

Rename ReflectComponent::add_component into ReflectComponent::insert_component.

Make reflect_partial_eq return more accurate results #

Previously, all reflect_***_partial_eq helper methods returned Some(false) when the comparison could not be performed, which was misleading. They now return None when the comparison cannot be performed.

Make RenderStage::Extract run on the render world #

The Extract RenderStage now runs on the render world (instead of the main world as before). You must use the Extract SystemParam to access the main world during the extract phase. Extract takes a single type parameter, which is any system parameter (such as Res, Query etc.). It will extract this from the main world. Note that Commands will not work correctly in Extract - it will currently silently do nothing.

// 0.7
fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
    for cloud in clouds.iter() {

// 0.8
fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
    for cloud in clouds.iter() {

You can now also access resources from the render world using the normal system parameters during Extract:

fn extract_assets(mut render_assets: ResMut<MyAssets>, source_assets: Extract<Res<MyAssets>>) {
     *render_assets = source_assets.clone();

Because extraction now runs in the render world, usage of Res<RenderWorld> in the main world, should be replaced with usage of Res<MainWorld> in the render world.

Please note that all existing extract systems need to be updated to match this new style; even if they currently compile they will not run as expected. A warning will be emitted on a best-effort basis if this is not met.

Improve Gamepad DPad Button Detection #

D-pad inputs can no longer be accessed as axes. Access them as gamepad buttons instead.

Change window position types from tuple to vec #

Changed the following fields

  • WindowCommand::SetWindowMode.resolution from (u32, u32) to UVec2
  • WindowCommand::SetResolution.logical_resolution from (f32, f32) to Vec2

Full documentation for bevy_asset #

Rename FileAssetIo::get_root_path to FileAssetIo::get_base_path

FileAssetIo::root_path() is a getter for the root_path field, while FileAssetIo::get_root_path returned the parent directory of the asset root path, which was the executable's directory unless CARGO_MANIFEST_DIR was set. This change solves the ambiguity between the two methods.

Hierarchy commandization #

The Parent and Children component fields are now private.

  • Replace parent.0 by parent.get()
  • Replace children.0 with *children
  • You can't construct Children or Parent component anymore, you can use this as a stopgap measure, which may introduce a single frame delay
pub struct MakeChildOf(pub Entity);

fn add_parent(
    mut commands: Commands,
    orphans: Query<(Entity, &MakeChildOf)>,
) {
    for (child, MakeChildOf(parent)) in &orphans {

Remove blanket Serialize + Deserialize requirement for Reflect on generic types #

.register_type for generic types like Option<T>, Vec<T>, HashMap<K, V> will no longer insert ReflectSerialize and ReflectDeserialize type data. Instead you need to register it separately for concrete generic types like so:

    .register_type_data::<Option<String>, ReflectSerialize>()
    .register_type_data::<Option<String>, ReflectDeserialize>()

Lighter no default features #

bevy_asset and bevy_scene are no longer enabled when no-default-features is used with the bevy dependency.

  • Crates that use Bevy with no-default-features will need to add these features manually if they rely on them.
bevy = { version = "0.8", default-features = false, features = [
] }

Improve ergonomics and reduce boilerplate around creating text elements #

Text::with_section was renamed to Text::from_section and no longer takes a TextAlignment as argument. Use with_alignment to set the alignment instead.

Add QueryState::get_single_unchecked_manual and its family #

Change system::QuerySingleError to query::QuerySingleError

tracing-tracy updated from 0.8.0 to 0.10.0 #

The required tracy version when using the trace-tracy feature is now 0.8.1.