Migration Guides

Migration Guide: 0.18 to 0.19

Like every major Bevy release, Bevy 0.19 comes with its own set of breaking changes. The most important changes to be aware of are listed below:

  1. Resources-as-components: resources are now stored as components on dedicated abstract entities
  2. Render-graph-as-systems: rendering now uses systems too!
  3. Parley text overhaul: we've moved from cosmic-text to parley and revamped our text internals
  4. Cargo feature collection changes: now more granular. Check here if your audio isn't working!
  5. bevy_scene has been renamed to bevy_world_serialization: making space for BSN
  6. Bloom now uses linear color: more correct, looks subtly different

This list prioritizes changes which are sweeping overhauls (1-3) or break in confusing or quiet ways (4-6).

For a full list of changes that may require migration, please see below. We recommend keeping this page open as you migrate your code base, and searching within it for any issues you encounter.

If you run into a problem while migrating that was not covered by this migration guide, please open an issue on the bevy-website repo or file a PR directly with the correct migration advice.

Resources as Components #

#[derive(Resource)] implements the Component trait #

In 0.19, Resource is a subtrait of Component and #[derive(Resource)] implements both Resource as well as Component. This means it's no longer possible to doubly derive both Component and Resource.

Types can no longer meaningfully be used as both resources and components. This was a deliberate change: ensuring that "resource" always ensures uniqueness, regardless of how the type is used.

Inserting new copies of any type which implements Resource, even as a component, will despawn other copies of that component on other entities. Similarly, data of type T stored as a resource will show up in queries for e.g. &T, leading to confusing new bugs.

To migrate, you should split these types into distinct resource and component types:

// 0.18
#[derive(Component, Resource)]
struct MyData(f32);

becomes

// 0.19
#[derive(Component)]
struct MyDataComp(f32);

#[derive(Resource)]
struct MyDataRes(f32);

We had a few types that were both resources and components inside of Bevy itself, for a "global fallback" pattern. UiDebugOverlay is split into GlobalUiDebugOverlay (resource) and UiDebugOverlay (component), and UiDebugOptions is split into GlobalUiDebugOptions (resource) and UiDebugOptions (component).

#[reflect(Resource)] Changes #

In 0.19, the ReflectResource is a ZST (zero-sized type) and only functions to signify that the trait is reflected. Instead, #[reflect(Resource)] also reflects the Component trait, so use ReflectComponent instead. This is likely to show up in code that uses reflection, like BRP (Bevy Remote Protocol) and bevy_world_serialization.

Broad Queries and System Conflicts #

Now that resources are components, they can be queried using 'broad' queries. These are queries that query all entities. Examples include:

  • Query<()>
  • Query<Entity>
  • Query<EntityMut>
  • Query<EntityRef>
  • Query<EntityMutExcept>
  • Query<EntityRefExcept>
  • Query<Option<&T>>

These should rarely come up in real games, but if they do, they might conflict with resource access, i.e.

fn system(entity_query: Query<EntityMut>, some_resource: Res<MyResource>) {} // err! entity_query conflicts with some_resource

To fix this, you can narrow down the query by using either the Without<MyResource> or Without<IsResource> filter. The IsResource marker is attached to all resource entities, so it always filters them out.

The same is true for non-send data:

fn system(entity_query: Query<EntityMut>, some_non_send: NonSend<MyNonSend>) {} // err! entity_query conflicts with some_resource

This can be fixed by adding a Without<MyNonSend> filter to the query.

Renaming Non-Send Resources to Non-Send Data #

Previously there were two types of resources: Send resources and !Send resources. Now that Send resources are stored as components, !Send resources have little in common with their Send counterparts. This is why non-send resources are being renamed to non-send data. The following APIs are affected:

  • App::init_non_send_resource is deprecated in favor of App::init_non_send.
  • App::insert_non_send_resource is deprecated in favor of App::insert_non_send.
  • DeferredWorld::non_send_resource_mut is deprecated in favor of DeferredWorld::non_send_mut.
  • DeferredWorld::get_non_send_resource_mut is deprecated in favor of DeferredWorld::get_non_send_mut.
  • ResourceData<SEND: true> is removed, while ResourceData<SEND: false> is renamed to NonSendData.
  • Resources<SEND: true> is removed and Resources<Send: false> is renamed to NonSends.
  • UnsafeWorldCell::get_non_send_resource is deprecated in favor of UnsafeWorldCell::get_non_send.
  • UnsafeWorldCell::get_non_send_resource_by_id is deprecated in favor of UnsafeWorldCell::get_non_send_by_id.
  • UnsafeWorldCell::get_non_send_resource_mut is deprecated in favor of UnsafeWorldCell::get_non_send_mut.
  • UnsafeWorldCell::get_non_send_resource_mut_by_id is deprecated in favor of UnsafeWorldCell::get_non_send_mut_by_id.
  • World::init_non_send_resource is deprecated in favor of World::init_non_send.
  • World::insert_non_send_resource is deprecated in favor of World::insert_non_send.
  • World::remove_non_send_resource is deprecated in favor of World::remove_non_send.
  • World::non_send_resource is deprecated in favor of World::non_send.
  • World::non_send_resource_mut is deprecated in favor of World::non_send_mut.
  • World::get_non_send_resource is deprecated in favor of World::get_non_send.
  • World::get_non_send_resource_mut is deprecated in favor of World::get_non_send_mut.

Component Registration #

Before using components and resources they must be registered to a world. The registration process for components and resources is very similar and now that Send resources are components, we're able to simplify some of the code; removing / deprecating some methods.

  • Components::register_resource_unchecked is renamed to Components::register_non_send_unchecked.
  • Components::get_valid_resource_id was deprecated in favor of Components::get_valid_id.
  • Components::valid_resource_id was deprecated in favor of Components::valid_component_id.
  • Components::resource_id was deprecated in favor of Components::component_id.
  • ComponentsRegistrator::register_resource is deprecated in favor of ComponentsRegistrator::register_component.
  • ComponentsRegistrator::register_resource_with is renamed to ComponentsRegistrator::register_non_send_with.
  • ComponentsRegistrator::register_resource_with_descriptor is removed in favor of ComponentsRegistrator::register_component_with_descriptor.
  • ComponentsQueuedRegistrator::queue_register_resource_with_descriptor was removed in favor of ComponentsQueuedRegistrator::queue_register_component_with_descriptor.
  • ComponentsQueuedRegistrator::queue_register_resource was deprecated in favor of ComponentsQueuedRegistrator::queue_register_component.
  • ComponentDescriptor::new_resource was deprecated in favor of ComponentDescriptor::new
  • World::register_resource_with_descriptor was renamed to World::register_non_send_with_descriptor.

Access #

Resources were also removed from Access, which keeps track what data any given query / system has access to.

  • Access::add_component_read and Access::add_resource_read were deprecated in favor of Access::add_read.
  • Access::add_component_write and Access::add_resource_write were deprecated in favor of Access::add_write.
  • Access::remove_component_read was deprecated in favor of Access::remove_read.
  • Access::remove_component_write was deprecated in favor of Access::remove_write.
  • Access::has_component_read and Access::has_resource_read were deprecated in favor of Access::has_read.
  • Access::has_any_component_read and Access::has_any_resource_read were deprecated in favor of Access::has_any_read.
  • Access::has_component_write and Access::has_resource_write were deprecated in favor of Access::has_write.
  • Access::has_any_component_write and Access::has_any_resource_write were deprecated in favor of Access::has_any_write.
  • Access::read_all_components was deprecated in favor of Access::read_all.
  • Access::write_all_components was deprecated in favor of Access::write_all.
  • Access::read_all_resources and Access::write_all_resources were removed.
  • Access::has_read_all_components was deprecated in favor of Access::has_read_all.
  • Access::has_write_all_components was deprecated in favor of Access::has_write_all.
  • Access::has_read_all_resources and Access::has_write_all_resources were removed.
  • Access::is_components_compatible was deprecated in favor of Access::is_compatible.
  • Access::is_resources_compatible was removed.
  • Access::is_subset_components was deprecated in favor of Access::is_subset.
  • Access::is_subset_resources was removed.
  • Access::resource_reads_and_writes, Access::resource_reads, Access::resource_writes were removed.
  • Access::try_iter_component_access was deprecated in favor of Access::try_iter_access.
  • FilteredAccess::add_component_read was deprecated in favor of FilteredAccess::add_read.
  • FilteredAccess::add_component_write was deprecated in favor of FilteredAccess::add_write.
  • FilteredAccess::add_resource_read and FilteredAccess::add_resource_write were removed.
  • FilteredAccess::read_all_components was deprecated in favor of FilteredAccess::read_all.
  • FilteredAccess::write_all_components was deprecated in favor of FilteredAccess::write_all.
  • FilteredAccessSet::add_unfiltered_resource_read was deprecated in favor of FilteredAccessSet::add_resource_read.
  • FilteredAccessSet::add_unfiltered_resource_write was deprecated in favor of FilteredAccessSet::add_resource_write.

Due to the split storage it used to be possible to both access an entity and a resource in a WorldQuery implementor. This is no longer valid. In order to access multiple different entities for a WorldQuery implementation, use WorldQuery::init_nested_access. See the implementation of WorldQuery for AssetChanged for an example of how this can be done correctly.

Immutable Resources #

Since resources may now be immutable, the following now carry a Mutability = Mutable bound:

  • ResMut
  • World::resource_mut, World::get_resource_mut
  • UnsafeWorldCell::get_resource_mut
  • EntityWorldMut::resource_mut, EntityWorldMut::get_resource_mut
  • DeferredWorld::resource_mut, DeferredWorld::get_resource_mut
  • TemplateContext::resource_mut
  • as well as ExtractResourcePlugin

If you are calling these in a generic context and the resource is always mutable, you need to add this bound to your type parameter:

// 0.18
fn my_generic_system<R: Resource>(mut res: ResMut<R>) {
}

// 0.19
fn my_generic_system<R: Resource<Mutability = Mutable>>(mut res: ResMut<R>) {
}

