Migration Guides

Draft Page

The page is still under development (Tracking Issue: 1741). This page and all pages under it will be hidden from search engines and menus!

Migration Guide: 0.14 to 0.15

Without area #

Fix asset_settings example regression #

This PR obviously requires no migration guide as this is just a bug-fix, but I believe that #15812 should mention that meta files needs updating. Proposal:

  • Asset loader name must be updated in .meta files for images. Change: loader: "bevy_render::texture::image_loader::ImageLoader", to: loader: "bevy_image::image_loader::ImageLoader", It will fix the following error: no AssetLoader found with the name 'bevy_render::texture::image_loader::ImageLoader

Remove AVIF feature #

AVIF images are no longer supported. They never really worked, and require system dependencies (libdav1d) to work correctly, so, it’s better to simply offer this support via an unofficial plugin instead as needed. The corresponding types have been removed from Bevy to account for this.

Animation #

Make AnimationPlayer::start and ::play work accordingly to documentation #

AnimationPlayer::start now correspondingly to its docs restarts a running animation. AnimationPlayer::play doesn’t reset the weight anymore.

Implement animation masks, allowing fine control of the targets that animations affect. #

  • The serialized format of animation graphs has changed with the addition of animation masks. To upgrade animation graph RON files, add mask and mask_groups fields as appropriate. (They can be safely set to zero.)

Remove TransformCurve #

There is no released version that contains this, but we should make sure that TransformCurve is excluded from the release notes for #15434 if we merge this pull request.

Impose a more sensible ordering for animation graph evaluation. #

This section is optional. If there are no breaking changes, you can delete this section.

  • If this PR is a breaking change (relative to the last release of Bevy), describe how a user might need to migrate their code to support these changes
  • Simply adding new functionality is not a breaking change.
  • Fixing behavior that was definitely a bug, rather than a questionable design choice is not a breaking change.

Implement additive blending for animation graphs. #

  • The animgraph.ron format has changed to accommodate the new additive blending feature. You’ll need to change clip fields to instances of the new AnimationNodeType enum.

Fix additive blending of quaternions #

This PR changes the implementation of Quat: Animatable, which was not used internally by Bevy prior to this release version. If you relied on the old behavior of additive quaternion blending in manual applications, that code will have to be updated, as the old behavior was incorrect.

Replace Handle<AnimationGraph> component with a wrapper #

Handle<AnimationGraph> is no longer a component. Instead, use the AnimationGraphHandle component which contains a Handle<AnimationGraph>.

Curve-based animation #

Most user code that does not directly deal with AnimationClip and VariableCurve will not need to be changed. On the other hand, VariableCurve has been completely overhauled. If you were previously defining animation curves in code using keyframes, you will need to migrate that code to use curve constructors instead. For example, a rotation animation defined using keyframes and added to an animation clip like this:

animation_clip.add_curve_to_target(
    animation_target_id,
    VariableCurve {
        keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0],
        keyframes: Keyframes::Rotation(vec![
            Quat::IDENTITY,
            Quat::from_axis_angle(Vec3::Y, PI / 2.),
            Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
            Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
            Quat::IDENTITY,
        ]),
        interpolation: Interpolation::Linear,
    },
);

would now be added like this:

animation_clip.add_curve_to_target(
    animation_target_id,
    AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
        Quat::IDENTITY,
        Quat::from_axis_angle(Vec3::Y, PI / 2.),
        Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
        Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
        Quat::IDENTITY,
    ]))
    .map(RotationCurve)
    .expect("Failed to build rotation curve"),
);

Note that the interface of AnimationClip::add_curve_to_target has also changed (as this example shows, if subtly), and now takes its curve input as an impl AnimationCurve. If you need to add a VariableCurve directly, a new method add_variable_curve_to_target accommodates that (and serves as a one-to-one migration in this regard).

For reviewers

The diff is pretty big, and the structure of some of the changes might not be super-obvious:

  • keyframes.rs became animation_curves.rs, and AnimationCurve is based heavily on Keyframes, with the adaptors also largely following suite.
  • The Curve API adaptor structs were moved from bevy_math::curve::mod into their own module adaptors. There are no functional changes to how these adaptors work; this is just to make room for the specialized reflection implementations since mod.rs was getting kind of cramped.
  • The new module gltf_curves holds the additional curve constructions that are needed by the glTF loader. Note that the loader uses a mix of these and off-the-shelf bevy_math curve stuff.
  • animatable.rs no longer holds logic related to keyframe interpolation, which is now delegated to the existing abstractions in bevy_math::curve::cores.

Allow animation clips to animate arbitrary properties. #

  • Animation keyframes are now an extensible trait, not an enum. Replace Keyframes::Translation(...), Keyframes::Scale(...), Keyframes::Rotation(...), and Keyframes::Weights(...) with Box::new(TranslationKeyframes(...)), Box::new(ScaleKeyframes(...)), Box::new(RotationKeyframes(...)), and Box::new(MorphWeightsKeyframes(...)) respectively.

App #

Remove second generic from .add_before, .add_after #

Removed second generic from PluginGroupBuilder methods: add_before and add_after.

// Before:
DefaultPlugins
    .build()
    .add_before::<WindowPlugin, _>(FooPlugin)
    .add_after::<WindowPlugin, _>(BarPlugin)

// After:
DefaultPlugins
    .build()
    .add_before::<WindowPlugin>(FooPlugin)
    .add_after::<WindowPlugin>(BarPlugin)

Remove need for EventLoopProxy to be NonSend #

EventLoopProxy has been renamed to EventLoopProxyWrapper and is now Send, making it an ordinary resource.

Before:

event_loop_system(event_loop: NonSend<EventLoopProxy<MyEvent>>) {
    event_loop.send_event(MyEvent);
}

After:

event_loop_system(event_loop: Res<EventLoopProxy<MyEvent>>) {
    event_loop.send_event(MyEvent);
}

Remove deprecated bevy_dynamic_plugin #

Dynamic plugins were deprecated in 0.14 for being unsound, and they have now been fully removed. Please consider using the alternatives listed in the bevy_dynamic_plugin crate documentation, or worst-case scenario you may copy the code from 0.14.

Allow ordering variable timesteps around fixed timesteps #

run_fixed_main_schedule is no longer public. If you used to order against it, use the new dedicated RunFixedMainLoopSystem system set instead. You can replace your usage of run_fixed_main_schedule one for one by RunFixedMainLoopSystem::FixedMainLoop, but it is now more idiomatic to place your systems in either RunFixedMainLoopSystem::BeforeFixedMainLoop or RunFixedMainLoopSystem::AfterFixedMainLoop

Old:

app.add_systems(
    RunFixedMainLoop,
    some_system.before(run_fixed_main_schedule)
);

New:

app.add_systems(
    RunFixedMainLoop,
    some_system.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop)
);

Add features to switch NativeActivity and GameActivity usage #

GameActivity is now the default activity for Android projects, replacing NativeActivity. cargo-apk has been replaced with cargo-ndk since the former is not compatible with GameActivity.

Before:

rustup target add aarch64-linux-android armv7-linux-androideabi
cargo install cargo-apk

After:

rustup target add aarch64-linux-android
cargo install cargo-ndk

Shared object files must be now built for the target architecture before launching package builds with the Gradle wrapper.

Before:

cargo apk build --package bevy_mobile_example

After:

cargo ndk -t arm64-v8a -o android_example/app/src/main/jniLibs build --package bevy_mobile_example
./android_example/gradlew build

(replace target and project name as required). Note that build output paths have changed. APK builds can be found under app/build/outputs/apk).

Android Studio may also be used.

Bevy may require the libc++_shared.so library to run on Android. This can be manually obtained from NDK source, or NDK describes a build.rs approach. A suggested solution is also presented in the Bevy mobile example.

Applications that still require NativeActivity should:

  1. disable default features in Cargo.toml
  2. re-enable all default features except android-game-activity
  3. enable the android-native-activity feature

Assets #

AssetServer LoadState API consistency #

Cleanup unneeded lifetimes in bevy_asset #

The traits AssetLoader, AssetSaver and Process traits from bevy_asset now use elided lifetimes. If you implement these then remove the named lifetime.

Replace AsyncSeek trait by AsyncSeekForward for Reader to address #12880 #