If the bound cannot be added, there are a couple of options:

  • If the resource is only sometimes mutable OR the API should not be unsafe, use World::modify_resource and World::modify_resource_by_id. They behave exactly like their component counterparts.
  • If your API can be made unsafe, use the UnsafeWorldCell::*_assume_mutable methods and make sure that the safety conditions are satisfied. UnsafeWorldCell::get_resource_mut_assume_mutable has been provided for this explicit purpose. It is also possible to use the *_assume_mutable component methods, but you will first have to retrieve the resource entity from ResourceEntities.

Miscellaneous #

Since MapEntities is implemented by default for components, it's no longer necessary to add derive(MapEntities) to a resource.

// 0.18
#[derive(Resource, MapEntities)]
struct EntityStruct(#[entities] Entity);

// 0.19
#[derive(Resource)]
struct EntityStruct(#[entities] Entity);

Next, World::clear_entities now also clears all resources, and World::clear_all now clears all entities, resources, and non-send data.

Lastly, World::remove_resource_by_id now returns bool instead of Option<()>.

map_handle_to_font_id and get_font_id have been removed from TextPipeline #

PRs:#22385

map_handle_to_font_id and get_font_id have been removed from TextPipeline.

The ID and family name of Font assets can be retrieved from the asset itself.

Methods of Affine3 to_transpose and inverse_transpose_3x3 are now part of an extension trait #

PRs:#22681

With the addition of Affine3 on glam, Bevy's version was removed. To keep the functionality that Bevy's version provided we created the extension trait Affine3Ext. Locations that accessed Affine3::to_transpose or Affine3::inverse_transpose_3x3 will now need the extension trait to be in scope.

AssetPath::resolve and resolve_embed now take &AssetPath #

PRs:#22416

AssetPath::resolve and AssetPath::resolve_embed no longer accept &str and now take &AssetPath directly. The previous string-based APIs have been renamed to resolve_str and resolve_embed_str.

This change avoids unnecessary string allocation and parsing when an AssetPath is already available. To migrate, pass an AssetPath directly to resolve or resolve_embed; when working with strings, use the corresponding *_str methods instead.

IndirectParametersBuffers settings split #

PRs:#23459

allow_copies_from_indirect_parameter_buffers has been moved from IndirectParametersBuffers to a new IndirectParametersBuffersSettings resource.

The old bevy_scene is now bevy_world_serialization #

In Bevy 0.19 we landed a subset of Bevy's Next Generation Scene system (known as BSN), which now lives in the bevy_scene / bevy::scene crate. However the old bevy_scene system still needs to stick around for a bit longer, as it provides some features that Bevy's Next Generation Scene system doesn't (yet!):

  1. It is not yet possible to write a World to BSN, so the old system is still necessary for "round trip World serialization".
  2. The GLTF scene loader has not yet been ported to BSN, so the old system is still necessary to spawn GLTF scenes in Bevy.

For this reason, we have renamed the old bevy_scene crate to bevy_world_serialization. If you were referencing bevy_scene::* or bevy::scene::* types, rename those paths to bevy_world_serialization::* and bevy::world_serialization::* respectively.

Additionally, to avoid confusion / conflicts with the new scene system, all "scene" terminology / types have been reframed as "world serialization":

  • Scene -> WorldAsset (as this was always just a World wrapper)
  • SceneRoot -> WorldAssetRoot
  • DynamicScene -> DynamicWorld
  • DynamicScene::from_scene -> DynamicWorld::from_world_asset
  • DynamicSceneBuilder -> DynamicWorldBuilder
  • DynamicSceneRoot -> DynamicWorldRoot
  • SceneInstanceReady -> WorldInstanceReady
  • SceneLoader -> WorldAssetLoader
  • ScenePlugin -> WorldSerializationPlugin
  • SceneRootTemplate -> WorldAssetRootTemplate
  • SceneSpawner -> WorldInstanceSpawner
  • SceneFilter -> WorldFilter
  • SceneLoaderError -> WorldAssetLoaderError
  • SceneSpawnError -> WorldInstanceSpawnError

GLTF scene spawning is the most likely source of breakage for most people, as round trip world serialization is a relatively niche use case. For most people, the migration should be as simple as:

// before
commands.spawn(SceneRoot(asset_server.load("scene.gltf#Scene0")));

// after
commands.spawn(WorldAssetRoot(asset_server.load("scene.gltf#Scene0")));

We know this naming is a bit awkward. Once we port GLTF loading over to BSN (hopefully in the next release), you will be able to do cool stuff like this:

bsn! {
    :"scene.gltf#Scene0"
    Transform { position: Vec3 { x: 10. } }
}

This would set just the x position in the GLTF scene root to x, patching on top of the position defined in the gltf scene. Cool!

The experimental_bevy_feathers feature is no longer experimental #

PRs:#24108

The feature flag experimental_bevy_feathers is now bevy_feathers.

With the introduction of bsn! and another cycle of bug fixes and features, the Feathers UI framework is now stable enough for broad use in tooling. While it remains incomplete and is subject to breaking changes, it is no longer substantially more experimental than the rest of Bevy.

Rename Font::try_from_bytes to Font::from_bytes #

Font::try_from_bytes has been renamed to Font::from_bytes to reflect that it no longer returns Result.

// 0.18
let font = Font::try_from_bytes(bytes.to_vec()).unwrap();

// 0.19
let font = Font::from_bytes(bytes.to_vec());

Skybox image is now optional #

PRs:#23691

The image field of the Skybox component now has the type Option<Handle<Image>> instead of Handle<Image>. A Skybox component without an image will not draw anything, just as though it isn't present.

If you were creating a skybox with an image, wrap the image handle in Some:

// 0.18
Skybox {
    image: my_skybox,
    brightness: 1000.0,
    ..default()
}

// 0.19
Skybox {
    image: Some(my_skybox),
    brightness: 1000.0,
    ..default()
}

If you were previously creating a Skybox component with a placeholder image to be changed later, you can now remove the placeholder:

// 0.18
Skybox {
    image: cubemap_image_that_will_not_actually_be_seen,
    brightness: 1000.0,
    ..default()
}

// 0.19
Skybox {
    brightness: 1000.0,
    ..default()
}

Resources MeshPipelineViewLayouts, MeshPipeline and RenderDebugOverlayPipeline are now created in RenderStartup systems #

PRs:#22443

Systems using the MeshPipelineViewLayouts, MeshPipeline and RenderDebugOverlayPipeline resources in the RenderStartup schedule now need to be run after the MeshPipelineSystems system set.

RenderMeshInstance becomes atomic #

PRs:#22988

In order to enhance the performance and scalability of RenderMeshInstance, its fields have been made atomic. Code that accessed fields like:

let instance: &RenderMeshInstance = ...;
... instance.mesh_asset_id ...

Should now use the accessor methods like this:

let instance: &RenderMeshInstance = ...;
... instance.mesh_asset_id() ...

There are associated setter methods with a set_ prefix as well. Note that, because RenderMeshInstance fields are atomic, you don't need an &mut reference to call them. However, it's now your responsibility to avoid data races.

RenderSystems::ManageViews has been split into three system sets #

PRs:#22949

ManageViews was previously somewhat overloaded with responsibility, and made resolving render system order ambiguities difficult. To fix this, ManageViews has been split into three phases: CreateViews, Specialize, and PrepareViews. It is very likely whatever you were ordering against ManageViews can now be ordered against PrepareViews and have identical behavior. If you are creating additional views, for example for cubemap rendering, please do so in CreateViews.

TextRoot, TextSpanAccess and TextSpanComponent are replaced by TextSection #

PRs:#23423

The TextRoot, TextSpanAccess and TextSpanComponent traits have been consolidated into a single trait TextSection.

The methods read_span and write_span have been renamed to get_text and get_text_mut, respectively.

audio feature is now no longer implied by the 3d, 2d, or ui features #

PRs:#23126

Our default features used to be

  • 2d
  • 3d
  • ui

where each of these features enabled the audio feature among others.

Since Cargo doesn't allow selectively disabling features, if you wished to disable bevy_audio, either because you don't need audio or because you use an alternative such as bevy_seedling, you had a problem. You needed to essentially enable all features enabled by the above except audio, leading to a big features soup. To avoid this, audio is now no longer enabled by the above features, but instead enabled by default, bumping our default features to:

  • 2d
  • 3d
  • ui
  • audio

Now what does this mean for you?

  • If you used all default features before, nothing changes for you.
  • If you want to opt out of using bevy_audio, it is now as simple as disabling default features, and manually opting into 3d, 2d, and/or ui.
  • If you already opted into non-default features and want to continue using bevy_audio, you will now have to add the audio feature.

WgpuSettingsPriority::Compatibility renamed to WgpuSettingsPriority::WebGPU #

PRs:#23783

WgpuSettingsPriority::Compatibility has been renamed to WgpuSettingsPriority::WebGPU. To set this using an environment variable, update WGPU_SETTINGS_PRIO to webgpu from compatibility.

shadow_pass has been split into per_view_shadow_pass and shared_shadow_pass #

PRs:#23713

shadow_pass has been split into per_view_shadow_pass (for rendering DirectionalLight shadow maps) and shared_shadow_pass (for rendering PointLight and SpotLight shadow maps).

Image::pixel_bytes and Image::pixel_data_offset now return Result #

PRs:#22908

Image::pixel_bytes, Image::pixel_bytes_mut, and Image::pixel_data_offset now return Result<..., TextureAccessError>.

Previously, these methods returned Option and would silently fail for both out-of-bounds access and unsupported texture formats (such as compressed textures). This caused error information to be lost, making it impossible for callers to distinguish between these different failure cases.

Now, these methods properly propagate TextureAccessError:

  • TextureAccessError::OutOfBounds for coordinates outside the image bounds
  • TextureAccessError::UnsupportedTextureFormat for compressed or unsupported texture formats
  • TextureAccessError::Uninitialized if the image data is not initialized

Update any code using these methods to handle the Result return type:

// 0.18
if let Some(bytes) = image.pixel_bytes(coords) {
    // use bytes
}

// 0.19
match image.pixel_bytes(coords) {
    Ok(bytes) => {
        // use bytes
    }
    Err(TextureAccessError::Uninitialized) => {
        // handle missing image data
    }
    Err(TextureAccessError::OutOfBounds { .. }) => {
        // handle out of bounds
    }
    Err(TextureAccessError::UnsupportedTextureFormat(format)) => {
        // handle compressed/unsupported format
    }
}

New crate bevy_material #

PRs:#22426

Various material-related machinery was extracted from bevy_pbr and bevy_render into a new crate called bevy_material.

The following were moved from bevy_render to bevy_material:

  • AlphaMode
  • SpecializedMeshPipelineError

The following were moved from bevy_pbr to bevy_material:

  • OpaqueRendererMethod
  • ErasedMeshPipelineKey, ErasedMaterialPipelineKey, ErasedMaterialKey, ErasedMaterialKeyVTable, RenderPhaseType
  • MaterialProperties

The following were moved from bevy_render to bevy_material but do not require migration thanks to re-exports:

  • BindGroupLayoutDescriptor, RenderPipelineDescriptor, NoFragmentStateError, VertexState, FragmentState, ComputePipelineDescriptor
  • ShaderLabel, DrawFunctionLabel, DrawFunctionId
  • BindGroupLayoutEntryBuilder, BindGroupLayoutEntries, DynamicBindGroupLayoutEntries, IntoBindGroupLayoutEntryBuilder, IntoIndexedBindGroupLayoutEntryBuilderArray

Atmosphere is now an entity #

PRs:#23651

Previously, the Atmosphere component was added to the camera. In 0.19, the Atmosphere is spawned as an entity. The nearest atmosphere will be chosen for rendering.

AtmosphereSettings still belongs to the camera. This is the component that enables atmosphere rendering for that view.

The scene_units_to_m field has been removed from AtmosphereSettings. Use Transform on the Atmosphere entity for scale. It is inversely proportional to the old scene_units_to_m factor. For example, to treat one unit as 1 km (as with scene_units_to_m: 1000.0), set scale to 0.001.

// 0.18
commands.spawn((
    Camera3d::default(),
    Atmosphere::earth(earth_medium),
    AtmosphereSettings {
        scene_units_to_m: 1000.0,
        ..default()
    },
));
// 0.19
let earth = Atmosphere::earth(earth_medium);
let scale = 0.001;
commands.spawn((
    earth,
    Transform::from_scale(Vec3::splat(scale)).with_translation(-Vec3::Y * earth.inner_radius * scale),
));

commands.spawn((
    Camera3d::default(),
    AtmosphereSettings::default(),
));

If you don't need to customize the scale, a default Transform component is added that positions the atmosphere such that the horizon lines up with the camera's default Y-up direction.

commands.spawn(Atmosphere::earth(earth_medium));

The bottom_radius and top_radius fields on the Atmosphere component have been renamed to inner_radius and outer_radius respectively to reflect their new meaning.

See the updated atmosphere example and documentation for details.

There have been some renames:

  • Atmosphere::earthlike -> Atmosphere::earth
  • bevy::pbr::Atmosphere -> bevy::light::Atmosphere
  • bevy::pbr::ScatteringMedium -> bevy::light::atmosphere::ScatteringMedium

Invert bevy_gltf dependency with bevy_pbr #

PRs:#22569

Previously, bevy_gltf depended on bevy_pbr. This meant scene definition was tightly coupled to rendering. This dependency has been inverted, to allow bevy_gltf to function without any of the rendering stack present.

bevy_gltf is now also an optional dependency.

In 0.18, loading a material sub-asset would return a Handle<StandardMaterial>.

let handle: Handle<StandardMaterial> = asset_server.load("models/animated/Fox.glb#Material0");

In 0.19, loading a material sub-asset loads a GltfMaterial to accurately represent the data in the glTF file. To load the StandardMaterial, use the /std suffix when the bevy_pbr feature is turned on (the feature is on by default).

let handle: Handle<GltfMaterial> = asset_server.load("models/animated/Fox.glb#Material0");
let handle_std: Handle<StandardMaterial> = asset_server.load("models/animated/Fox.glb#Material0/std");

You can disable PBR rendering by initializing PbrPlugin as follows:

PbrPlugin {
    gltf_enable_standard_materials: false,
    ..Default::default()
}

GltfExtensionHandler trait's methods have been updated:

  • on_material passes in the material_asset : &GltfMaterial and material_label: &str
  • on_spawn_mesh_and_material also passes in the material_label: &str

UvChannel has moved from bevy_pbr to bevy_mesh.

New fields added to UiDebugOptions #

PRs:#21931

UiDebugOptions has new bool fields: outline_border_box, outline_padding_box, outline_content_box,outline_scrollbars, and ignore_border_radius. To match the previous behavior of UiDebugOptions, where only the border box outline was rendered, use the default values with outline_border_box: true and the rest of the new fields set to false.

Rename System::type_id to System::system_type #

PRs:#23326

System::type_id has been renamed to System::system_type to avoid shadowing Any::type_id.

The old System::type_id method is now deprecated and will be removed in a future release. Replace all calls to System::type_id with System::system_type:

// 0.18
let id = my_system.type_id();

// 0.19
let id = my_system.system_type();

If you have a custom System implementation that overrides type_id, rename it to system_type.

Ref now directly implements Clone and Copy #

PRs:#23549

Ref now implements Clone and Copy, which means calling ref.clone() now returns another Ref<T> rather than a cloned inner T. To continue cloning the inner T, use ref.as_ref().clone(), ref.deref().clone(), or ref.into_inner().clone().

Post Processing Split #

PRs:#23098

For both Core2dSystems and Core3dSystems, the PostProcess system set has been split into EarlyPostProcess and PostProcess. 2D now also has a Prepass. Both systems have the following sets:

  • Prepass
  • MainPass
  • EarlyPostProcess
  • PostProcess

Reflect Struct QOL #

PRs:#22708

bevy_reflect::DynamicStruct::index_of was moved to the bevy_reflect::Struct trait and is now bevy_reflect::Struct::index_of_name. Most utility methods already existed on Struct, except for the method to get a fields index by name.

The bevy_reflect::FieldIter iterator had its items changed, from &dyn PartialReflect to a tuple of (&str, &dyn PartialReflect), so that you can iterate the names alongside the fields.

PositionedGlyph::span_index is now section_index #

PRs:#23381

PositionedGlyph::span_index has been renamed to section_index, because only a TextSpan entity should be referred to as a "span". We use "section" when an entity could be either a text root or a TextSpan.

DynamicSceneBuilder and DynamicScene::from_scene now require a &TypeRegistry #

PRs:#23401

Previously, DynamicSceneBuilder and DynamicScene (now DynamicWorldBuilder and DynamicWorld respectively) would get the type registry out of the world being extracted. However, when building a world from scratch just for serialization, this required artificially cloning the registry and putting it in the world being saved.

In 0.19 DynamicWorldBuilder and DynamicWorld::from_world_asset require an existing type registry in Bevy. For example, before:

// 0.18
let world: &World = ...;
let scene = DynamicSceneBuilder::from_world(world)
    .extract_entity(e1)
    .extract_entity(e2)
    .extract_resources()
    .build();

Becomes:

// 0.19
let world: &World = ...;
let dynamic_world = {
    let type_registry = world.resource::<AppTypeRegistry>().read();
    DynamicWorldBuilder::from_world(world, &type_registry)
        .extract_entity(e1)
        .extract_entity(e2)
        .extract_resources()
        .build()
};

For DynamicScene::from_scene:

// 0.18
let type_registry: AppTypeRegistry = get_from_main_world();
let mut scene: Scene = ...;
// Previously the scene world needed the type registry.
scene.world.insert_resource(type_registry);
let dynamic_scene = DynamicScene::from_scene(scene);

Becomes:

// 0.19
let type_registry: AppTypeRegistry = get_from_main_world();
let world_asset: WorldAsset = ...; // Scene was renamed to WorldAsset in 0.19
// No need to insert into the world asset!
let dynamic_world = DynamicWorld::from_world_asset(&world_asset, &type_registry.read());

Dropping Tasks in Web Builds #

PRs:#21795

Dropping a Task<T> in a web build now cancels it, call Task<T>::detach() to keep the old behavior.

Return type of Access::archetypal changed #

PRs:#23384

The return type of Access::archetypal has changed from impl Iterator to a new &ComponentIdSet type. That type does implement IntoIterator, but callers may need to call the iter() method to get an Iterator.

bevy_reflect reorganized to de-clutter the crate root #

PRs:#22342

bevy_reflect has undergone a large reorganization. Many modules have been exposed in the crate root, each containing items relevant to their "kind" of reflected type:

  • array
  • enums
  • list
  • map
  • set
  • structs
  • tuple
  • tuple_struct

For example, the structs module now contains the Struct trait, as well as related items like DynamicStruct or StructInfo.

This change was made to de-clutter the crate root of bevy_reflect, hopefully making it easier to find what traits and types you need for your use of reflection.

Migrating should only require editing your use statements. The Rust compiler will give hints at the new type paths, should you need any assistance.

Align bevy_transform's feature flags #

PRs:#23919

bevy_transform no longer depends on bevy_log. The bevy_log feature flag has been removed.

Tracing instrumentation is now gated on the new trace feature (using tracing directly, matching bevy_ecs):

# 0.18
bevy_transform = { features = ["bevy_log"] }

# 0.19
bevy_transform = { features = ["trace"] }

Parallel transform propagation is no longer tied to the std feature. It now requires the explicit multi_threaded feature:

# 0.18 — parallel was enabled implicitly via std
bevy_transform = { features = ["std"] }

# 0.19 — opt in explicitly
bevy_transform = { features = ["std", "multi_threaded"] }

UiWidgetsPlugins and InputDispatchPlugin are now in DefaultPlugins #

PRs:#23346

UiWidgetsPlugins and InputDispatchPlugin are now part of DefaultPlugins.

These plugins are now mature enough to be included as part of the default Bevy experience.

Remove UiWidgetsPlugins if you have DefaultPlugins

// 0.18
fn main() {
    App::new()
        .add_plugins(DefaultPlugins, UiWidgetsPlugins)
        .add_plugins((my_ambitious_game::game_plugin))
        .run();
}

// 0.19
fn main() {
    App::new()
        .add_plugins(DefaultPlugins) // Puff!
        .add_plugins((my_ambitious_game::game_plugin))
        .run();
}

Remove InputDispatchPlugin if you have DefaultPlugins

// 0.18
fn main() {
    App::new()
        .add_plugins(DefaultPlugins, UiWidgetsPlugins, InputDispatchPlugin)
        .add_plugins((my_sequel_game::game_plugin))
        .run();
}

// 0.19
fn main() {
    App::new()
        .add_plugins(DefaultPlugins) // Puff!
        .add_plugins((my_sequel_game::game_plugin))
        .run();
}

Atmosphere has been moved to bevy_light #

PRs:#22709

Atmosphere, ScatteringMedium, ScatteringTerm, PhaseFunction, and Falloff have been moved from bevy_pbr to bevy_light. Atmosphere is available at bevy::light::Atmosphere, the rest are under bevy::light::atmosphere.

bevy_window, bevy_input_focus, custom_cursor features moved to alternate feature collections #

PRs:#22488

In Bevy 0.18, feature collections were introduced. The bevy_window, bevy_input_focus, & custom_cursor features were included in the default_app collection.

In Bevy 0.19, these have been moved from default_app:

Featureis included in...
bevy_windowcommon_api
bevy_input_focusui_api
custom_cursordefault_platform

This change was made because:

  • the default_app collection is for core functionality that most apps will need. Scene definition for windowing is not usually required, and
  • apps that don't use windowing (ex: command line tools, servers, etc) can compile fewer dependencies.

If you were relying on these being included in default_app, you can cherry-pick them into your Cargo.toml feature list:

# 0.18
bevy = { version = "0.18", default-features = false, features = [ "default_app" ] }

# 0.19
bevy = { version = "0.19", default-features = false, features = [
    "default_app",
    "bevy_window",
    "bevy_input_focus",
    "custom_cursor"
] }

If you already depend on a high-level profile (2d, 3d, ui), or a mid-level collection ending in '_render' or '_api', then you do not need to make any changes.

extra_buffer_usages moved from MeshAllocator to MeshAllocatorSettings #

PRs:#23444

extra_buffer_usages has been moved from MeshAllocator to MeshAllocatorSettings. If you were accessing it on MeshAllocator, please do so on MeshAllocatorSettings now.

Avoiding unnecessary AssetEvent::Modified events that lead to rendering performance costs #

PRs:#22460

Assets::get_mut will now return AssetMut<A: Asset> instead of &mut Asset. Similar to Mut/ResMut, the new implementation will trigger an AssetEvent::Modified event only when the asset is actually mutated.

In some cases (like materials), triggering the AssetEvent::Modified event might lead to measurable performance costs. To avoid this, you can check if the Asset will change before mutating it:

fn update(
    query: Query<MeshMaterial3d<StandardMaterial>>,
    materials: ResMut<Assets<StandardMaterial>>,
    time: Res<Time>,
) {
    for material_handle in query.iter_mut() {
        // material variable now needs to be marked as mut
        let Some(mut material) = materials.get_mut(material_handle) else {
            continue;
        };
        
        let new_color = compute_new_color(&time);
        if material.base_color != new_color {
            // material will be marked as changed and extracted down the line
            // only if the color has actually changed
            material.base_color = new_color;
        }
    }
}

Interned is now reflectable but requires additional trait bounds #

PRs:#22472

Interned<T> now requires all instances with T to implement Internable, where previously only the PartialEq, Eq, and Hash implementations required it. Implement Internable for T to fix this.

EnvironmentMapUniform is removed #

PRs:#24095

EnvironmentMapUniform has been removed. It previously stored the rotation transformation matrix of view environment maps. Now the rotation is stored as a quaternion in LightProbesUniform::view_rotation.

bevy_shader cleanups #

PRs:#22774

ShaderReflectError has been deleted, as it was unused.

ShaderCache::new now accepts a RenderDevice, and ShaderCache::get does not. This is to reflect the fact that a ShaderCache must only be used with one RenderDevice for it to be valid.

The set_import_path, with_import_path, import_path, and imports methods on Shader have been removed. Just access the fields directly, these were superfluous getter methods.

new_with_ prefix removed from TextLayout constructors #

PRs:#24049

Constructor functions for the TextLayout type were simplified:

  • TextLayout::new_with_justify -> TextLayout::justify
  • TextLayout::new_with_linebreak -> TextLayout::linebreak
  • TextLayout::new_with_no_wrap -> TextLayout::no_wrap

Changes to TextFont's font_size and font fields #

TextFont's font field has been changed from a Handle<Font> to a FontSource, and its font_size field was changed from an f32 to a FontSize.

FontSource has two variants: Handle, which identifies a font by asset handle, and Family, which selects a font by its family name.

FontSource implements From<Handle<Font>>. Migration of existing code should only require calling into() on the handle.

Font texture atlases are no longer automatically cleared when the font asset they were generated from is removed. Even after the asset is removed, the font is still accessible using the family name with FontSource::family. Removing the text atlases naively could cause a panic as rendering expects them to be present.

For font_size, wrap the f32 value in a FontSize::Px(...).

Concretely:

// 0.18
TextFont {
    font: asset_server.load("FiraMono-medium.ttf"),
    font_size: 35.,
    ..default()
}

becomes

// 0.19
TextFont {
    font: asset_server.load("FiraMono-medium.ttf").into(),
    font_size: FontSize::Px(35.),
    ..default()
}

experimental_ui_widgets feature is no longer experimental #

PRs:#22934

The experimental_bevy_ui_widgets feature has been renamed to bevy_ui_widgets.

The bevy_ui_widgets feature has been added to the ui feature collection (and thus bevy's default features) for ease of use.

This crate remains immature, and is subject to heavy breaking changes, even relative to Bevy's pre-1.0 standards. However, it is useful enough to see wider adoption, and this change substantially improves the user experience when setting up new projects and running Bevy examples.

Camera TextureFormat rework #

PRs:#23734

ExtractedView::hdr has been moved to ExtractedCamera::hdr. Views do not have a notion of HDR, which is a camera-specific property. TextureFormat::bevy_default() and ViewTargets::TEXTURE_FORMAT_HDR are deprecated, please source your texture format from ExtractedView::target_format instead, and plumb it through your specialization keys. Similarly, ViewTarget::is_hdr was removed. Use ExtractedCamera::hdr to check this instead.

Rodio 0.22 Update #

PRs:#20323

rodio was updated to 0.22 and cpal to 0.17. The following sections will guide you through the necessary changes to ensure compatibility.

Audio Feature Flags #

Audio format related features were reworked with this update.

By default, Bevy will enable the vorbis feature, which supports OGG/VORBIS files through lewton.

If you are not using Bevy's default features, here's a list you can use for reference:

  • vorbis: OGG/VORBIS audio format support (through lewton).
  • wav: WAV audio format support (through hound).
  • mp3: MP3 audio format support (through symphonia).
  • mp4: MP4 audio format support (through symphonia). It also enables AAC support.
  • flac: FLAC audio format support (through claxon).
  • aac: AAC audio format support (through symphonia).

There are also specific symphonia backend flags you can use for certain formats instead of the default flags:

  • symphonia-flac
  • symphonia-vorbis
  • symphonia-wav

Notice that OGG/VORBIS support through symphonia is currently subject to issues with buffering, reverb, looping and spatial audio. Check the following issues/PRs for additional context:

The audio-all-formats feature collection was added for convenience. It will enable bevy_audio and all the available audio formats through their default backends.

Audio Traits #

type DecoderItem was removed from the Decodable trait. Now rodio::Sample is an alias for f32.

The android_shared_stdcxx feature was removed, as cpal's oboe-shared-stdcxx feature was also removed in favor of Android NDK audio APIs.

Keep in mind that if you are using bevy_audio the minimum supported Android API version is now 26 (Android 8/Oreo).

InputFocus fields are no longer public #

PRs:#23723

The .0 field on InputFocus is no longer public. Use the getter and setter methods instead.

In 0.18:

let focused_entity = input_focus.0;
input_focus.0 = Some(entity);
input_focus.0 = None;

In 0.19:

let focused_entity = input_focus.get();
input_focus.set(entity);
input_focus.clear();

Additionally, the core setup of InputFocus and related resources now occurs in InputFocusPlugin, rather than InputDispatchPlugin. This is part of DefaultPlugins, so most users won't need to make any changes.

PlaneMeshBuilder, allow for different number of subdivisions in X and Z directions #

PRs:#19479

It is now possible to assign a different number of subdivisions on the X and Z axis.

The subdivisions field of PlaneMeshBuilder has been split into subdivisions_x and subdivisions_z.

// 0.18:
builder.subdivisions = 4

// 0.19:
builder.subdivisions(4)

EasyScreenRecordPlugin output_dir #

PRs:#23096

EasyScreenRecordPlugin has a new public field output_dir: Option<PathBuf>. If you are constructing this struct manually, you must now include the output_dir field. If you are using ..default(), no changes are needed.

// 0.18
let plugin = EasyScreenRecordPlugin {
    toggle: KeyCode::Space,
    preset: Preset::Medium,
    tune: Tune::Animation,
    frame_time: Duration::from_millis(33),
};

// 0.19
let plugin = EasyScreenRecordPlugin {
    toggle: KeyCode::Space,
    preset: Preset::Medium,
    tune: Tune::Animation,
    frame_time: Duration::from_millis(33),
    output_dir: Some("recordings".into()),
};

Measure changes #

PRs:#23568

Measure::measure no longer takes a separate style: &taffy::Style parameter. Instead the taffy Style is now accessible via a new style field on MeasureArgs.

The width and height fields of MeasureArgs have been renamed to known_width and known_height, respectively.

FullscreenMaterial API changes #

PRs:#23786

FullscreenMaterial::run_in, FullscreenMaterial::run_after and FullscreenMaterial::run_before are replaced by FullscreenMaterial::schedule_configs to configure the system order.

// 0.18
impl FullscreenMaterial for FullscreenEffect {
    fn fragment_shader() -> ShaderRef {
        "shaders/fullscreen_effect.wgsl".into()
    }
    fn run_in() -> impl SystemSet {
        Core3dSystems::PostProcess
    }
    fn run_after() -> Option<Core3dSystems> {
        None
    }
    fn run_before() -> Option<Core3dSystems> {
        None
    }
}

// 0.19
impl FullscreenMaterial for FullscreenEffect {
    fn fragment_shader() -> ShaderRef {
        "shaders/fullscreen_effect.wgsl".into()
    }
    fn schedule_configs(system: ScheduleConfigs<BoxedSystem>) -> ScheduleConfigs<BoxedSystem> {
        system
            .in_set(Core3dSystems::PostProcess)
            .before(tonemapping)
    }
}

set_executor replaced ExecutorKind #

PRs:#23414

ExecutorKind has been removed. Schedules are now configured by passing an executor instance directly via Schedule::set_executor.

  • Schedule::set_executor_kind has been removed. Use Schedule::set_executor instead.
  • Schedule::get_executor_kind has been removed. There is no replacement; executors are no longer identified by an enum variant.
  • SystemExecutor::kind has been removed from the trait.
  • SystemExecutor is now a public trait. You can implement it to provide a fully custom executor.
  • SystemSchedule::systems is now pub.
// 0.18
use bevy::ecs::schedule::ExecutorKind;

schedule.set_executor_kind(ExecutorKind::SingleThreaded);
schedule.set_executor_kind(ExecutorKind::MultiThreaded);
schedule.set_executor_kind(ExecutorKind::default());

// 0.19
use bevy::ecs::schedule::{SingleThreadedExecutor, MultiThreadedExecutor, default_executor};

schedule.set_executor(SingleThreadedExecutor::new());
schedule.set_executor(MultiThreadedExecutor::new());
schedule.set_executor(default_executor());

New ComputedTextBlock::needs_rerender parameters #

PRs:#22614

ComputedTextBlock::needs_rerender takes two new boolean parameters: is_viewport_size_changed and is_rem_size_changed.

is_viewport_size_changed should be true if the local viewport size has changed this frame, and is_rem_size_changed should be true if the rem size (probably corresponding to the value of the RemSize resource) has changed this frame.

MorphWeights and MeshMorphWeights have been restructured #

PRs:#18465

Mesh morph target weights have been restructured to improve flexibility and performance. Users who manually create MeshMorphWeights or MorphWeights components may need to make changes.

In Bevy 0.18, entities with a Mesh3d component could have a MeshMorphWeights component containing morph weight values. In addition, if a parent of the mesh entity had a MorphWeights component then its values would be automatically copied to the MeshMorphWeights component - this allowed multiple meshes to share a single set of weight values.

In Bevy 0.19, MeshMorphWeights has been changed. It can now be either a set of weight values as before, or a reference to an entity containing a MorphWeights component. Referencing replaces the previous automatic copying.

// 0.18
struct MeshMorphWeights { weights: Vec<f32> }

// 0.19
enum MeshMorphWeights {
    Value { weights: Vec<f32> },
    Reference(Entity),
}

If you were using MeshMorphWeights on its own, then you just need to use MeshMorphWeights::Value.

If you were using MorphWeights and MeshMorphWeights and relying on the automatic copying, then you need to use MeshMorphWeights::Reference and point it to the entity with MorphWeights.

// 0.18
parent_entity.insert(MorphWeights::new(...));
mesh_entity.insert((mesh, MeshMorphWeights::new(...)));

// 0.19
parent_entity.insert(MorphWeights::new(...));
mesh_entity.insert((mesh, MeshMorphWeights::Reference(parent_entity)));

These changes improve performance due to less copying. They also add flexibility - a MeshMorphWeights component can reference a MorphWeights component on any entity, not just its parent.

As a result of these changes, MorphPlugin was no longer needed and has been removed.

ViewportNode's camera is now an Option<Entity> #

PRs:#23298

The ViewportNode's camera field is now an Option<Entity>. ViewportNodes can now exist without a valid camera.

WindowPlugin exit systems moved to Last #

PRs:#23624

bevy::window::close_when_requested, bevy::window::exit_on_all_closed and bevy::window::exit_on_primary_closed have all been moved into the Last schedule to prevent systems that run after Update and rely on windows existing from panicking on the last frame of the application.

exit_on_all_closed and exit_on_primary_closed have also been added to a new SystemSet, ExitSystems.

rand, glam & uuid updated to latest versions #

PRs:#22928

For rand/rand_core, the RngCore trait is now Rng. The Rng trait is now RngExt, update imports as needed. For the full extent of the changes to rand v0.10, consult the rand book here.

DefaultErrorHandler renamed to FallbackErrorHandler #

PRs:#23610

DefaultErrorHandler has been renamed to FallbackErrorHandler to better reflect its role as the handler of last resort when no specific error handling is performed.

A deprecated type alias is provided for one release to ease migration. To update your code:

// 0.18
world.insert_resource(DefaultErrorHandler(my_error_handler));

// 0.19
world.insert_resource(FallbackErrorHandler(my_error_handler));

The default_error_handler method has similarly been renamed to fallback_error_handler.

Some bevy_camera primitives moved to bevy_math #

PRs:#22684

bevy_camera::primitives::HalfSpace has moved to bevy_math::primitives::HalfSpace. Some parts of bevy_camera::primitives::Frustum have moved to bevy_math::primitives::ViewFrustum.

bevy_camera has some rendering primitives that can be extracted to be more generally useful. To expose them for others to use, some of these primitives and/or functionality have moved to bevy_math.

// 0.18
use bevy_camera::primitives::{Frustum, HalfSpace}
let half_spaces: [HalfSpace; 6] = ...;
let frustum_one: Frustum = Frustum {
  half_spaces
};
let frustum_two: Frustum = Frustum::from_clip_from_world(...);

// 0.19
use bevy_math::primitives::{HalfSpace, ViewFrustum}
use bevy_camera::primitives::Frustum
let half_spaces: [HalfSpace; 6] = ...;
let frustum_one: Frustum = Frustum(
  ViewFrustum {
    half_spaces
});
let frustum_two: Frustum = Frustum(ViewFrustum::from_clip_from_world(...));

define_atomic_id now lives in bevy_utils #

PRs:#22417

bevy_render::define_atomic_id was moved out of bevy_render and into bevy_utils. If you were using bevy::render::define_atomic_id, update to bevy::utils::define_atomic_id.

World::entities_allocator is now World::entity_allocator #

PRs:#22638

World::entities_allocator() has been renamed to World::entity_allocator() to match the type returned (EntityAllocator). Likewise, World::entities_allocator_mut() has been renamed to World::entity_allocator_mut().

Transmission has been moved to bevy_pbr #

Camera3d::screen_space_specular_transmission_steps and Camera3d::screen_space_specular_transmission_quality have been pulled out into a separate component, ScreenSpaceTransmission, and put in bevy_pbr. The field names have been shortened to steps and quality.

Additionally:

  • ScreenSpaceTransmissionQuality has been moved from bevy_camera to bevy_pbr.
  • ScreenSpaceTransmissionQuality is no longer a Resource.
  • ViewTransmissionTexture and Transmissive3d has been moved from bevy_core_pipelines to bevy_pbr.
  • Node3d::MainTransmissivePass is now initialized by PbrPlugin.
  • screen_space_specular_transmission_pipeline_key has become ScreenSpaceTransmissionQuality::pipeline_key.

New Node::direction field #

PRs:#23605

Node has a new field direction, which can be used to set the inline axis direction used for layout. By default it uses InlineDirection::Ltr, which matches the layout behavior before this change.

Feathers widgets moving to BSN #

PRs:#23804

Going forward, BSN will be the primary means to create Feathers widgets. The old spawning functions have been renamed (button is now button_bundle), and will be removed in a future release.

Some of the BSN widgets have changed slightly:

  • button no longer automatically includes flex_grow. This was originally added due to the difficulty of overriding node styles when spawning, but in BSN that's no longer a problem.
  • button, checkbox and radio now accept a caption parameter which lets you specify the label directly instead of appending them via Children.

Hdr moved to bevy_camera #

PRs:#22683

Hdr has been moved from bevy_render to bevy_camera.

Furthermore, it is no longer extracted to the render world. If you were relying on its presence in the render world, consider using ExtractedCamera::hdr instead.

get_full_extension now returns Option<&str>. #

PRs:#23105

Previously, AssetPath::get_full_extension returned Option<String>. Now it returns Option<&str>. To keep the original behavior, change the following:

// 0.18
asset_path.get_full_extension()

To:

// 0.19
asset_path.get_full_extension().map(ToString::to_string)

Mesh pipeline key requires strip index format bits #

PRs:#22188

BaseMeshPipelineKey and Mesh2dPipelineKey now have STRIP_INDEX_FORMAT_* bits because strip_index_format will be required by wgpu and primitive restart is always enabled.

The strip index format bits in the mesh pipeline key must match the mesh index format for indexed strip topologies (For non-indexed strip topologies, the bits don't matter), and must be STRIP_INDEX_FORMAT_NONE for non-strip topologies. The from_primitive_topology method of mesh pipeline key has been changed to from_primitive_topology_and_strip_index to handle it and RenderMesh now has an index_format method.

In 0.18:

let key = MeshPipelineKey::from_primitive_topology(render_mesh.primitive_topology());

In 0.19:

let key = MeshPipelineKey::from_primitive_topology_and_strip_index(
    render_mesh.primitive_topology(),
    render_mesh.index_format(),
);

Core prefix removed from UI widget components #

  • CoreScrollbarThumb has been renamed to ScrollbarThumb.
  • CoreScrollbarDragState has been renamed to ScrollbarDragState.
  • CoreSliderDragState has been renamed to SliderDragState.

Additionally, ScrollbarThumb nodes are now laid out after ui_layout_system by update_scrollbar_thumb. ScrollbarThumb entities do not have a Node component. The only layout options are for borders, which can be set using ScrollbarThumb's new border and border_radius fields.

Lifecycle observers include old and new archetypes #

PRs:#22828

Lifecycle observers now include information about the old and new archetypes during a change in the EntityComponentsTrigger. As all of the fields for this struct are pub, adding new ones is a breaking change.

If you were pattern matching the components field on EntityComponentsTrigger, you will need to add .. to the pattern.

// 18.0
let EntityComponentsTrigger { components } = e.trigger();

// 19.0
let EntityComponentsTrigger { components, .. } = e.trigger();

If you were constructing an EntityComponentsTrigger manually, you will need to supply values for old_archetype and new_archetype.

// 18.0
world.trigger_with(
    event,
    EntityComponentsTrigger {
        components: &[component_a],
    },
);

// 19.0
world.trigger_with(
    event,
    EntityComponentsTrigger {
        components: &[component_a],
        old_archetype: None,
        new_archetype: None,
    },
);

RelationshipAccessor improvements #

PRs:#23280

RelationshipAccessor now has additional fields:

  • allow_self_referential that stores the value of Relationship::ALLOW_SELF_REFERENTIAL
  • relationship_target for RelationshipAccessor::Relationship and relationship for RelationshipAccessor::RelationshipTarget which store the ComponentId of the counterpart component.

ComponentDescriptor::new_with_layout now takes Option<RelationshipAccessorInitializer> instead of Option<RelationshipAccessor>, which requires providing a way to get ComponentId of the counterpart component.

Change lists #

PRs:#22966

Previously, Bevy required rendering phases to iterate over all visible entities to determine which objects changed via ticks. This became a bottleneck, so Bevy now uses change lists instead. This change affects custom render phases; if you aren't creating your own render phases, you shouldn't have to update any code.

In the render world, the list of changed items can now be accessed in the new DirtySpecializations resource. In specialize systems, use code like the following:

// First, remove meshes that need to be respecialized, and those that were removed, from the bins.
for &main_entity in dirty_specializations
    .iter_to_dequeue(view.retained_view_entity, render_visible_mesh_entities)
{
    opaque_phase.remove(main_entity);
}

// Specialize new meshes.
for (render_entity, visible_entity) in dirty_specializations.iter_to_queue(
    view.retained_view_entity,
    render_visible_mesh_entities,
    &view_pending_mesh_queues.prev_frame,
) {
    ...
}

In queue systems, use code like this:

// First, remove meshes that need to be respecialized, and those that were removed, from the bins.
for &main_entity in dirty_specializations
    .iter_to_dequeue(view.retained_view_entity, render_visible_mesh_entities)
{
    my_phase.remove(Entity::PLACEHOLDER, main_entity);
}

// Now bin new items.
for (render_entity, visible_entity) in dirty_specializations.iter_to_queue(
    view.retained_view_entity,
    render_visible_mesh_entities,
    &view_pending_mesh_queues.prev_frame,
) {
    ...
}

If you need to handle the case in which a mesh might not be able to be specialized and/or queued right away because its dependencies (e.g. materials) haven't loaded yet, there's a new type PendingQueues that can help with this.

Additionally, sorted render phases now use an IndexMap instead of a Vec, so that entities can be added and removed incrementally instead of having to reconstruct the list every frame. This is incompatible with some exotic sorting algorithms that were commonly in use before (e.g. radix sort), so you may need to switch to the built-in sort_unstable method on IndexMap.

The add method on SortedRenderPhase has been split in two. The old behavior of clearing every frame is available as add_transient, while add_retained should be used with the change list system.

See examples/shader_advanced/specialized_mesh_pipeline.rs for a comprehensive example.

bevy_picking feature flag no longer includes bevy_input_focus #

The bevy/bevy_picking feature flag no longer enables bevy_input_focus picking functionality. For context, bevy_input_focus is inherently a bevy_ui related feature, allowing users to select UI elements to focus using their mouse.

Instead, this functionality is now tied to the existing bevy/ui_picking feature, which is itself part of the ui feature collection. In most cases, you should add the ui feature collection to your project if you are using bevy_ui.

If you want to enable bevy_input_focus's picking functionality, but do not want to use bevy_ui, add a separate dependency to the same version of bevy_input_focus in your project and enable the optional bevy_picking feature there.

This change means it now possible to enable bevy_picking without any assumptions about which backend in particular will be used.

PositionedGlyph's byte_index and byte_length fields have been removed #

PRs:#23695

PositionedGlyph's byte_index and byte_length fields have been removed. Unlike Cosmic Text, Parley doesn't expose these values in its GlyphRuns.

If needed, these range can be retrieved using visual_clusters by mapping each cluster's text_range to its corresponding Glyph(s). However, this approach is quite fragile.

Light gizmos have been moved from bevy_gizmos to bevy_light #

PRs:#22583

LightGizmoPlugin, LightGizmoColor, LightGizmoConfigGroup, and ShowLightGizmo have been moved from bevy_gizmos::light to bevy_light.

DataFormat renamed to TextureChannelLayout #

PRs:#23267

bevy_image::DataFormat is renamed to bevy_image::TextureChannelLayout. Replace all references and imports.

Contact Shadows #

PRs:#22382

The shadows_enabled field on PointLight, DirectionalLight, and SpotLight has changed to shadow_maps_enabled.

This was changed because these lights now support contact shadows, and have a contact_shadows_enabled field. The old shadows_enabled field only configures shadow maps, making the old name misleading.

Implementations of Reader now must implement Reader::seekable, and AsyncSeekForward is deleted. #

PRs:#22182

The Reader trait no longer requires implementing AsyncSeekForward and instead requires implementing Reader::seekable, which will cast the Reader to &mut dyn SeekableReader if it supports AsyncSeek (SeekableReader: Reader + AsyncSeek).

// If MyReader implements `AsyncSeek` 
impl Reader for MyReader {
    fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
        Ok(self)
    }
}

// If MyReader does not implement `AsyncSeek` 
impl Reader for MyReader {
    fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
        Err(ReaderNotSeekableError)
    }
}

Since we now just use the AsyncSeek trait, we've deleted the AsyncSeekForward trait. Users of this trait can migrate by calling the AsyncSeek::poll_seek method with SeekFrom::Current(offset), or the AsyncSeekExt::seek method.

AnimationTargetId algorithm changes #

PRs:#22876

The algorithm used to calculate AnimationTargetId has changed. This fixes a bug where different joint hierarchies could mistakenly be assigned the same id.

If you have serialized data containing AnimationTargetId values then these will need to be recalculated.

Bloom luma calculation now in linear space #

PRs:#22561

The luma calculation for bloom's Karis average (used for downsampling) has been corrected to use linear color space instead of non-linear sRGB space.

As a result, the intensity of the bloom effect may appear reduced, especially for colors with high saturation or those that were significantly affected by the previous non-linear calculation.

If your scene's bloom now appears too dim, you can:

  • Increase the intensity field on the Bloom component.
  • Increase the emissive strength of your materials.
  • Adjust the prefilter settings in the Bloom component.

Render Graph as Systems #

PRs:#22144

The RenderGraph API has been removed. Render passes are now systems that run in Core3d or Core2d schedules.

In 0.18:

impl ViewNode for MyNode {
    type ViewQuery = (&'static ExtractedCamera, &'static ViewTarget);

    fn run<'w>(
        &self,
        _graph: &mut RenderGraphContext,
        render_context: &mut RenderContext<'w>,
        (camera, target): QueryItem<'w, Self::ViewQuery>,
        world: &'w World,
    ) -> Result<(), NodeRunError> {
        // ...
    }
}

render_app
    .add_render_graph_node::<ViewNodeRunner<MyNode>>(Core3d, MyLabel)
    .add_render_graph_edges(Core3d, (Node3d::Foo, MyLabel, Node3d::Bar));

In 0.19:

pub fn my_render_pass(
    world: &World,
    view: ViewQuery<(&ExtractedCamera, &ViewTarget)>,
    mut ctx: RenderContext,
) {
    let (camera, target) = view.into_inner();
    // ...
}

render_app.add_systems(
    Core3d,
    my_render_pass
        .after(foo_pass)
        .before(bar_pass)
        .in_set(Core3dSystems::MainPass),
);

The ViewNode trait is replaced by a regular system using the ViewQuery parameter. RenderContext is now a system parameter instead of being passed as &mut. Use .before() / .after() with the actual system functions (e.g., main_opaque_pass_3d) rather than Node3d labels.

System sets Core3dSystems::Prepass, MainPass, and PostProcess are available for coarse ordering. The RenderGraph schedule, available as bevy::render::renderer::RenderGraph, remains as the top-level schedule for non-camera rendering.

DespawnOnEnter / DespawnOnExit can now trigger during same state transitions #

PRs:#23390

In bevy_state, you can define states and transition between them. For example, this can be used to transition between AppState::Menu and AppState::InGame, and run different logic depending on the state the App is in. In 0.18, it became possible to transition from a state to itself: A 'same state transition'. However, there was a bug that made it so that DespawnOnEnter and DespawnOnExit did not trigger for same state transition when they should have.

In 0.19, this bug is fixed. If your application transitions between states using NextState::set(), your application will trigger DespawnOnEnter and DespawnOnExit, even in same state transitions.

If this is undesired, use NextState::set_if_neq() to transition between states. set_if_neq() does not run any state transition schedules if the target state is the same as the current one.

Lifecycle event changes #

PRs:#22789

Replace has been renamed to Discard and ComponentHooks::on_replace has been renamed to ComponentHooks::on_discard. The #[component(on_replace = ...)] derive attribute is now #[component(on_discard = ...)]. Replace all references and imports.

ui feature is now no longer implied by the 3d or 2d features #

PRs:#23180

Swapping the UI framework for your Bevy project is a common form of customization. We think that users should be able to do this easily, without having to give up the ease of use (and updates!) that come with our top-level feature collections.

To achieve this, the ui feature collection is now no longer implied by the 3d or 2d feature collection.

To migrate:

  • If you used all default features before, nothing changes for you.
  • If you want to opt out of using bevy_ui, it is now as simple as disabling default features, and manually opting into 3d or 2d (and optionally audio).
  • If you already opted into non-default features and want to continue using bevy_ui, you will now have to add the ui feature.

Skybox moved to bevy_light #

PRs:#22682

Skybox has been moved from bevy_core_pipelines to bevy_light.

Command error handling has been simplified #

The Command trait now takes Out as an associated type rather than as a generic parameter. For function-style commands that return a Result, the code changes as follows:

// 0.18
fn my_command() -> impl Command<Result> {
    move |world: &mut World| -> Result {
        // ...
    }
}

// 0.19
fn my_command() -> impl Command {
    move |world: &mut World| -> Result {
        // ...
    }
}

Implementors of the Command trait must now fill in the Out associated type:

// 0.18
impl Command for Foo {
    fn apply(self, world: &mut World) {
        // ...
    }
}

// 0.19
impl Command for Foo {
    type Out = ();

    fn apply(self, world: &mut World) {
        // ...
    }
}

For commands that return Result:

// 0.18
impl Command<Result> for Foo {
    fn apply(self, world: &mut World) -> Result {
        // ...
    }
}

// 0.19
impl Command for Foo {
    type Out = Result;

    fn apply(self, world: &mut World) -> Result {
        // ...
    }
}

The functionality of the HandleError and CommandWithEntity traits have been folded into Command and EntityCommand, respectively. Use the latter traits as the sole trait bound, if needed.

Added mouse panning to PanCamera component #

PRs:#22859

By default PanCamera now includes mouse panning.

To go back to the keyboard only panning, you need to set the enabled field of the PanCamera's mouse_pan_settings field to false.

The validate_parent_has_component is superseded by ValidateParentHasComponentPlugin #

PRs:#22675

The validate_parent_has_component insert hook has been replaced by a plugin: ValidateParentHasComponentPlugin. This uses an observer, a resource, and a system to achieve a more robust (and less spurious) warning for invalid configuration of entities.

SystemParam validation is now done when fetching the data #

PRs:#23225

In an effort to improve performance by reducing redundant data fetches and simplifying internals, system parameter validation is now done as part of fetching the data for those system parameters. To be more precise:

  • SystemParam::get_param now returns a Result<Self::Item<'world, 'state>, SystemParamValidationError>, instead of simply a Self::Item<'world, 'state>.
    • If validation fails, an appropriate SystemParamValidationError should be returned.
    • If validation passes, the item should be returned wrapped in Ok.
  • SystemParam::validate_param has been removed.
    • All logic that was done in this method should be moved to the get_param method of that type.
  • SystemState::validate_param has been removed.
    • Validation now happens automatically when calling get, get_mut, or get_unchecked.
  • SystemState::fetch, get_unchecked, get and get_mut now return a Result<..., SystemParamValidationError>. Callers that previously destructured the result directly will need to add .unwrap() or handle the Result:
// 0.18
let (res, query) = system_state.get(&world);

// 0.19
let (res, query) = system_state.get(&world).unwrap();

When executing systems, we no longer check for system validation before running the systems. As a result of these changes, System::validate_param and System::validate_param_unsafe have been removed. Instead, validation has been moved to be part of the trait implementation for System::run_unsafe. All implementations of the System trait should validate that their parameters are valid during this method, bubbling up any errors originating in SystemParam::get_param.

Custom SystemParam implementations #

If you have a custom SystemParam implementation, you need to:

  1. Remove the validate_param method.
  2. Move any validation logic into get_param.
  3. Change get_param to return Result<Self::Item<'world, 'state>, SystemParamValidationError>.
// 0.18
unsafe impl SystemParam for MyParam<'_> {
    // ...
    unsafe fn validate_param(
        state: &Self::State,
        system_meta: &SystemMeta,
        world: UnsafeWorldCell,
    ) -> Result<(), SystemParamValidationError> {
        // validation logic
        if !is_valid(state, world) {
            return Err(SystemParamValidationError::invalid::<Self>("not valid"));
        }
        Ok(())
    }

    unsafe fn get_param<'w, 's>(
        state: &'s mut Self::State,
        system_meta: &SystemMeta,
        world: UnsafeWorldCell<'w>,
        change_tick: Tick,
    ) -> Self::Item<'w, 's> {
        // fetch logic
        MyParam { /* ... */ }
    }
}

// 0.19
unsafe impl SystemParam for MyParam<'_> {
    // ...
    unsafe fn get_param<'w, 's>(
        state: &'s mut Self::State,
        system_meta: &SystemMeta,
        world: UnsafeWorldCell<'w>,
        change_tick: Tick,
    ) -> Result<Self::Item<'w, 's>, SystemParamValidationError> {
        // validation logic merged into get_param
        if !is_valid(state, world) {
            return Err(SystemParamValidationError::invalid::<Self>("not valid"));
        }
        // fetch logic
        Ok(MyParam { /* ... */ })
    }
}

Custom ExclusiveSystemParam implementations #

Similarly, ExclusiveSystemParam::get_param now returns a Result<Self::Item<'s>, SystemParamValidationError> instead of Self::Item<'s>. Existing implementations should wrap their return value in Ok(...) and return an appropriate SystemParamValidationError if validation fails.

// 0.18
impl ExclusiveSystemParam for MyExclusiveParam {
    // ...
    fn get_param<'s>(
        state: &'s mut Self::State,
        system_meta: &SystemMeta,
    ) -> Self::Item<'s> {
        MyExclusiveParam { /* ... */ }
    }
}

// 0.19
impl ExclusiveSystemParam for MyExclusiveParam {
    // ...
    fn get_param<'s>(
        state: &'s mut Self::State,
        system_meta: &SystemMeta,
    ) -> Result<Self::Item<'s>, SystemParamValidationError> {
        Ok(MyExclusiveParam { /* ... */ })
    }
}