Replace all instances of AsyncSeek with AsyncSeekForward in your asset reader implementations.

bevy_asset: Improve NestedLoader API #

Code which uses bevy_asset’s LoadContext::loader / NestedLoader will see some naming changes:

  • untyped is replaced by with_unknown_type
  • with_asset_type is replaced by with_static_type
  • with_asset_type_id is replaced by with_dynamic_type
  • direct is replaced by immediate (the opposite of “immediate” is “deferred”)

Remove incorrect equality comparisons for asset load error types #

The types bevy_asset::AssetLoadError and bevy_asset::LoadState no longer support equality comparisons. If you need to check for an asset’s load state, consider checking for a specific variant using LoadState::is_loaded or the matches! macro. Similarly, consider using the matches! macro to check for specific variants of the AssetLoadError type if you need to inspect the value of an asset load error in your code.

DependencyLoadState and RecursiveDependencyLoadState are not released yet, so no migration needed,

Faster MeshletMesh deserialization #

  • Regenerate your MeshletMesh assets, as the disk format has changed, and MESHLET_MESH_ASSET_VERSION has been bumped
  • MeshletMesh fields are now private
  • MeshletMeshSaverLoad is now named MeshletMeshSaverLoader
  • The Meshlet, MeshletBoundingSpheres, and MeshletBoundingSphere types are now private
  • MeshletMeshSaveOrLoadError::SerializationOrDeserialization has been removed
  • Added MeshletMeshSaveOrLoadError::WrongFileType, match on this variant if you match on MeshletMeshSaveOrLoadError

Split TextureAtlasSources out of TextureAtlasLayout and make TextureAtlasLayout serializable #

TextureAtlasBuilder no longer stores a mapping back to the original images in TextureAtlasLayout; that functionality has been added to a new struct, TextureAtlasSources, instead. This also means that the signature for TextureAtlasBuilder::finish has changed, meaning that calls of the form:

let (atlas_layout, image) = builder.build()?;

Will now change to the form:

let (atlas_layout, atlas_sources, image) = builder.build()?;

And instead of performing a reverse-lookup from the layout, like so:

let atlas_layout_handle = texture_atlases.add(atlas_layout.clone());
let index = atlas_layout.get_texture_index(&my_handle);
let handle = TextureAtlas {
    layout: atlas_layout_handle,
    index,
};

You can perform the lookup from the sources instead:

let atlas_layout = texture_atlases.add(atlas_layout);
let index = atlas_sources.get_texture_index(&my_handle);
let handle = TextureAtlas {
    layout: atlas_layout,
    index,
};

Additionally, TextureAtlasSources also has a convenience method, handle, which directly combines the index and an existing TextureAtlasLayout handle into a new TextureAtlas:

let atlas_layout = texture_atlases.add(atlas_layout);
let handle = atlas_sources.handle(atlas_layout, &my_handle);

Export glTF skins as a Gltf struct #

  • Change GltfAssetLabel::Skin(..) to GltfAssetLabel::InverseBindMatrices(..).

Replace bevy_utils::CowArc with atomicow #

bevy_utils::CowArc has moved to a new crate called atomicow.

Audio #

Migrate audio to required components #

Replace all insertions of AudioSourceBundle, AudioBundle, and PitchBundle with the AudioPlayer component. The other components required by it will now be inserted automatically.

In cases where the generics cannot be inferred, you may need to specify them explicitly. For example:

commands.spawn(AudioPlayer::<AudioSource>(asset_server.load("sounds/sick_beats.ogg")));

Color #

Update Grid Gizmo to use Color #

This shouldn’t be adding anything that isn’t already in a migration guide? I assume as it uses impl Into<...> in the public interfaces that any users of these APIs shouldn’t have to make any code changes.

Core #

Rename bevy_core::name::DebugName to bevy_core::name::NameOrEntity #

  • Rename usages of bevy_core::name::DebugName to bevy_core::name::NameOrEntity

Cross-Cutting #

Add core and alloc over std Lints #

The MSRV is now 1.81. Please update to this version or higher.

Remove the Component trait implementation from Handle #

Handle can no longer be used as a Component. All existing Bevy types using this pattern have been wrapped in their own semantically meaningful type. You should do the same for any custom Handle components your project needs.

The Handle<MeshletMesh> component is now MeshletMesh3d.

The WithMeshletMesh type alias has been removed. Use With<MeshletMesh3d> instead.

Fix floating point math #

  • Not a breaking change
  • Projects should use bevy math where applicable

Add custom cursors #

  • CursorIcon is no longer a field in Window, but a separate component can be inserted to a window entity. It has been changed to an enum that can hold custom images in addition to system icons.
  • Cursor is renamed to CursorOptions and cursor field of Window is renamed to cursor_options
  • CursorIcon is renamed to SystemCursorIcon

Diagnostics #

Don't ignore draw errors #

If you were using RenderCommandResult::Failure to just ignore an error and retry later, use RenderCommandResult::Skip instead.

This wasn’t intentional, but this PR should also help with https://github.com/bevyengine/bevy/issues/12660 since we can turn a few unwraps into error messages now.

ECS #

Created an EventMutator for when you want to mutate an event before reading #

Users currently using ManualEventReader should use EventCursor instead. ManualEventReader will be removed in Bevy 0.16. Additionally, Events::get_reader has been replaced by Events::get_cursor.

Users currently directly accessing the Events resource for mutation should move to EventMutator if possible.

Update trigger_observers to operate over slices of data #

  • TBD

Simplify run conditions #

Some run conditions have been simplified.

// Before:
app.add_systems(Update, (
    system_0.run_if(run_once()),
    system_1.run_if(resource_changed_or_removed::<T>()),
    system_2.run_if(resource_removed::<T>()),
    system_3.run_if(on_event::<T>()),
    system_4.run_if(any_component_removed::<T>()),
));

// After:
app.add_systems(Update, (
    system_0.run_if(run_once),
    system_1.run_if(resource_changed_or_removed::<T>),
    system_2.run_if(resource_removed::<T>),
    system_3.run_if(on_event::<T>),
    system_4.run_if(any_component_removed::<T>),
));

Require &mut self for World::increment_change_tick #

The method World::increment_change_tick now requires &mut self instead of &self. If you need to call this method but do not have mutable access to the world, consider using world.as_unsafe_world_cell_readonly().increment_change_tick(), which does the same thing, but is less efficient than the method on World due to requiring atomic synchronization.

fn my_system(world: &World) {
    // Before
    world.increment_change_tick();

    // After
    world.as_unsafe_world_cell_readonly().increment_change_tick();
}

Add FilteredAccess::empty and simplify the implementation of update_component_access for AnyOf/Or #

  • The behaviour of AnyOf<()> and Or<()> has been changed to match no archetypes rather than all archetypes to naturally match the corresponding logical operation. Consider replacing them with () instead.

Track source location in change detection #

  • Added changed_by field to many internal ECS functions used with change detection when the track_change_detection feature flag is enabled. Use Location::caller() to provide the source of the function call.

Make QueryState::transmute&co validate the world of the &Components used #

  • QueryState::transmute, QueryState::transmute_filtered, QueryState::join and QueryState::join_filtered now take a impl Into<UnsafeWorldCell> instead of a &Components

Fix soudness issue with Conflicts involving read_all and write_all #

The get_conflicts method of Access now returns an AccessConflict enum instead of simply a Vec of ComponentIds that are causing the access conflict. This can be useful in cases where there are no particular ComponentIds conflicting, but instead all of them are; for example fn system(q1: Query<EntityMut>, q2: Query<EntityRef>)

Support more kinds of system params in buildable systems. #

The API for SystemBuilder has changed. Instead of constructing a builder with a world and then adding params, you first create a tuple of param builders and then supply the world.

// Before
let system = SystemBuilder::<()>::new(&mut world)
    .local::<u64>()
    .builder::<Local<u64>>(|x| *x = 10)
    .builder::<Query<&A>>(|builder| { builder.with::<B>(); })
    .build(system);

// After
let system = (
    ParamBuilder,
    LocalBuilder(10),
    QueryParamBuilder::new(|builder| { builder.with::<B>(); }),
)
    .build_state(&mut world)
    .build_system(system);