Custom System implementations #

If you have a custom System implementation, remove the validate_param_unsafe method. Parameter validation should now occur inside run_unsafe by propagating errors from SystemParam::get_param.

MultithreadedExecutor performance changes #

For the parallel MultithreadedExecutor, validation was previously done as a cheap pre-validation step, while checking run conditions. Now, tasks will be spawned for systems which would fail or are skipped during validation.

In most cases, avoiding the extra overhead of looking up the required data twice should dominate. However, this change may negatively affect systems which are frequently skipped (e.g. due to Single). If you find that this is a significant performance overhead for your use case, the previous behavior can be recovered by adding run conditions.

ExtractComponent refactor #

Previously, SyncComponentPlugin/ExtractComponentPlugin would despawn the render entity if the synced component was removed. This removes all the derived components too. In 0.19, the render entity is no longer despawned and only the Target components of the SyncComponent trait are removed.

SyncComponent is a subtrait of ExtractComponent and you must implement it to clean up extracted and derived components.

impl SyncComponent for MyComponent {
    type Target = (Self, OtherDerivedComponents);
}

impl ExtractComponent for MyComponent {
    type QueryData = ();
    type QueryFilter = ();
    type Out = Self;

    fn extract_component(
        item: QueryItem<'_, '_, Self::QueryData>,
    ) -> Option<Self::Out> {
        Some(*item)
    }
}

You can also specify the sync target (default to Self) using extract_component_sync_target attribute in derive macros.

#[derive(Component, ExtractComponent)]
#[extract_component_sync_target((Self, OtherDerivedComponents))]
struct MyComponent;

Both SyncComponent and ExtractComponent have also gotten an optional marker type that can be used to bypass orphan rules, see the docs for details.

Nested query access #

PRs:#21557

Queries are now able to access data from multiple entities in the same query item. This will be used to support richer querying across relations, such as by querying components from an entity's parent.

However, some query operations are not sound for queries that access multiple entities, and need additional trait bounds to ensure they are only used soundly.

An IterQueryData bound has been added to iteration methods on Query:

  • into_iter
  • iter_many_unique_mut / iter_many_unique_unsafe / iter_many_unique_inner
  • get_many_mut / get_many_inner / get_many_unique_mut / get_many_unique_inner
  • par_iter_mut / par_iter_inner / par_iter_many_unique_mut
  • single_mut / single_inner
  • iter_combinations_mut / iter_combinations_inner / iter_combinations_unsafe

iter_mut, iter_unsafe, and iter_inner may be called with non-iterable data, but the resulting QueryIter will not impl Iterator. It may be iterated using streaming or lending iteration by calling the new fetch_next method.

iter, iter_many, par_iter, single, and iter_combinations have no extra bounds, since read-only queries are always sound to iterate. iter_many_mut and iter_many_inner methods have no extra bounds, either, since they already prohibit concurrent access to multiple entities.