Add query reborrowing #

  • WorldQuery now has an additional shrink_fetch method you have to implement if you were implementing WorldQuery manually.

Rename Commands::register_one_shot_system -> register_system #

Commands::register_one_shot_system has been renamed to register_system.

Make QueryFilter an unsafe trait #

QueryFilter is now an unsafe trait. If you were manually implementing it, you will need to verify that the WorldQuery implementation is read-only and then add the unsafe keyword to the impl.

EntityRef/Mut get_components (immutable variants only) #

  • Renamed FilteredEntityRef::components to FilteredEntityRef::accessed_components and FilteredEntityMut::components to FilteredEntityMut::accessed_components.

Removed Type Parameters from Observer #

If you filtered for observers using Observer<A, B>, instead filter for an Observer.

Remove redundant information and optimize dynamic allocations in Table #

Table now uses ThinColumn instead of Column. That means that methods that previously returned Column, will now return ThinColumn instead.

ThinColumn has a much more limited and low-level API, but you can still achieve the same things in ThinColumn as you did in Column. For example, instead of calling Column::get_added_tick, you’d call ThinColumn::get_added_ticks_slice and index it to get the specific added tick.

Rename push children to add children #

This section is optional. If there are no breaking changes, you can delete this section.

  • If this PR is a breaking change (relative to the last release of Bevy), describe how a user might need to migrate their code to support these changes

rename any use of push_children() to the updated add_children()

Rename Add to Queue for methods with deferred semantics #

  • Commands::add and Commands::push have been replaced with Commands::queue.
  • ChildBuilder::add_command has been renamed to ChildBuilder::queue_command.

change return type of World::resource_ref to Ref #

Previously World::get_resource_ref::<T> and World::resource_ref::<T> would return a Res<T> which was inconsistent with the rest of the World API (notably resource_scope). This has been fixed and the methods now return Ref<T>.

This means it is no longer possible to get Res<T> from World. If you were relying on this, you should try using Ref<T> instead since it has the same functionality.

Before

let my_resource: Res<MyResource> = world.resource_ref();
function_taking_resource(my_resource);

fn function_taking_resource(resource: Res<MyResource>) { /* ... */ }

After

let my_resource: Ref<MyResource> = world.resource_ref();
function_taking_resource(my_resource);

fn function_taking_resource(resource: Ref<MyResource>) { /* ... */ }

Support systems that take references as input #

  • All current explicit usages of the following types must be changed in the way specified:

    • SystemId<I, O> to SystemId<In<I>, O>
    • System<In = T> to System<In = In<T>>
    • IntoSystem<I, O, M> to IntoSystem<In<I>, O, M>
    • Condition<M, T> to Condition<M, In<T>>
  • In<Trigger<E, B>> is no longer a valid input parameter type. Use Trigger<E, B> directly, instead.

Follow up to cached run_system #

  • IntoSystem::pipe and IntoSystem::map now return IntoPipeSystem and IntoAdapterSystem instead of PipeSystem and AdapterSystem. Most notably these types don’t implement System but rather only IntoSystem.

List components for QueryEntityError::QueryDoesNotMatch #

  • QueryEntityError now has a lifetime. Convert it to a custom error if you need to store it.

Rename init_component & friends #

  • World::init_component has been renamed to register_component.
  • World::init_component_with_descriptor has been renamed to register_component_with_descriptor.
  • World::init_bundle has been renamed to register_bundle.
  • Components::init_component has been renamed to register_component.
  • Components::init_component_with_descriptor has been renamed to register_component_with_descriptor.
  • Components::init_resource has been renamed to register_resource.
  • Components::init_non_send had been renamed to register_non_send.

System param validation for observers, system registry and run once #

  • RunSystemOnce::run_system_once and RunSystemOnce::run_system_once_with now return a Result<Out> instead of just Out

15540 Make World::flush_commands private #

Enable EntityRef::get_by_id and friends to take multiple ids and get multiple pointers back #

  • The following functions now return an Result<_, EntityComponentError> instead of a Option<_>: EntityRef::get_by_id, EntityMut::get_by_id, EntityMut::into_borrow_by_id, EntityMut::get_mut_by_id, EntityMut::into_mut_by_id, EntityWorldMut::get_by_id, EntityWorldMut::into_borrow_by_id, EntityWorldMut::get_mut_by_id, EntityWorldMut::into_mut_by_id

Rename observe to observe_entity on EntityWorldMut #

The observe() method on entities has been renamed to observe_entity() to prevent confusion about what is being observed in some cases.

Deprecate Events::oldest_id #

  • Change usages of Events::oldest_id to Events::oldest_event_count
  • If Events::oldest_id was used to get the actual oldest EventId::id, note that the deprecated method never reliably did that in the first place as the buffers may contain no id currently.

Allow World::entity family of functions to take multiple entities and get multiple references back #

  • World::get_entity now returns Result<_, Entity> instead of Option<_>.

    • Use world.get_entity(..).ok() to return to the previous behavior.
  • World::get_entity_mut and DeferredWorld::get_entity_mut now return Result<_, EntityFetchError> instead of Option<_>.

    • Use world.get_entity_mut(..).ok() to return to the previous behavior.
  • Type inference for World::entity, World::entity_mut, World::get_entity, World::get_entity_mut, DeferredWorld::entity_mut, and DeferredWorld::get_entity_mut has changed, and might now require the input argument’s type to be explicitly written when inside closures.

  • The following functions have been deprecated, and should be replaced as such:

    • World::many_entities -> World::entity::<[Entity; N]>

    • World::many_entities_mut -> World::entity_mut::<[Entity; N]>

    • World::get_many_entities -> World::get_entity::<[Entity; N]>

    • World::get_many_entities_dynamic -> World::get_entity::<&[Entity]>

    • World::get_many_entities_mut -> World::get_entity_mut::<[Entity; N]>

      • The equivalent return type has changed from Result<_, QueryEntityError> to Result<_, EntityFetchError>
    • World::get_many_entities_dynamic_mut -> World::get_entity_mut::<&[Entity]>

      • The equivalent return type has changed from Result<_, QueryEntityError> to Result<_, EntityFetchError>
    • World::get_many_entities_from_set_mut -> World::get_entity_mut::<&EntityHashSet>

      • The equivalent return type has changed from Result<Vec<EntityMut>, QueryEntityError> to Result<EntityHashMap<EntityMut>, EntityFetchError>. If necessary, you can still convert the EntityHashMap into a Vec.

Deprecate get_or_spawn #

If you are given an Entity and you want to do something with it, use Commands.entity(...) or World.entity(...). If instead you want to spawn something use Commands.spawn(...) or World.spawn(...). If you are not sure if an entity exists, you can always use get_entity and match on the Option<...> that is returned.

bevy_ecs: Special-case Entity::PLACEHOLDER formatting #

The Debug and Display impls for Entity now return PLACEHOLDER for the Entity::PLACEHOLDER constant. If you had any code relying on these values, you may need to account for this change.

Minimal Bubbling Observers #

  • Manual implementations of Event should add associated type Traverse = TraverseNone and associated constant AUTO_PROPAGATE = false;
  • Trigger::new has new field propagation: &mut Propagation which provides the bubbling state.
  • ObserverRunner now takes the same &mut Propagation as a final parameter.

Change ReflectMapEntities to operate on components before insertion #

  • Consumers of ReflectMapEntities will need to call map_entities on values prior to inserting them into the world.
  • Implementors of MapEntities will need to remove the mappings method, which is no longer needed for ReflectMapEntities and has been removed from the trait.

Bubbling observers traversal should use query data #

Update implementations of Traversal.

Migrate bevy picking #

This API hasn’t shipped yet, so I didn’t bother with a deprecation. However, for any crates tracking main the changes are as follows:

Previous api:

commands.insert(PointerBundle::new(PointerId::Mouse));
commands.insert(PointerBundle::new(PointerId::Mouse).with_location(location));

New api:

commands.insert(PointerId::Mouse);
commands.insert((PointerId::Mouse, PointerLocation::new(location)));

feat: Add World::get_reflect() and World::get_reflect_mut() #

No breaking changes, but users can use the new methods if they did it manually before.

Use crate: disqualified #