In addition, a SingleEntityQueryData bound has been added to

  • The EntityRef::get_components family of methods
  • The Traversal trait
  • The Query::transmute and Query::join families of methods
  • The QueryIter::sort family of methods

All existing query types will satisfy those bounds, but generic code may need to add bounds.

// 0.18
fn generic_func<D: QueryData>(query: Query<D>) {
    for item in &mut query { ... }
}
// 0.19
fn generic_func<D: IterQueryData>(query: Query<D>) {
    for item in &mut query { ... }
}
// 0.19, but with support for non-iterable query types
fn generic_func<D: QueryData>(mut query: Query<D>) {
    let mut iter = query.iter_mut();
    while let Some(item) = iter.fetch_next() { ... }
}

Conversely, manual implementations of QueryData may want to implement IterQueryData and SingleEntityQueryData if appropriate.

Finally, two new methods have been added to WorldQuery: init_nested_access and update_archetypes. Manual implementations of WorldQuery should implement those methods as appropriate. Queries that only access the current entity may leave them empty, but queries that delegate to other implementations, especially generic ones, should delegate the new methods as well.

StaticTransformOptimizations no longer stores a threshold for dynamic toggling #

PRs:#23193

The threshold has been removed completely from StaticTransformOptimizations: the optimization is always either enabled or disabled. As a result this is now a simple enum, and some method calls will need to be updated.

If you want to toggle this dynamically, you can count the entities in a system and dynamically enable or disable this. Performing this check can be slow however, so you probably should not perform this check each frame.

Mesh view bind group layout is changed #

PRs:#23982

MeshPipelineViewLayouts no longer stores all possible view layouts. Now it only stores necessary parameters for creating bind group layouts on demand. And MeshPipelineViewLayouts::get_view_layout returns MeshPipelineViewLayout by value instead of by reference.

generate_view_layouts is removed and layout_entries is private now. Please use MeshPipelineViewLayouts::get_view_layout.

Mesh view bind group layout has more variants now and some dynamic uniforms such as distance fog, ssr, contact shadows, and environment map are not guaranteed to exist. Please use MeshViewBindGroup::main_offsets to get the dynamic offsets.

In 0.18:

    let mut offsets: SmallVec<[u32; 8]> = smallvec![
        view_uniform_offset.offset,
        view_lights_offset.offset,
        view_fog_offset.offset,
        **view_light_probes_offset,
        **view_ssr_offset,
        **view_contact_shadows_offset,
        **view_environment_map_offset,
    ];
    if let Some(oit_settings_offset) = maybe_oit_settings_offset {
        offsets.push(oit_settings_offset.offset);
    }
    pass.set_bind_group(I, &mesh_view_bind_group.main, &offsets);