Replace references to bevy_utils::ShortName with disqualified::ShortName.

Migrate visibility to required components #

Replace all insertions of VisibilityBundle with the Visibility component. The other components required by it will now be inserted automatically.

Migrate fog volumes to required components #

Replace all insertions of FogVolumeBundle with the Visibility component. The other components required by it will now be inserted automatically.

Migrate meshes and materials to required components #

Asset handles for meshes and mesh materials must now be wrapped in the Mesh2d and MeshMaterial2d or Mesh3d and MeshMaterial3d components for 2D and 3D respectively. Raw handles as components no longer render meshes.

Additionally, MaterialMesh2dBundle, MaterialMeshBundle, and PbrBundle have been deprecated. Instead, use the mesh and material components directly.

Previously:

commands.spawn(MaterialMesh2dBundle {
    mesh: meshes.add(Circle::new(100.0)).into(),
    material: materials.add(Color::srgb(7.5, 0.0, 7.5)),
    transform: Transform::from_translation(Vec3::new(-200., 0., 0.)),
    ..default()
});

Now:

commands.spawn((
    Mesh2d(meshes.add(Circle::new(100.0))),
    MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))),
    Transform::from_translation(Vec3::new(-200., 0., 0.)),
));

If the mesh material is missing, a white default material is now used. Previously, nothing was rendered if the material was missing.

The WithMesh2d and WithMesh3d query filter type aliases have also been removed. Simply use With<Mesh2d> or With<Mesh3d>.

Migrate motion blur, TAA, SSAO, and SSR to required components #

MotionBlurBundle, TemporalAntiAliasBundle, ScreenSpaceAmbientOcclusionBundle, and ScreenSpaceReflectionsBundle have been deprecated in favor of the MotionBlur, TemporalAntiAliasing, ScreenSpaceAmbientOcclusion, and ScreenSpaceReflections components instead. Inserting them will now also insert the other components required by them automatically.

Migrate cameras to required components #

Camera2dBundle and Camera3dBundle have been deprecated in favor of Camera2d and Camera3d. Inserting them will now also insert the other components required by them automatically.

Synchronize removed components with the render world #

The retained render world notes should be updated to explain this edge case and SyncComponentPlugin

Migrate reflection probes to required components #

ReflectionProbeBundle has been deprecated in favor of inserting the LightProbe and EnvironmentMapLight components directly. Inserting them will now automatically insert Transform and Visibility components.

Migrate bevy_transform to required components #

Replace all insertions of GlobalTransform and/or TransformBundle with Transform alone.

Deprecate SpatialBundle #

SpatialBundle is now deprecated, insert Transform and Visibility instead which will automatically insert all other components that were in the bundle. If you do not specify these values and any other components in your spawn/insert call already requires either of these components you can leave that one out.

before:

commands.spawn(SpatialBundle::default());

after:

commands.spawn((Transform::default(), Visibility::default());

Gizmos #

Making bevy_render an optional dependency for bevy_gizmos #

No user-visible changes needed from the users.

Consistency between Wireframe2d and Wireframe #

  • Wireframe2dConfig.default_color type is now Color instead of Srgba. Use .into() to convert between them.
  • Wireframe2dColor.color type is now Color instead of Srgba. Use .into() to convert between them.

Fix Gizmos warnings and doc errors when a subset of features are selected #

There shouldn’t be any reason to migrate, although if for some reason you use GizmoMeshConfig and bevy_render but not bevy_pbr or bevy_sprite (such that it does nothing), then you will get an error that it no longer exists.

Fix arc_2d Gizmos #

  • users have to adjust their usages of arc_2d:
    • before:
arc_2d(
  pos,
  angle,
  arc_angle,
  radius,
  color
)
  • after:
arc_2d(
  // this `+ arc_angle * 0.5` quirk is only if you want to preserve the previous behavior 
  // with the new API.
  // feel free to try to fix this though since your current calls to this function most likely
  // involve some computations to counter-act that quirk in the first place
  Isometry2d::new(pos, Rot2::radians(angle + arc_angle * 0.5),
  arc_angle,
  radius,
  color
)

Use Isometry in bevy_gizmos wherever we can #

The gizmos methods function signature changes as follows:

  • 2D

    • if it took position & rotation_angle before -> Isometry2d::new(position, Rot2::radians(rotation_angle))
    • if it just took position before -> Isometry2d::from_translation(position)
  • 3D

    • if it took position & rotation before -> Isometry3d::new(position, rotation)
    • if it just took position before -> Isometry3d::from_translation(position)

Improve the gizmo for Plane3d, reusing grid #

The optional builder methods on


gizmos.primitive_3d(&Plane3d { }, ...);

changed from

  • segment_length
  • segment_count
  • axis_count

to

  • cell_count
  • spacing

Switch rotation & translation in grid gizmos #

  • Users might have to double check their already existing calls to all the grid methods. It should be more intuitive now though.

Make TrackedRenderPass::set_vertex_buffer aware of slice size #

  • TrackedRenderPass::set_vertex_buffer function has been modified to update vertex buffers when the same buffer with the same offset is provided, but its size has changed. Some existing code may rely on the previous behavior, which did not update the vertex buffer in this scenario.

Hierarchy #

Optimize transform propagation #

This change does not introduce any breaking changes. Users of the Bevy engine will automatically benefit from this performance improvement without needing to modify their code.

Only propagate transforms entities with GlobalTransforms. #

  • To avoid surprising performance pitfalls, Transform / GlobalTransform propagation is no longer performed down through hierarchies where intermediate parent are missing a GlobalTransform. To restore the previous behavior, add GlobalTransform::default to intermediate entities.

Input #

Remove ReceivedCharacter #

ReceivedCharacter was deprecated in 0.14 due to winit reworking their keyboard system. It has now been fully removed. Switch to using KeyboardInput instead.

// 0.14
fn listen_characters(events: EventReader<ReceivedCharacter>) {
    for event in events.read() {
        info!("{}", event.char);
    }
}

// 0.15
fn listen_characters(events: EventReader<KeyboardInput>) {
    for event in events.read() {
        // Only check for characters when the key is pressed.
        if !event.state.is_pressed() {
            continue;
        }

        // Note that some keys such as `Space` and `Tab` won't be detected as a character.
        // Instead, check for them as separate enum variants.
        match &event.logical_key {
            Key::Character(character) => {
                info!("{} pressed.", character);
            },
            Key::Space => {
                info!("Space pressed.");
            },
            _ => {},
        }
    }
}

Implement gamepads as entities #

Gamepad input is no longer accessed using resources, instead they are entities and are accessible using the Gamepad component as long as the gamepad is connected.

Gamepads resource has been deleted, instead of using an internal id to identify gamepads you can use its Entity. Disconnected gamepads will NOT be despawned. Gamepad components that don’t need to preserve their state will be removed i.e. Gamepad component is removed, but GamepadSettings is kept. Reconnected gamepads will try to preserve their Entity id and necessary components will be re-inserted.

GamepadSettings is no longer a resource, instead it is a component attached to the Gamepad entity.

Axis, Axis and ButtonInput methods are accessible via Gamepad component.

fn gamepad_system(
-   gamepads: Res<Gamepads>,
-   button_inputs: Res<ButtonInput<GamepadButton>>,
-   button_axes: Res<Axis<GamepadButton>>,
-   axes: Res<Axis<GamepadAxis>>,
+   gamepads: Query<&Gamepad>
) {
    for gamepad in gamepads.iter() {
-      if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
+      if gamepad.just_pressed(GamepadButton::South) {
            println!("just pressed South");
        } 
         
-      let right_trigger = button_axes
-           .get(GamepadButton::new(
-               gamepad,
-               GamepadButtonType::RightTrigger2,
-           ))
-           .unwrap();
+      let right_trigger = gamepad.get(GamepadButton::RightTrigger2).unwrap();
        if right_trigger.abs() > 0.01 {
            info!("RightTrigger2 value is {}", right_trigger);      
        }

-        let left_stick_x = axes
-           .get(GamepadAxis::new(gamepad, GamepadAxisType::LeftStickX))
-           .unwrap();
+       let left_stick_x = gamepad.get(GamepadAxis::LeftStickX).unwrap();
        if left_stick_x.abs() > 0.01 {
            info!("LeftStickX value is {}", left_stick_x);        
        }
    }
}

Picking event ordering #

For users switching from bevy_mod_picking to bevy_picking:

  • Instead of adding an On<T> component, use .observe(|trigger: Trigger<T>|). You may now apply multiple handlers to the same entity using this command.
  • Pointer interaction events now have semi-deterministic ordering which (more or less) aligns with the order of the raw input stream. Consult the docs on bevy_picking::event::pointer_events for current information. You may need to adjust your event handling logic accordingly.
  • PointerCancel has been replaced with Pointer<Canceled>, which now has the semantics of an OS touch pointer cancel event.
  • InputMove and InputPress have been merged into PointerInput. The use remains exactly the same.
  • Picking interaction events are now only accessible through observers, and no EventReader. This functionality may be re-implemented later.

For users of bevy_winit:

  • The event bevy_winit::WinitEvent has moved to bevy_window::WindowEvent. If this was the only thing you depended on bevy_winit for, you should switch your dependency to bevy_window.
  • bevy_window now depends on bevy_input. The dependencies of bevy_input are a subset of the existing dependencies for bevy_window so this should be non-breaking.

Math #

Added new method to Cone 3D primitive #

  • Addition of new method to the 3D primitive Cone struct.

Disallow empty cubic and rational curves #

The to_curve method on Bevy’s cubic splines is now fallible (returning a Result), meaning that any existing calls will need to be updated by handling the possibility of an error variant.

Similarly, any custom implementation of CubicGenerator or RationalGenerator will need to be amended to include an Error type and be made fallible itself.

Finally, the fields of CubicCurve and RationalCurve are now private, so any direct constructions of these structs from segments will need to be replaced with the new CubicCurve::from_segments and RationalCurve::from_segments methods.

Refactor Bounded2d/Bounded3d to use isometries #

The Bounded2d and Bounded3d traits now take Isometry2d and Isometry3d parameters (respectively) instead of separate translation and rotation arguments. Existing calls to aabb_2d, bounding_circle, aabb_3d, and bounding_sphere will have to be changed to use isometries instead. A straightforward conversion is to refactor just by calling Isometry2d/3d::new, as follows:

// Old:
let aabb = my_shape.aabb_2d(my_translation, my_rotation);

// New:
let aabb = my_shape.aabb_2d(Isometry2d::new(my_translation, my_rotation));

However, if the old translation and rotation are 3d translation/rotations originating from a Transform or GlobalTransform, then to_isometry may be used instead. For example:

// Old:
let bounding_sphere = my_shape.bounding_sphere(shape_transform.translation, shape_transform.rotation);

// New:
let bounding_sphere = my_shape.bounding_sphere(shape_transform.to_isometry());

This discussion also applies to the from_point_cloud construction method of Aabb2d/BoundingCircle/Aabb3d/BoundingSphere, which has similarly been altered to use isometries.

Basic integration of cubic spline curves with the Curve API #

The RationalCurve::domain method has been renamed to RationalCurve::length. Calling .domain() on a RationalCurve now returns its entire domain as an Interval.

Use Dir2/Dir3 instead of Vec2/Vec3 for Ray2d::new/Ray3d::new #

Ray2d::new and Ray3d::new now take a Dir2 and Dir3 instead of Vec2 and Vec3 respectively for the ray direction.

bevy_reflect: Update EulerRot to match glam 0.29 #

The reflection implementation for EulerRot has been updated to align with glam 0.29. Please update any reflection-based usages accordingly.

Picking #

rename Drop to bevy::picking::events::DragDrop to unclash std::ops:Drop #

  • Rename Drop to DragDrop
    • bevy::picking::events::Drop is now bevy::picking::events::DragDrop

Reflection #

bevy_reflect: Nested TypeInfo getters #

All active fields for reflected types (including lists, maps, tuples, etc.), must implement Typed. For the majority of users this won’t have any visible impact.

However, users implementing Reflect manually may need to update their types to implement Typed if they weren’t already.

Additionally, custom dynamic types will need to implement the new hidden MaybeTyped trait.

Implement FromIterator/IntoIterator for dynamic types #

  • Change DynamicArray::from_vec to DynamicArray::from_iter

Dedicated Reflect implementation for Set-like things #

  • The new Set variants on the enums listed in the change section should probably be considered by people working with this level of the lib

Help wanted!

I’m not sure if this change is able to break code. From my understanding it shouldn’t since we just add functionality but I’m not sure yet if theres anything missing from my impl that would be normally provided by impl_reflect_value!

bevy_reflect: Add Type type #

Certain type info structs now only return their item types as Type instead of exposing direct methods on them.

The following methods have been removed:

  • ArrayInfo::item_type_path_table
  • ArrayInfo::item_type_id
  • ArrayInfo::item_is
  • ListInfo::item_type_path_table
  • ListInfo::item_type_id
  • ListInfo::item_is
  • SetInfo::value_type_path_table
  • SetInfo::value_type_id
  • SetInfo::value_is
  • MapInfo::key_type_path_table
  • MapInfo::key_type_id
  • MapInfo::key_is
  • MapInfo::value_type_path_table
  • MapInfo::value_type_id
  • MapInfo::value_is

Instead, access the Type directly using one of the new methods:

  • ArrayInfo::item_ty
  • ListInfo::item_ty
  • SetInfo::value_ty
  • MapInfo::key_ty
  • MapInfo::value_ty

For example:

// BEFORE
let type_id = array_info.item_type_id();

// AFTER
let type_id = array_info.item_ty().id();

bevy_reflect: Refactor serde module #

The fields on ReflectSerializer and TypedReflectSerializer are now private. To instantiate, the corresponding constructor must be used:

// BEFORE
let serializer = ReflectSerializer {
    value: &my_value,
    registry: &type_registry,
};

// AFTER
let serializer = ReflectSerializer::new(&my_value, &type_registry);

Additionally, the following types are no longer public:

  • ArraySerializer
  • EnumSerializer
  • ListSerializer
  • MapSerializer
  • ReflectValueSerializer (fully removed)
  • StructSerializer
  • TupleSerializer
  • TupleStructSerializer

As well as the following traits:

  • DeserializeValue (fully removed)

bevy_reflect: Add DynamicTyped trait #

Reflect now has a supertrait of DynamicTyped. If you were manually implementing Reflect and did not implement Typed, you will now need to do so.

bevy_reflect: Replace "value" terminology with "opaque" #

The reflection concept of “value type” has been replaced with a clearer “opaque type”. The following renames have been made to account for this:

  • ReflectKind::ValueReflectKind::Opaque
  • ReflectRef::ValueReflectRef::Opaque
  • ReflectMut::ValueReflectMut::Opaque
  • ReflectOwned::ValueReflectOwned::Opaque
  • TypeInfo::ValueTypeInfo::Opaque
  • ValueInfoOpaqueInfo
  • impl_reflect_value!impl_reflect_opaque!
  • impl_from_reflect_value!impl_from_reflect_opaque!

Additionally, declaring your own opaque types no longer uses #[reflect_value]. This attribute has been replaced by #[reflect(opaque)]:

// BEFORE
#[derive(Reflect)]
#[reflect_value(Default)]
struct MyOpaqueType(u32);

// AFTER
#[derive(Reflect)]
#[reflect(opaque)]
#[reflect(Default)]
struct MyOpaqueType(u32);

Note that the order in which #[reflect(opaque)] appears does not matter.

Remove Return::Unit variant #

  • Removed the Return::Unit variant; use Return::unit() instead.

Make drain take a mutable borrow instead of Box<Self> for reflected Map, List, and Set. #

  • reflect::Map, reflect::List, and reflect::Set all now take a &mut self instead of a Box<Self>. Callers of these traits should add &mut before their boxes, and implementers of these traits should update to match.

Serialize and deserialize tuple struct with one field as newtype struct #

  • Reflection now will serialize and deserialize tuple struct with single field as newtype struct. Consider this code.
#[derive(Reflect, Serialize)]
struct Test(usize);
let reflect = Test(3);
let serializer = TypedReflectSerializer::new(reflect.as_partial_reflect(), &registry);
return serde_json::to_string(&serializer)

Old behavior will return ["3"]. New behavior will return "3". If you were relying on old behavior you need to update your logic. Especially with serde_json. ron doesn’t affect from this.

Use FromReflect when extracting entities in dynamic scenes #

The DynamicScene format is changed to use custom serialize impls so old scene files will need updating:

Old:

(
  resources: {},
  entities: {
    4294967299: (
      components: {
        "bevy_render::camera::projection::OrthographicProjection": (
          near: 0.0,
          far: 1000.0,
          viewport_origin: (
            x: 0.5,
            y: 0.5,
          ),
          scaling_mode: WindowSize(1.0),
          scale: 1.0,
          area: (
            min: (
              x: -1.0,
              y: -1.0,
            ),
            max: (
              x: 1.0,
              y: 1.0,
            ),
          ),
        ),
      },
    ),
  },
)

New:

(
  resources: {},
  entities: {
    4294967299: (
      components: {
        "bevy_render::camera::projection::OrthographicProjection": (
          near: 0.0,
          far: 1000.0,
          viewport_origin: (0.5, 0.5),
          scaling_mode: WindowSize(1.0),
          scale: 1.0,
          area: (
            min: (-1.0, -1.0),
            max: (1.0, 1.0),
          ),
        ),
      },
    ),
  },
)

move ShortName to bevy_reflect #

  • References to bevy_utils::ShortName should instead now be bevy_reflect::ShortName.

Rendering #

Lighting Should Only hold Vec<Entity> instead of TypeId<Vec<Entity>> #

now SpotLightBundle, CascadesVisibleEntities and CubemapVisibleEntities use VisibleMeshEntities instead of VisibleEntities

Add support for skybox transformation #

  • Since we have added a new filed to the Skybox struct, users will need to include ..Default::default() or some rotation value in their initialization code.

Allow volumetric fog to be localized to specific, optionally voxelized, regions. #

  • A FogVolume is now necessary in order to enable volumetric fog, in addition to VolumetricFogSettings on the camera. Existing uses of volumetric fog can be migrated by placing a large FogVolume surrounding the scene.

Pack multiple vertex and index arrays together into growable buffers. #

Changed

  • Vertex and index buffers for meshes may now be packed alongside other buffers, for performance.
  • GpuMesh has been renamed to RenderMesh, to reflect the fact that it no longer directly stores handles to GPU objects.
  • Because meshes no longer have their own vertex and index buffers, the responsibility for the buffers has moved from GpuMesh (now called RenderMesh) to the MeshAllocator resource. To access the vertex data for a mesh, use MeshAllocator::mesh_vertex_slice. To access the index data for a mesh, use MeshAllocator::mesh_index_slice.

Add support for environment map transformation #

  • Since we have added a new filed to the EnvironmentMapLight struct, users will need to include ..default() or some rotation value in their initialization code.

Using Cas instead of CAS #14341 #

Move Msaa to component #

Msaa is no longer configured as a global resource, and should be specified on each spawned camera if a non-default setting is desired.

Add 2d opaque phase with depth buffer #

  • ColorMaterial now contains AlphaMode2d. To keep previous behaviour, use AlphaMode::BLEND. If you know your sprite is opaque, use AlphaMode::OPAQUE

Changed Mesh::attributes* functions to return MeshVertexAttribute #

  • When using the iterator returned by Mesh::attributes or Mesh::attributes_mut the first value of the tuple is not the MeshVertexAttribute instead of MeshVertexAttributeId. To access the MeshVertexAttributeId use the MeshVertexAttribute.id field.

Add RenderSet::FinalCleanup for World::clear_entities #

World::clear_entities is now part of RenderSet::PostCleanup rather than RenderSet::Cleanup. Your cleanup systems should likely stay in RenderSet::Cleanup.

Add feature requirement info to image loading docs #

Image format related entities are feature gated, if there are compilation errors about unknown names there are some of features in list (exr, hdr, basis-universal, png, dds, tga, jpeg, bmp, ktx2, webp and pnm) should be added.

Fix underflow panic in InitTriInfo #

  • No breaking changes introduced.

Rewrite screenshots. #

ScreenshotManager has been removed. To take a screenshot, spawn a Screenshot entity with the specified render target and provide an observer targeting the ScreenshotCaptured event. See the window/screenshot example to see an example.

Replace the wgpu_trace feature with a field in bevy_render::settings::WgpuSettings #

The bevy/wgpu_trace and bevy_render/wgpu_trace features have been removed, as WGPU tracing is now enabled during the creation of bevy_render::RenderPlugin.

{% callout(type="info") %} At the time of writing, WGPU has not reimplemented tracing support, so WGPU tracing will not currently work. However, once WGPU has reimplemented tracing support, the steps below should be sufficient to continue generating WGPU traces.

You can track the progress of WGPU tracing being reimplemented at gfx-rs/wgpu#5974. {% end %}

To continue generating WGPU traces:

  1. Remove any instance of the bevy/wgpu_trace or bevy_render/wgpu_trace features you may have in any of your Cargo.toml files.
  2. Follow the instructions in docs/debugging.md, under the WGPU Tracing section.

Refactor AsBindGroup to use a associated SystemParam. #

AsBindGroup now allows the user to specify a SystemParam to be used for creating bind groups.

Meshlet software raster + start of cleanup #

  • TBD (ask me at the end of the release for meshlet changes as a whole)

Adds ShaderStorageBuffer asset #

The AsBindGroup storage attribute has been modified to reference the new Handle<Storage> asset instead. Usages of Vec` should be converted into assets instead.

Return Results from Camera's world/viewport conversion methods #

The following methods on Camera now return a Result instead of an Option so that they can provide more information about failures:

  • world_to_viewport
  • world_to_viewport_with_depth
  • viewport_to_world
  • viewport_to_world_2d

Call .ok() on the Result to turn it back into an Option, or handle the Result directly.

Replaced implicit emissive weight with default. #

Split OrthographicProjection::default into 2d & 3d (Adopted) #

  • In initialization of OrthographicProjection, change ..default() to ..OrthographicProjection::default_2d() or ..OrthographicProjection::default_3d()

Example:

--- a/examples/3d/orthographic.rs
+++ b/examples/3d/orthographic.rs
@@ -20,7 +20,7 @@ fn setup(
         projection: OrthographicProjection {
             scale: 3.0,
             scaling_mode: ScalingMode::FixedVertical(2.0),
-            ..default()
+            ..OrthographicProjection::default_3d()
         }
         .into(),
         transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),

Remove OrthographicProjection.scale (adopted) #

Replace all uses of scale with scaling_mode, keeping in mind that scale is (was) a multiplier. For example, replace

    scale: 2.0,
    scaling_mode: ScalingMode::FixedHorizontal(4.0),

with

    scaling_mode: ScalingMode::FixedHorizontal(8.0),

Rename rendering components for improved consistency and clarity #

Many rendering components have been renamed for improved consistency and clarity.

  • AutoExposureSettingsAutoExposure
  • BloomSettingsBloom
  • BloomPrefilterSettingsBloomPrefilter
  • ContrastAdaptiveSharpeningSettingsContrastAdaptiveSharpening
  • DepthOfFieldSettingsDepthOfField
  • FogSettingsDistanceFog
  • SmaaSettingsSmaa
  • TemporalAntiAliasSettingsTemporalAntiAliasing
  • ScreenSpaceAmbientOcclusionSettingsScreenSpaceAmbientOcclusion
  • ScreenSpaceReflectionsSettingsScreenSpaceReflections
  • VolumetricFogSettingsVolumetricFog

Migrate lights to required components #

PointLightBundle, SpotLightBundle, and DirectionalLightBundle have been deprecated. Use the PointLight, SpotLight, and DirectionalLight components instead. Adding them will now insert the other components required by them automatically.

Fix Mesh allocator bug and reduce Mesh data copies by two #

  • Mesh::get_vertex_buffer_data has been renamed Mesh::create_packed_vertex_buffer_data to reflect the fact that it copies data and allocates.

Added visibility bitmask as an alternative SSAO method #

SSAO algorithm was changed from GTAO to VBAO (visibility bitmasks). A new field, constant_object_thickness, was added to ScreenSpaceAmbientOcclusion. ScreenSpaceAmbientOcclusion also lost its Eq and Hash implementations.

Split out bevy_mesh from bevy_render #

bevy_render::mesh::morph::inherit_weights is now bevy_render::mesh::inherit_weights

if you were using Mesh::compute_aabb, you will need to use bevy_render::mesh::MeshAabb; now

Feature-gate all image formats #

Image formats that previously weren’t feature-gated are now feature-gated, meaning they will have to be enabled if you use them:

  • avif
  • ff (Farbfeld)
  • gif
  • ico
  • tiff

Additionally, the qoi feature has been added to support loading QOI format images.

Previously, these formats appeared in the enum by default, but weren’t actually enabled via the image crate, potentially resulting in weird bugs. Now, you should be able to add these features to your projects to support them properly.


If you were individually configuring the bevy_render crate, the feature flags for the general image formats were moved to bevy_image instead. For example, bevy_render/png no longer exists, and bevy_image/png is the new location for this. The texture formats are still available on bevy_render, e.g. bevy_render/ktx2 is needed to fully enable ktx2 support, and this will automatically enable bevy_image/ktx2 for loading the textures.

Per-meshlet compressed vertex data #

  • TBD by JMS55 at the end of the release

Migrate bevy_sprite to required components #

Replace all uses of SpriteBundle with Sprite. There are several new convenience constructors: Sprite::from_image, Sprite::from_atlas_image, Sprite::from_color.

WARNING: use of Handle<Image> and TextureAtlas as components on sprite entities will NO LONGER WORK. Use the fields on Sprite instead. I would have removed the Component impls from TextureAtlas and Handle<Image> except it is still used within ui. We should fix this moving forward with the migration.

Type safe retained render world #

With the advent of the retained render world, collections that contain references to Entity that are extracted into the render world have been changed to contain MainEntity in order to prevent errors where a render world entity id is used to look up an item by accident. Custom rendering code may need to be changed to query for &MainEntity in order to look up the correct item from such a collection. Additionally, users who implement their own extraction logic for collections of main world entity should strongly consider extracting into a different collection that uses MainEntity as a key.

Additionally, render phases now require specifying both the Entity and MainEntity for a given PhaseItem. Custom render phases should ensure MainEntity is available when queuing a phase item.

Renderers can now check RenderVisibleEntities to avoid rendering items that are not visible from a view. RenderVisibleMeshEntities, RenderCubemapVisibleEntities, and RenderCascadeVisibleEntities are also available for more fine-grained control.

Move ImageLoader and CompressedImageSaver to bevy_image. #

  • ImageLoader can no longer be initialized directly through init_asset_loader. Now you must use app.register_asset_loader(ImageLoader::new(supported_compressed_formats)) (check out the implementation of bevy_render::ImagePlugin). This only affects you if you are initializing the loader manually and does not affect users of bevy_render::ImagePlugin.

Attempt to remove component from render world if not extracted. #

Components that implement ExtractComponent and return None will cause the extracted component to be removed from the render world.

Improve API for scaling orthographic cameras #

ScalingMode has been refactored for clarity, especially on how to zoom orthographic cameras and their projections:

  • ScalingMode::WindowSize no longer stores a float, and acts as if its value was 1. Divide your camera’s scale by any previous value to achieve identical results.
  • ScalingMode::FixedVertical and FixedHorizontal now use named fields.

Fix UI texture atlas with offset #

let ui_node = ExtractedUiNode {
                    stack_index,
                    transform,
                    color,
                    rect,
                    image,
-                   atlas_size: Some(atlas_size * scale_factor),      
+                   atlas_scaling: Some(Vec2::splat(scale_factor)),
                    clip,
                    flip_x,
                    flip_y,
                    camera_entity,
                    border,
                    border_radius,
                    node_type,
                },
let computed_slices = ComputedTextureSlices {
    slices,
-    image_size,
}

use precomputed border values #

The logical_rect and physical_rect methods have been removed from Node. Use Rect::from_center_size with the translation and node size instead.

The types of the fields border and border_radius of ExtractedUiNode have been changed to BorderRect and ResolvedBorderRadius respectively.

Inverse bevy_render bevy_winit dependency and move cursor to bevy_winit #

CursorIcon and CustomCursor previously provided by bevy::render::view::cursor is now available from bevy::winit. A new feature custom_cursor enables this functionality (default feature).

Scenes #

Send SceneInstanceReady when spawning any kind of scene #

  • SceneInstanceReady { parent: Entity } is now SceneInstanceReady { id: InstanceId, parent: Option<Entity> }.

Align Scene::write_to_world_with to match DynamicScene::write_to_world_with #

Scene::write_to_world_with no longer returns an InstanceInfo.

Before

scene.write_to_world_with(world, &registry)

After

let mut entity_map = EntityHashMap::default();
scene.write_to_world_with(world, &mut entity_map, &registry)

Align Scene::write_to_world_with to match DynamicScene::write_to_world_with #

Scene::write_to_world_with no longer returns an InstanceInfo.

Before

scene.write_to_world_with(world, &registry)

After

let mut entity_map = EntityHashMap::default();
scene.write_to_world_with(world, &mut entity_map, &registry)

Change SceneInstanceReady to trigger an observer. #

If you have a system which read SceneInstanceReady events:

fn ready_system(ready_events: EventReader<'_, '_, SceneInstanceReady>) {

It must be rewritten as an observer:

commands.observe(|trigger: Trigger<SceneInstanceReady>| {

Or, if you were expecting the event in relation to a specific entity or entities, as an entity observer:

commands.entity(entity).observe(|trigger: Trigger<SceneInstanceReady>| {

explicitly mention component in methods on DynamicSceneBuilder #

DynamicSceneBuilder::allow_all and deny_all now set resource accesses, not just components. To return to the previous behavior, use the new allow_all_components or deny_all_components methods.

The following methods for DynamicSceneBuilder have been renamed:

  • with_filter -> with_component_filter
  • allow -> allow_component
  • deny -> deny_component

Migrate scenes to required components #

Asset handles for scenes and dynamic scenes must now be wrapped in the SceneRoot and DynamicSceneRoot components. Raw handles as components no longer spawn scenes.

Additionally, SceneBundle and DynamicSceneBundle have been deprecated. Instead, use the scene components directly.

Previously:

let model_scene = asset_server.load(GltfAssetLabel::Scene(0).from_asset("model.gltf"));

commands.spawn(SceneBundle {
    scene: model_scene,
    transform: Transform::from_xyz(-4.0, 0.0, -3.0),
    ..default()
});

Now:

let model_scene = asset_server.load(GltfAssetLabel::Scene(0).from_asset("model.gltf"));

commands.spawn((
    SceneRoot(model_scene),
    Transform::from_xyz(-4.0, 0.0, -3.0),
));

Text #

Text rework #

TODO: very breaking

Accessing text spans by index

Text sections are now text sections on different entities in a hierarchy, Use the new TextReader and TextWriter system parameters to access spans by index.

Before:

fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) {
    let text = query.single_mut();
    text.sections[1].value = format_time(time.elapsed());
}

After:

fn refresh_text(
    query: Query<Entity, With<TimeText>>,
    mut writer: UiTextWriter,
    time: Res<Time>
) {
    let entity = query.single();
    *writer.text(entity, 1) = format_time(time.elapsed());
}

Iterating text spans

Text spans are now entities in a hierarchy, so the new UiTextReader and UiTextWriter system parameters provide ways to iterate that hierarchy. The UiTextReader::iter method will give you a normal iterator over spans, and UiTextWriter::for_each lets you visit each of the spans.

split up TextStyle #

TextStyle has been renamed to TextFont and its color field has been moved to a separate component named TextColor which newtypes Color.

Text Rework cleanup #

Doubles as #15591 migration guide.

Text bundles (TextBundle and Text2dBundle) were removed in favor of Text and Text2d. Shared configuration fields were replaced with TextLayout, TextFont and TextColor components. Just TextBundle’s additional field turned into TextNodeFlags component, while Text2dBundle’s additional fields turned into TextBounds and Anchor components.

Text sections were removed in favor of hierarchy-based approach. For root text entities with Text or Text2d components, child entities with TextSpan will act as additional text sections. To still access text spans by index, use the new TextUiReader, Text2dReader and TextUiWriter, Text2dWriter system parameters.

Add the ability to control font smoothing #

  • Text now contains a font_smoothing: FontSmoothing property, make sure to include it or add ..default() when using the struct directly;
  • FontSizeKey has been renamed to FontAtlasKey, and now also contains the FontSmoothing setting;
  • The following methods now take an extra font_smoothing: FontSmoothing argument:
    • FontAtlas::new()
    • FontAtlasSet::add_glyph_to_atlas()
    • FontAtlasSet::get_glyph_atlas_info()
    • FontAtlasSet::get_outlined_glyph_texture()

Time #

aligning public apis of Time,Timer and Stopwatch #

The APIs of Time, Timer and Stopwatch have been cleaned up for consistency with each other and the standard library’s Duration type. The following methods have been renamed:

  • Stowatch::paused -> Stopwatch::is_paused
  • Time::elapsed_seconds -> Time::elapsed_secs (including _f64 and _wrapped variants)

UI #

Clean up UiSystem system sets #

UiSystem system set adjustments.

  • The UiSystem::Outline system set is now strictly ordered after UiSystem::Layout, rather than overlapping it.

Fix error in bevy_ui when building without bevy_text #

This is not a breaking change for users migrating from 0.14, since MeasureArgs did not exist then.

When the bevy_text feature is disabled for bevy_ui, the type of the MeasureArgs::font_system field is now a PhantomData instead of being removed entirely. This is in order to keep the lifetime parameter, even though it is unused without text being enabled.

Remove useless Direction field #

Style no longer has a direction field, and Direction has been deleted. They didn’t do anything, so you can delete any references to them as well.

Rename BreakLineOn to LineBreak #

BreakLineOn was renamed to LineBreak, and parameters named linebreak_behavior were renamed to linebreak.

Add UI GhostNode #

Any code that previously relied on Parent/Children to iterate UI children may now want to use bevy_ui::UiChildren to ensure ghost nodes are skipped, and their first descendant Nodes included.

UI root nodes may now be children of ghost nodes, which means Without<Parent> might not query all root nodes. Use bevy_ui::UiRootNodes where needed to iterate root nodes instead.

Replace Handle<M: UiMaterial> component with UiMaterialHandle wrapper #

Let’s defer the migration guide to the required component port. I just want to yeet the Component impl on Handle in the meantime :)

Clip to the UI node's content box #

Migration guide is on #15561

Overflow clip margin #

Style has a new field OverflowClipMargin. It allows users to set the visible area for clipped content when using overflow-clip, -hidden, or -scroll and expand it with a margin.

There are three associated constructor functions content_box, padding_box and border_box:

  • content_box: elements painted outside of the content box area (the innermost part of the node excluding the padding and border) of the node are clipped. This is the new default behaviour.
  • padding_box: elements painted outside outside of the padding area of the node are clipped.
  • border_box: elements painted outside of the bounds of the node are clipped. This matches the behaviour from Bevy 0.14.

There is also a with_margin method that increases the size of the visible area by the given number in logical pixels, negative margin values are clamped to zero.

OverflowClipMargin is ignored unless overflow-clip, -hidden or -scroll is also set on at least one axis of the UI node.

Migrate UI bundles to required components #

  • Replace all uses of NodeBundle with Node. e.g.
     commands
-        .spawn(NodeBundle {
-            style: Style {
+        .spawn((
+            Node::default(),
+            Style {
                 width: Val::Percent(100.),
                 align_items: AlignItems::Center,
                 justify_content: JustifyContent::Center,
                 ..default()
             },
-            ..default()
-        })
+        ))
  • Replace all uses of ButtonBundle with Button. e.g.
                     .spawn((
-                        ButtonBundle {
-                            style: Style {
-                                width: Val::Px(w),
-                                height: Val::Px(h),
-                                // horizontally center child text
-                                justify_content: JustifyContent::Center,
-                                // vertically center child text
-                                align_items: AlignItems::Center,
-                                margin: UiRect::all(Val::Px(20.0)),
-                                ..default()
-                            },
-                            image: image.clone().into(),
+                        Button,
+                        Style {
+                            width: Val::Px(w),
+                            height: Val::Px(h),
+                            // horizontally center child text
+                            justify_content: JustifyContent::Center,
+                            // vertically center child text
+                            align_items: AlignItems::Center,
+                            margin: UiRect::all(Val::Px(20.0)),
                             ..default()
                         },
+                        UiImage::from(image.clone()),
                         ImageScaleMode::Sliced(slicer.clone()),
                     ))
  • Replace all uses of ImageBundle with UiImage. e.g.
-    commands.spawn(ImageBundle {
-        image: UiImage {
+    commands.spawn((
+        UiImage {
             texture: metering_mask,
             ..default()
         },
-        style: Style {
+        Style {
             width: Val::Percent(100.0),
             height: Val::Percent(100.0),
             ..default()
         },
-        ..default()
-    });
+    ));

Merge Style properties into Node. Use ComputedNode for computed properties. #

Move any fields set on Style into Node and replace all Style component usage with Node.

Before:

commands.spawn((
    Node::default(),
    Style {
        width:  Val::Px(100.),
        ..default()
    },
));

After:

commands.spawn(Node {
    width:  Val::Px(100.),
    ..default()
});

For any usage of the “computed node properties” that used to live on Node, use ComputedNode instead:

Before:

fn system(nodes: Query<&Node>) {
    for node in &nodes {
        let computed_size = node.size();
    }
}

After:

fn system(computed_nodes: Query<&ComputedNode>) {
    for computed_node in &computed_nodes {
        let computed_size = computed_node.size();
    }
}

Explicitly order CameraUpdateSystem before UiSystem::Prepare #

CameraUpdateSystem is now explicitly ordered before UiSystem::Prepare instead of being ambiguous with it.

Utils #

Remove unused type parameter in Parallel::drain() #

The type parameter of Parallel::drain() was unused, so it is now removed. If you were manually specifying it, you can remove the bounds.

// 0.14
// Create a `Parallel` and give it a value.
let mut parallel: Parallel<Vec<u8>> = Parallel::default();
*parallel.borrow_local_mut() = vec![1, 2, 3];

for v in parallel.drain::<u8>() {
    // ...
}

// 0.15
let mut parallel: Parallel<Vec<u8>> = Parallel::default();
*parallel.borrow_local_mut() = vec![1, 2, 3];

// Remove the type parameter.
for v in parallel.drain() {
    // ...
}
  • Uses of bevy::utils::{EntityHash, EntityHasher, EntityHashMap, EntityHashSet} now have to be imported from bevy::ecs::entity.

Allow bevy_utils in no_std Contexts #

If you were importing bevy_utils and setting default_features to false, but relying on elements which are now gated behind the std or alloc features, include the relevant feature in your Cargo.toml.

Remove allocation in get_short_name #

For format!, dbg!, panic!, etc.

// Before
panic!("{} is too short!", get_short_name(name));

// After
panic!("{} is too short!", ShortName(name));

Need a String Value

// Before
let short: String = get_short_name(name);

// After
let short: String = ShortName(name).to_string();

Windowing #

Remove unused default feature from bevy_window #

bevy_window had an empty default feature flag that did not do anything, so it was removed. You may have to remove any references to it if you specified it manually.

# 0.14
[dependencies]
bevy_window = { version = "0.14", default-features = false, features = ["default"] }

# 0.15
[dependencies]
bevy_window = { version = "0.15", default-features = false }

Expose winit's MonitorHandle #

  • WindowMode variants now take a MonitorSelection, which can be set to MonitorSelection::Primary to mirror the old behavior.

move ANDROID_APP to bevy_window #

If you use the android_activity reexport from bevy::winit::android_activity, it is now in bevy::window::android_activity. Same for the ANDROID_APP static

Add bevy_window::Window options for MacOS #

bevy_window::Window now has extra fields for configuring MacOS window settings:

    pub movable_by_window_background: bool,
    pub fullsize_content_view: bool,
    pub has_shadow: bool,
    pub titlebar_shown: bool,
    pub titlebar_transparent: bool,
    pub titlebar_show_title: bool,
    pub titlebar_show_buttons: bool,

Using Window::default keeps the same behaviour as before.