In 0.19:

    pass.set_bind_group(
        I,
        &mesh_view_bind_group.main,
        &mesh_view_bind_group.main_offsets,
    );

Occlusion culling is no longer experimental #

PRs:#22631

Occlusion culling is no longer experimental, as all known issues that caused Bevy to cull meshes incorrectly are fixed. Consequently, the bevy::render::experimental::occlusion_culling module has been renamed to simply bevy::render::occlusion_culling.

Morph targets are now stored in meshes. #

Previously, morph targets were stored as a Handle<Image> in a Mesh. Now, morph targets are stored inside the Mesh itself.

As a consequence, Gltf assets no longer provide a GltfAssetLabel::MorphTarget subasset. This subasset can be replaced with the corresponding GltfAssetLabel::Primitive to look up the correct Mesh, followed by Mesh::get_morph_targets.

Advanced AssetServer load variants are now exposed through a builder pattern. #

In previous versions of Bevy, there were many different ways to load an asset:

  • AssetServer::load
  • AssetServer::load_acquire
  • AssetServer::load_untyped
  • AssetServer::load_acquire_override_with_settings
  • etc.

All these variants have been simplified to only two variants:

  1. AssetServer::load(): This is a convenience method and just calls the load builder internally.
  2. AssetServer::load_builder(): allows for constructing more complex loads like untyped loads, loads including guards, loads with settings, etc.

Every load variant above can be reimplemented using load_builder, and each one of these methods has deprecation messages on them explaining their new equivalent. For example, load_with_settings_override can now be replaced with:

asset_server
    .load_builder()
    .with_settings(settings)
    .override_unapproved()
    .load(path)

NestedLoader #

To match this change, NestedLoader has been replaced with NestedLoadBuilder. Similarly, LoadContext::loader has been replaced with LoadContext::load_builder. The various calls have now been simplified:

  • context.loader().load(path) -> context.load_builder().load(path)
  • context.loader().with_dynamic_type(type_id).load(path) -> context.load_builder().load_erased(type_id, path)
  • context.loader().with_unknown_type().load(path) -> context.load_builder().load_untyped(path)
  • context.loader().immediate().load(path) -> context.load_builder().load_value(path)
  • context.loader().immediate().with_dynamic_type(type_id).load(path) -> context.load_builder().load_erased_value(type_id, path)
  • context.loader().immediate().with_unknown_type().load(path) -> context.load_builder().load_untyped_value(path)
  • context.loader().immediate().with_reader(reader).load(path) -> context.load_builder().load_value_from_reader(path, reader)
  • context.loader().immediate().with_reader(reader).with_dynamic_type(type_id).load(path) -> context.load_builder().load_erased_value_from_reader(type_id, path, reader)
  • context.loader().immediate().with_reader(reader).with_unknown_type().load(path) -> context.load_builder().load_untyped_value_from_reader(path, reader)

PipelineCacheError renamed to ShaderCacheError #

PRs:#22362

PipelineCacheError has been renamed to ShaderCacheError and the ProcessShaderError variant has been Boxed.

ViewTarget's output accessors now return Option #

PRs:#23959

ViewTarget::out_texture, out_texture_color_attachment, and out_texture_view_format now return an Option. They are None when the render target has no output surface this frame, e.g. an occluded swap chain or a camera with CameraOutputMode::Skip. Nodes that blit to the output should short-circuit when given None.

SystemBuffer requires queue() to be implemented #

PRs:#22832

SystemBuffer now requires queue() to be implemented, instead of apply(). apply()'s default implementation now delegates to queue().

This is to ensure that a SystemBuffer used in an observer context applies its changes. In most cases, if apply() does not change the World structurally, apply() and queue() can mutate the World directly in the same way.

If apply() does not change the World structurally, apply() should be changed to queue():

// 0.18
impl SystemBuffer for MySystemBuffer {
  fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) {
    // your impl here
  }
}

// 0.19
impl SystemBuffer for MySystemBuffer {
  fn queue(&mut self, system_meta: &SystemMeta, mut world: DeferredWorld) {
    // your impl here, using a DeferredWorld instead
  }
}

If apply() does change the World structurally, implement both apply() and queue(). To queue structural changes to a DeferredWorld, add the structural changes to its command queue, accessible via world.commands().

FontAtlas changes #

PRs:#23012

The texture atlas layout for font atlases is no longer stored as a separate asset. Instead, it is stored directly in the texture_atlas field of FontAtlas.

The TextureAtlasLayout parameters of FontAtlas's new and add_glyph_to_atlas methods have been removed.

FontAtlas::add_glyph's offset parameter has been changed from an IVec2 to a Vec2

GlyphAtlasInfo's texture_atlas and location fields have been removed, replaced by rect and offset fields.

The size field has been removed from PositionedGlyph. The glyph's size can now be obtained from the Rect stored in the atlas_info: GlyphAtlasInfo field.

GlyphAtlasLocation::offset is now a Vec2.

ComputedNode::stack_index has been replaced by ComputedStackIndex #

PRs:#23878

The stack_index field has been removed from ComputedNode. Instead the stack index for each UI node is stored on a specialized component ComputedStackIndex, which is required by ComputedNode.

Rename FeathersPlugin to FeathersCorePlugin #

PRs:#23771

FeathersPlugin has been renamed to FeathersCorePlugin to reduce confusion with FeathersPlugins, the PluginGroup.

Remove android game activity from default #

PRs:#23708

Bevy previously had android-game-activity as part of its default features. Users that wanted to use android-native-activity instead, had to disable default-features and define all features plus android-native-activity explicitly.

Both options are no longer part of default-features, but they need to be added explicitly.

For apps using GameActivity you need to add the android-game-activity feature to your Cargo.toml:

bevy = { version = "0.19", features = ["android-game-activity"] }

For apps using NativeActivity you no longer have to define all features explicitly, you can simply use:

bevy = { version = "0.19", features = ["android-native-activity"] }

SavedAsset now contains two lifetimes, and AssetSaver now takes an AssetPath. #

PRs:#22622

SavedAsset now holds two lifetimes instead of one. This is primarily used in the context of AssetSaver. AssetSaver also now takes an AssetPath. So previously, users may have:

// 0.18
impl AssetSaver for MySaver {
    type Asset = MyAsset;
    type Settings = ();
    type OutputLoader = MyLoader;
    type Error = std::io::Error;

    async fn save(
        &self,
        writer: &mut Writer,
        asset: SavedAsset<'_, Self::Asset>,
        settings: &Self::Settings,
    ) -> Result<(), Self::Error> {
        todo!()
    }
}

Now with the extra SavedAsset lifetime, and the extra AssetPath, we have:

// 0.19
impl AssetSaver for MySaver {
    type Asset = MyAsset;
    type Settings = ();
    type OutputLoader = MyLoader;
    type Error = std::io::Error;

    async fn save(
        &self,
        writer: &mut Writer,
        asset: SavedAsset<'_, '_, Self::Asset>,
        settings: &Self::Settings,
        asset_path: AssetPath<'_>,
    ) -> Result<(), Self::Error> {
        todo!()
    }
}

In practice, this should not have an impact on usage.

ShaderStorageBuffer renamed to ShaderBuffer #

PRs:#22558

ShaderStorageBuffer has been renamed to ShaderBuffer and GpuShaderStorageBuffer has been renamed to GpuShaderBuffer. Update your imports and type references accordingly.

bevy_text migration from Cosmic Text to Parley #

PRs:#22879

bevy_text now uses Parley for its text layout. For the most part, this change should be invisible to users of bevy_text and Bevy more broadly.

However, some low-level public methods and types (such as FontAtlasKey) have changed to map to parley's distinct API.

This migration should be relatively straightforward. Use the linked PR as an example of the correct migration, but please ask for help (and explain your use case) if you run into difficulties not noted below.

Migration steps:

  • System font discovery now requires you to enable the bevy/system_font_discovery feature. Users on Linux will need the fontconfig library for this. On Ubuntu, this can be done using sudo apt install libfontconfig1-dev.
  • The various methods for setting the fallback font (such as set_serif_family, set_sans_serif_family or set_monospace_family) now return a Result. These will fail if the provided font is not found. By-and-large, you should not need to call these methods: font fallback is handled automatically via fontique through parley, using the system-provided fallback fonts (but see the above note about system font discovery).