Migration Guides

Migration Guide: 0.17 to 0.18

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.

Immutable Entity Events #

PRs:#21408

The mutable methods of EntityEvent (EntityEvent::from and EntityEvent::event_target_mut) have been moved to a separate trait: SetEntityEventTarget

This makes all EntityEvents immutable by default.

SetEntityEventTarget is implemented automatically for propagated events (e.g. #[entity_event(propagate)]).

AssetSources now give an async_channel::Sender instead of a crossbeam_channel::Sender #

PRs:#21626

Previously, when creating an asset source, AssetSourceBuilder::with_watcher would provide users with a crossbeam_channel::Sender. Now, this has been changed to async_channel::Sender.

If you were previously calling sender.send(AssetSourceEvent::ModifiedAsset("hello".into())), now it would be sender.send_blocking(AssetSourceEvent::ModifiedAsset("hello".into())). These channels are very comparable, so finding an analogous method between crossbeam_channel and async_channel should be straight forward.

Feature cleanup #

animation has been renamed to gltf_animation. bevy_sprite_picking_backend has been renamed to sprite_picking. bevy_ui_picking_backend has been renamed to ui_picking. bevy_mesh_picking_backend has been renamed to mesh_picking.

Removed dummy_white_gpu_image #

PRs:#21572

MeshPipeline, Mesh2dPipeline and SpritePipeline no longer have dummy_white_gpu_image.

MeshPipeline and Mesh2dPipeline no longer have get_image_texture() in their impl.

The method build_dummy_white_gpu_image() and get_image_texture() can be used if needed.

TickCells is now ComponentTickCells.

ComponentSparseSet::get_with_ticks now returns Option<(Ptr, ComponentTickCells)> instead of Option<(Ptr, TickCells, MaybeLocation)>.

The following types have been moved from the component module to the change_detection module:

  • Tick
  • ComponentTicks
  • ComponentTickCells
  • CheckChangeTicks

ImageRenderTargets scale_factor field is now an f32 #

PRs:#21054

The scale_factor field on ImageRenderTarget is now an f32 and no longer requires wrapping in FloatOrd.

#[reflect(...)] now supports only parentheses #

PRs:#21400

Previously, the #[reflect(...)] attribute of the Reflect derive macro supported parentheses, braces, or brackets, to standardize the syntax going forward, it now supports only parentheses.

/// 0.17
#[derive(Clone, Reflect)]
#[reflect[Clone]]

/// 0.18
#[derive(Clone, Reflect)]
#[reflect(Clone)]
/// 0.17
#[derive(Clone, Reflect)]
#[reflect{Clone}]

/// 0.18
#[derive(Clone, Reflect)]
#[reflect(Clone)]

Schedule cleanup #

  • ScheduleGraph::topsort_graph has been moved to DiGraph::toposort, and now takes a Vec<N> parameter for allocation reuse.
  • ReportCycles was removed: instead, DiGraphToposortErrors should be immediately wrapped into hierarchy graph or dependency graph ScheduleBuildError variants.
  • ScheduleBuildError::HierarchyLoop variant was removed, use ScheduleBuildError::HierarchySort(DiGraphToposortError::Loop()) instead.
  • ScheduleBuildError::HierarchyCycle variant was removed, use ScheduleBuildError::HierarchySort(DiGraphToposortError::Cycle()) instead.
  • ScheduleBuildError::DependencyLoop variant was removed, use ScheduleBuildError::DependencySort(DiGraphToposortError::Loop()) instead.
  • ScheduleBuildError::DependencyCycle variant was removed, use ScheduleBuildError::DependencySort(DiGraphToposortError::Cycle()) instead.
  • ScheduleBuildError::CrossDependency now wraps a DagCrossDependencyError<NodeId> instead of directly holding two NodeIds. Fetch them from the wrapped struct instead.
  • ScheduleBuildError::SetsHaveOrderButIntersect now wraps a DagOverlappingGroupError<SystemSetKey> instead of directly holding two SystemSetKeys. Fetch them from the wrapped struct instead.
  • ScheduleBuildError::SystemTypeSetAmbiguity now wraps a SystemTypeSetAmbiguityError instead of directly holding a SystemSetKey. Fetch them from the wrapped struct instead.
  • ScheduleBuildWarning::HierarchyRedundancy now wraps a DagRedundancyError<NodeId> instead of directly holding a Vec<(NodeId, NodeId)>. Fetch them from the wrapped struct instead.
  • ScheduleBuildWarning::Ambiguity now wraps a AmbiguousSystemConflictsWarning instead of directly holding a Vec. Fetch them from the wrapped struct instead.
  • ScheduleGraph::conflicting_systems now returns a &ConflictingSystems instead of a slice. Fetch conflicts from the wrapped struct instead.
  • ScheduleGraph::systems_in_set now returns a &HashSet<SystemKey> instead of a slice, to reduce redundant allocations.
  • ScheduleGraph::conflicts_to_string functionality has been replaced with ConflictingSystems::to_string.
  • ScheduleBuildPass::build now takes &mut Dag<SystemKey> instead of &mut DiGraph<SystemKey>, to allow reusing previous toposorts.
  • ScheduleBuildPass::collapse_set now takes &HashSet<SystemKey> instead of a slice, to reduce redundant allocations.
  • simple_cycles_in_component has been changed from a free function into a method on DiGraph.
  • DiGraph::try_into/UnGraph::try_into was renamed to DiGraph::try_convert/UnGraph::try_convert to prevent overlap with the TryInto trait, and now makes use of TryInto instead of TryFrom for conversions.

RenderTarget is now a component #

PRs:#20917

RenderTarget has been moved from a field on Camera to a separate required component.

When spawning a camera, specify RenderTarget as a component instead of setting camera.target:

// 0.17
commands.spawn((
    Camera3d::default(),
    Camera {
        target: RenderTarget::Image(image_handle.into()),
        ..default()
    },
));

// 0.18
commands.spawn((
    Camera3d::default(),
    RenderTarget::Image(image_handle.into()),
));

Winit user events removed #

PRs:#22088

In Bevy 0.17 and earlier, WinitPlugin and EventLoopProxyWrapper was generic over a M: Message type, that could be used to wake up the winit event loop and which was then forwarded to the ECS world. In 0.18 support for this has been removed, and those types are no longer generic.

If you used the default WakeUp type via the event loop proxy, you can still do this by using the new WinitUserEvent type:

// 0.17
fn wakeup_system(event_loop_proxy: Res<EventLoopProxyWrapper<WakeUp>>) -> Result {
    event_loop_proxy.send_event(WakeUp)?;

    Ok(())
}

// 0.18
fn wakeup_system(event_loop_proxy: Res<EventLoopProxyWrapper>) -> Result {
    event_loop_proxy.send_event(WinitUserEvent::WakeUp)?;

    Ok(())
}

If you were using it to send information into the ECS world from outside Bevy, you will need to create your own channel and system that forwards the messages.

System Combinators #

PRs:#20671

CombinatorSystems can be used to combine multiple SystemConditions with logical operators (such as and, or, and xor). Previously, these combinators would propagate any errors made when running the combined systems:

// 0.17
#[derive(Component)]
struct Foo;

// This run condition will fail validation because there is not an entity with `Foo` in the world.
fn fails_validation(_: Single<&Foo>) -> bool {
    // ...
}

fn always_true() -> bool {
    true
}

let mut world = World::new();

// Because `fails_validation` is invalid, trying to run this combinator system will return an
// error.
assert!(world.run_system_once(fails_validation.or(always_true)).is_err());

This behavior has been changed in Bevy 0.18. Now if one of the combined systems fails, it will be considered to have returned false. The error will not be propagated, and the combinator logic will continue:

// 0.18
let mut world = World::new();

// `fails_validation` is invalid, but it is converted to `false`. Because `always_true` succeeds,
// the combinator returns `true`.
assert_eq!(matches!(world.run_system_once(fails_validation.or(always_true)), Ok(true)));

This affects the following combinators:

CombinatorRust Equivalent
anda && b
ora || b
xora ^ b
nand!(a && b)
nor!(a || b)
xnor!(a ^ b)

FunctionSystem Generics #

PRs:#21917

FunctionSystem now has a new generic parameter: In.

Old: FunctionSystem<Marker, Out, F> New: FunctionSystem<Marker, In, Out, F>

Additionally, there's an extra bound on the System and IntoSystem impls related to FunctionSystem:

<F as SystemParamFunction>::In: FromInput<In>

This enabled systems to take as input any compatible type, in addition to the exact one specified by the system function. This shouldn't impact users at all since it only adds functionality, but users writing heavily generic code may want to add a similar bound. See function_system.rs to see how it works in practice.

Removed SimpleExecutor #

PRs:#21176

Bevy has removed the previously deprecated SimpleExecutor, one of the SystemExecutors in Bevy alongside SingleThreadedExecutor and MultiThreadedExecutor (which aren't going anywhere any time soon). The MultiThreadedExecutor is great at large schedules and async heavy work, and the SingleThreadedExecutor is good at smaller schedules or schedules that have fewer parallelizable systems. So what was SimpleExecutor good at? Not much. That's why it was removed. Removing it reduced some maintenance and consistency burdens on maintainers, allowing them to focus on more exciting features!

If you were using SimpleExecutor, consider upgrading to SingleThreadedExecutor instead, or try MultiThreadedExecutor if it fits the schedule. It's worth mentioning that SimpleExecutor ran deferred commands inbetween each system, regardless of if it was needed. The other executors are more efficient about this, but that means they need extra information about when to run those commands. In most schedules, that information comes from the contents and ordering of systems, via before, after, chain, etc. If a schedule that was previously using SimpleExecutor still needs commands from one system to be applied before another system runs, make sure that ordering is enforced explicitly by these methods, rather than implicitly by the order of add_systems. If you are looking for a quick fix, chain is the easiest way to do this.

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> {
        None
    }
}

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.

Remove ron re-export from bevy_scene and bevy_asset #

PRs:#21611

The ron crate is no longer re-exported from bevy_scene or bevy_asset. This was done to reduce naming conflicts and improve API clarity.

If you were importing ron through bevy_scene or bevy_asset, you should now add ron as a direct dependency to your project.

This change only affects code that was explicitly importing the ron module. All internal scene serialization and deserialization functionality remains unchanged.

Rename ThinSlicePtr::get() to ThinSlicePtr::get_unchecked() #

PRs:#21823

ThinSlicePtr::get() has been deprecated in favor of the new ThinSlicePtr::get_unchecked() method in order to more clearly signal that bounds checking is not performed. Beyond the name change, the only difference between these two methods is that get_unchecked() takes &self while get() takes self. In order to migrate, simply rename all usages of get() with get_unchecked():

let slice: &[u32] = &[2, 4, 8];
let thin_slice = ThinSlicePtr::from(slice);

// 0.17
let x = unsafe { thin_slice.get(0) };

// 0.18
let x = unsafe { thin_slice.get_unchecked(0) };

AnimationEventTrigger::animation_player has been renamed to AnimationEventTrigger::target #

PRs:#21593

This field and its docs strongly suggested that it would point to an entity holding an AnimationPlayer, but that actually depends on how the event was registered.

  • If you used AnimationClip::add_event, the field really did point to the AnimationPlayer
  • But if you used AnimationClip::add_event_to_target, this field instead pointed to an AnimationTargetId

To make this more clear, the field was renamed to target and the docs surrounding it improved.

DragEnter now fires on drag starts #

PRs:#21999

DragEnter now also fires when a drag starts over an already hovered entity.

Remove bevy::ptr::dangling_with_align() #

PRs:#21822

bevy::ptr::dangling_with_align() has been removed. Use NonNull::without_provenance() instead:

// 0.17
let ptr = dangling_with_align(align);

// 0.18
let ptr = NonNull::without_provenance(align);

AssetPlugin now has a use_asset_processor_override field. #

PRs:#21409

The AssetPlugin now has a use_asset_processor_override field. If you were previously setting all AssetPlugin fields, you should either use struct update syntax ..Default::default(), or set the new use_asset_processor_override to None.

enable_prepass and enable_shadows are now Material methods #

PRs:#20999

The MaterialPlugin fields prepass_enabled and shadows_enabled have been replaced by the Material methods enable_prepass and enable_shadows.

Analogous methods have also been added to MaterialExtension

// 0.17
MaterialPlugin::<MyMaterial> {
    prepass_enabled: false,
    shadows_enabled: false,
}

// 0.18
impl Material for MyMaterial {
    /// ...

    fn enable_prepass() { false }
    fn enable_shadows() { false }
}

ArchetypeQueryData trait #

PRs:#21581

To support richer querying across relations, Bevy now supports query data that are not archetypal: the query can return entities based on conditions that do not exclusively involve the entity's archetype.

An example of non-archetypal filter is Changed<C>: the entity is filtered based on the archetype (having the component C) but also based on the change ticks of the component.

Code that requires queries to impl ExactSizeIterator may need to replace QueryData bounds with ArchetypeQueryData.

// 0.17
fn requires_exact_size<D: QueryData>(q: Query<D>) -> usize {
    q.into_iter().len()
}

// 0.18
fn requires_exact_size<D: ArchetypeQueryData>(q: Query<D>) -> usize {
    q.into_iter().len()
}

Manual implementations of QueryData will now need to provide the IS_ARCHETYPAL associated constant. This will be true for most existing queries, although queries that wrap other queries should delegate as appropriate. In addition, queries with IS_ARCHETYPAL = true should implement ArchetypeQueryData.

Put input sources for bevy_input under features #

PRs:#21447

bevy_input provides primitives for all kinds of input. But on consoles you usually don't have things like touch. On more obscure platforms, like GBA, only gamepad input is needed.

If you use bevy_window or bevy_gilrs, they will automatically enable the necessary features on bevy_input. If you don't depend on them (for example, if you are developing for a platform that isn't supported by these crates), you need to enable the required input sources on the bevy_input / bevy crate manually:

# 0.17
bevy = { version = "0.17", default-features = false }

# 0.18 (enable sources that you actually use):
bevy = { version = "0.18", default-features = false, features = [
  "mouse",
  "keyboard",
  "gamepad",
  "touch",
  "gestures",
] }

TextLayoutInfo's section_rects field has been replaced with run_geometry #

PRs:#21656

TextLayoutInfo's section_rects field has been removed. In its place is a new field run_geometry that contains the non-glyph layout geometry for a run of glyphs: the run's span index, bounding rectangle, underline position and thickness, and strikethrough position and thickness. A run in bevy_text is a contiguous sequence of glyphs on the same line that share the same text attributes like font, font size, and line height. The coordinates stored in run_geometry are unscaled and relative to the top left corner of the text layout.

Unlike the tuples of section_rects, RunGeometry does not include an Entity id. To find the corresponding text entity, call the entities method on the root text entity’s ComputedTextBlock component and use the span_index to index into the returned slice.

Change Bundle::component_ids and Bundle::get_component_ids to return an iterator. #

Bundle::component_ids and Bundle::get_component_ids were changed to return an iterator over ComponentId and Option<ComponentId> respectively. In some cases this can avoid allocating.

For implementors:

// 0.17
unsafe impl<C: Component> Bundle for C {
    fn component_ids(components: &mut ComponentsRegistrator, ids: &mut impl FnMut(ComponentId)) {
        ids(components.register_component::<C>());
    }

    fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)) {
        ids(components.get_id(TypeId::of::<C>()));
    }
}

// 0.18
unsafe impl<C: Component> Bundle for C {
    fn component_ids<(
        components: &mut ComponentsRegistrator,
    // we use a `use` here to explicitly not capture the lifetime of `components`
    ) -> impl Iterator<Item = ComponentId> + use<C> {
        iter::once(components.register_component::<C>())
    }

    fn get_component_ids(components: &Components) -> impl Iterator<Item = Option<ComponentId>> {
        iter::once(components.get_id(TypeId::of::<C>()))
    }
}

For consumers:

// 0.17
let mut components = vec![];
MyBundle::component_ids(&mut world.components_registrator(), &mut |id| {
    components.push(id);
});

// 0.18
let components: Vec<_> = B::component_ids(&mut world.components_registrator()).collect();

BorderRadius has been added to Node and is no longer a component #

PRs:#21781

BorderRadius is no longer a component, instead a border_radius: BorderRadius field has been added to Node.

Per-RenderPhase Draw Functions #

PRs:#21021

This PR makes draw function labels in MaterialProperties per-RenderPhase instead of per-pass. This should only affect users of the low-level "manual Material" API, and not users of the broader Material API. Specifying all draw functions is not mandatory, but users should specify draw functions for all render phases the material may queue to, or the material may not render.

  • Removed MaterialDrawFunction in favor of:

    • MainPassOpaqueDrawFunction
    • MainPassAlphaMaskDrawFunction
    • MainPassTransmissiveDrawFunction
    • MainPassTransparentDrawFunction
  • Removed PrepassDrawFunction in favor of:

    • PrepassOpaqueDrawFunction
    • PrepassAlphaMaskDrawFunction
  • Removed DeferredDrawFunction in favor of:

    • DeferredOpaqueDrawFunction
    • DeferredAlphaMaskDrawFunction

Generalized Atmospheric Scattering Media #

PRs:#20838

Most of the fields on Atmosphere have been removed in favor of a handle to the new ScatteringMedium asset.

// 0.17
pub struct Atmosphere {
    pub bottom_radius: f32,
    pub top_radius: f32,
    pub ground_albedo: Vec3,
    // All of these fields have been removed.
    pub rayleigh_density_exp_scale: f32,
    pub rayleigh_scattering: Vec3,
    pub mie_density_exp_scale: f32,
    pub mie_scattering: f32,
    pub mie_absorption: f32,
    pub mie_asymmetry: f32,
    pub ozone_layer_altitude: f32,
    pub ozone_layer_width: f32,
    pub ozone_absorption: Vec3,
}

// 0.18
pub struct Atmosphere {
    pub bottom_radius: f32,
    pub top_radius: f32,
    pub ground_albedo: Vec3,
    // This replaces all of the old fields.
    pub medium: Handle<ScatteringMedium>,
}

Unfortunately, this means Atmosphere no longer implements Default. Instead, you can still access the default earthlike atmosphere through the EarthlikeAtmosphere resource:

fn setup_camera(
    mut commands: Commands,
    earthlike_atmosphere: Res<EarthlikeAtmosphere>
) {
    commands.spawn((
        Camera3d,
        earthlike_atmosphere.get(),
    ));
}

Image Loader Array Layout #

PRs:#21628

ImageLoader now supports loading array textures using the new ImageLoaderSettings::array_layout setting.

In previous versions, loading an array texture generally required a system that waited for the asset to load, then called Image::reinterpret_stacked_2d_as_array. Now the ImageLoader can do that for you automatically.

use bevy::image::{ImageLoaderSettings, ImageArrayLayout};

let array_texture = asset_server.load_with_settings(
    "textures/array_texture.png",
    |settings: &mut ImageLoaderSettings| {
        // Load the image as a stacked array of 4 textures.
        settings.array_layout = Some(ImageArrayLayout::RowCount { rows: 4 });
    },
);

Renamed bevy_reflect feature documentation to reflect_documentation #

PRs:#21577

The documentation feature in bevy_reflect has been renamed to reflect_documentation to clarify that it allows reflecting documentation of rust code at runtime.

Tilemap Chunk Layout #

PRs:#21684

TilemapChunk and TilemapChunkTileData's default layout has been changed from the origin being in the top left to the origin being in the bottom left.

The previous layout origin didn't align with Bevy's world coordinate system, so when mapping to and from chunk space (to map a world coordinate to a tile) you would have to account for the chunk y coordinate being inverted.

With the origin of the chunk being in the bottom left, you can simply mod world coordinates to get chunk coordinates.

Some other tiling tools have the convention of the origin being at the top left, but it's more important for Bevy's features to be internally consistent as it allows for better ease of use.

In summary, we renamed clear_* and remove_* methods to detach_*. This should clarify that these methods do not despawn the child entities or related entities.

We renamed several related methods on both EntityCommands and EntityWorldMut:

  • The method EntityCommands::clear_children has been renamed to EntityCommands::detach_all_children.
  • The method EntityWorldMut::clear_children has been renamed to EntityWorldMut::detach_all_children.
  • The method EntityCommands::remove_children has been renamed to EntityCommands::detach_children.
  • The method EntityWorldMut::remove_children has been renamed to EntityWorldMut::detach_children.
  • The method EntityCommands::remove_child has been renamed to EntityCommands::detach_child.
  • The method EntityWorldMut::remove_child has been renamed to EntityWorldMut::detach_child.
  • The method EntityCommands::clear_related has been renamed to EntityCommands::detach_all_related.
  • The method EntityWorldMut::clear_related has been renamed to EntityWorldMut::detach_all_related.

LoadContext::path now returns AssetPath. #

PRs:#21713

LoadContext::asset_path has been removed, and LoadContext::path now returns AssetPath. So the migrations are:

  • load_context.asset_path() -> load_context.path()
  • load_context.path() -> load_context.path().path()
    • While this migration will keep your code running, seriously consider whether you need to use the Path itself. The Path does not support custom asset sources, so care needs to be taken when using it directly. Consider instead using the AssetPath instead, along with AssetPath::resolve_embed, to properly support custom asset sources.

Use Mesh::try_* mesh functions for Assets<Mesh> entries when there can be RenderAssetUsages::RENDER_WORLD-only meshes. #

PRs:#21732

Previously, the Assets<Mesh> resource would not retain RenderAssetUsages::RENDER_WORLD-only meshes once their data was extracted.

In 0.18, Assets<Mesh> retains RenderAssetUsages::RENDER_WORLD-only meshes, even after their data is extracted. To handle such meshes, Mesh now contains Mesh::try_* functions which return a Result<..., MeshAccessError>. These functions return an Err(MeshAccessError,ExtractedToRenderWorld) when the mesh has already been extracted.

If Assets<Mesh> can contain RenderAssetUsages::RENDER_WORLD-only meshes, the following Mesh functions should be changed to their try_* equivalent and handled appropriately:

// assets: Res<'w, Assets<Mesh>>
let mesh = assets.get(some_mesh_handle).unwrap() // or assets.get_mut(some_mesh_handle).unwrap()

// 0.17
mesh.insert_attribute(...)
mesh.with_inserted_attribute(...)
mesh.remove_attribute(...)
mesh.with_removed_attribute(...)
mesh.contains_attribute(...)
mesh.attribute(...)
mesh.attribute_mut(...)
mesh.attributes(...)
mesh.attributes_mut(...)
mesh.insert_indices(...)
mesh.with_inserted_indices(...)
mesh.indices(...)
mesh.indices_mut(...)
mesh.remove_indices(...)
mesh.with_removed_indices(...)
mesh.duplicate_vertices(...)
mesh.with_duplicated_vertices(...)
mesh.compute_normals(...)
mesh.compute_flat_normals(...)
mesh.compute_smooth_normals(...)
mesh.compute_area_weighted_normals(...)
mesh.compute_custom_smooth_normals(...)
mesh.with_computed_normals(...)
mesh.with_computed_flat_normals(...)
mesh.with_computed_smooth_normals(...)
mesh.with_computed_area_weighted_normals(...)
mesh.with_custom_smooth_normals(...)
mesh.transformed_by(...)
mesh.transform_by(...)
mesh.translated_by(...)
mesh.translate_by(...)
mesh.rotated_by(...)
mesh.rotate_by(...)
mesh.scaled_by(...)
mesh.scale_by(...)
mesh.normalize_joint_weights(...)
// when feature = morph enabled
mesh.has_morph_targets(...)
mesh.set_morph_targets(...)
mesh.morph_targets(...)
mesh.with_morph_targets(...)
mesh.set_morph_target_names(...)
mesh.with_morph_target_names(...)
mesh.morph_target_names(...)

// 0.18
mesh.try_insert_attribute(...)
mesh.try_with_inserted_attribute(...)
mesh.try_remove_attribute(...)
mesh.try_contains_attribute(...)
mesh.try_with_removed_attribute(...)
mesh.try_attribute_option(...) // or mesh.try_attribute(...)
mesh.try_attribute_mut_option(...) // or mesh.try_attribute_mut(...)
mesh.try_attributes(...)
mesh.try_attributes_mut(...)
mesh.try_insert_indices(...)
mesh.try_with_inserted_indices(...)
mesh.try_indices_option(...) // or mesh.try_indices(...)
mesh.try_indices_mut_option(...) // or mesh.try_indices_mut(...)
mesh.try_remove_indices(...)
mesh.try_with_removed_indices(...)
mesh.try_duplicate_vertices(...)
mesh.try_with_duplicated_vertices(...)
mesh.try_compute_normals(...)
mesh.try_compute_flat_normals(...)
mesh.try_compute_smooth_normals(...)
mesh.try_compute_area_weighted_normals(...)
mesh.try_compute_custom_smooth_normals(...)
mesh.try_with_computed_normals(...)
mesh.try_with_computed_flat_normals(...)
mesh.try_with_computed_smooth_normals(...)
mesh.try_with_computed_area_weighted_normals(...)
mesh.try_with_custom_smooth_normals(...)
mesh.try_transformed_by(...)
mesh.try_transform_by(...)
mesh.try_translated_by(...)
mesh.try_translate_by(...)
mesh.try_rotated_by(...)
mesh.try_rotate_by(...)
mesh.try_scaled_by(...)
mesh.try_scale_by(...)
mesh.try_normalize_joint_weights(...)
// when feature = morph enabled
mesh.try_has_morph_targets(...)
mesh.try_set_morph_targets(...)
mesh.try_morph_targets(...)
mesh.try_with_morph_targets(...)
mesh.try_set_morph_target_names(...)
mesh.try_with_morph_target_names(...)
mesh.try_morph_target_names(...)
// the following functions do not have a try_ equivalent, but now panic if 
// the mesh data has been extracted to `RenderWorld`.
mesh.get_vertex_size(...)
mesh.get_vertex_buffer_size(...)
mesh.get_index_buffer_bytes(...)
mesh.get_mesh_vertex_buffer_layout(...)
mesh.count_vertices(...)
mesh.create_packed_vertex_buffer_data(...)
mesh.write_packed_vertex_buffer_data(...)

If the calls to Mesh functions (under the 0.17 section) are left unchanged after upgrading to 0.18, they will now panic if the mesh is a RenderAssetUsages::RENDER_WORLD-only mesh that has been extracted to the render world.

Custom asset sources now require a reader. #

PRs:#21721

Previously, it was possible to create asset sources with no reader, resulting in your asset sources silently being skipped. This is no longer possible, since AssetSourceBuilder must now be given a reader to start. We also slightly changed how sources are expected to be built.

// 0.17
AssetSource::build()
    .with_reader(move || /* reader logic */)
    .with_writer(move || /* ... */)
    .with_processed_reader(move || /* ... */)
    .with_processed_writer(move || /* ... */);

// 0.18
AssetSourceBuilder::new(move || /* reader logic */)
    .with_writer(move || /* ... */)
    .with_processed_reader(move || /* ... */)
    .with_processed_writer(move || /* ... */;

ExtractedUiNode's stack_index has been renamed to z_order and is now an f32. #

PRs:#19691

ExtractedUiNode’s stack_index field has been renamed to z_order and its type changed from u32 to f32. Previously stack_index would be converted into an f32 after extraction during the Render schedule, then offsets would be applied to determine draw order before sorting (lowest value rendered first). For example, a node's fill color is given an offset of 0. and a box shadow is given an offset of -0.1, so that the shadow will be drawn behind the node.

Changing the field to an f32, enables finer control of the UI draw order by allowing these offsets to be applied during extraction, and fixes a bug affecting the ordering of texture-sliced nodes.

The non-text areas of UI Text nodes are no longer pickable #

PRs:#22047

Only the sections of Text node's containing text are pickable now, the non-text areas of the node do not register pointer hits. To replicate Bevy 0.17's picking behavior, use an intermediate parent node to intercept the pointer hits.

Cargo Feature Collections #

PRs:#21472

Bevy now has high-level cargo feature collections (ex: 2d, 3d, ui) and mid-level feature collections (ex: 2d_api, 3d_api, default_app, default_platform). This isn't technically a breaking change, but if you were previously disabling Bevy's default features and manually enabling each specific cargo feature you wanted, we highly recommend switching to using the higher level feature collections wherever possible. This will make it much easier to define the functionality you want, and it will reduce the burden of keeping your list of features up to date across Bevy releases.

See the Cargo Feature Collections pull request for a full list of options.

LineHeight is now a separate component #

PRs:#21180

The line_height field has been removed from TextFont. LineHeight is now a component required by Text, Text2d, and TextSpan.

AnimationTarget replaced by separate components #

PRs:#20774

The AnimationTarget component has been split into two separate components. AnimationTarget::id is now an AnimationTargetId component, and AnimationTarget::player is now an AnimatedBy component.

This change was made to add flexibility. It's now possible to calculate the AnimationTargetId first, but defer the choice of player until later.

// 0.17
entity.insert(AnimationTarget { id: AnimationTargetId(id), player: player_entity });

// 0.18
entity.insert((AnimationTargetId(id), AnimatedBy(player_entity)));

Virtual Geometry #

PRs:#21301

Virtual geometry's asset format has changed. You must regenerate MeshletMesh assets.

BevyManifest::shared is now a scope-like API. #

PRs:#20630

In previous versions of Bevy, BevyManifest returned a mapped RwLock guard. Now, it's a scope-like API:

// 0.16
let manifest = BevyManifest::shared();
let path = manifest.get_path("my_bevy_crate");

// 0.17
let path = BevyManifest::shared(|manifest| {
    manifest.get_path("my_bevy_crate")
});

Entities APIs #

Entities are spawned by allocating their id and then giving that id a location within the world. In 0.17, this was done in one stroke through spawn and Entities::flush. In 0.18, the flushing functionality has been removed in favor of spawning individual EntityRows instead. Don't worry, these changes don't affect the common operations like spawn and despawn, but the did impact the peripheral interfaces and error types. For a full explanation of the new entity paradigm, errors and terms, see the new entity module docs. If you want more background for the justification of these changes or more information about where these new terms come from, see pr #19451. This opens up a lot of room for performance improvement but also caused a lot of breaking changes:

Entities rework #

A lot has changed here. First, alloc, free, reserve, reserve_entity, reserve_entities, flush, flush_as_invalid, EntityDoesNotExistError, total_count, used_count, and total_prospective_count have all been removed 😱.

Allocation has moved to the new EntitiesAllocator type, accessible via World::entities_allocator and World::entities_allocator_mut, which have alloc, free, and alloc_many.

Reservation and flushing have been completely removed as they are no longer needed. Instead of reserving an entity and later flushing it, you can EntitiesAllocator::alloc (which does not need mutable access), and World::spawn_at can be used to "flush" the entity.

The counting methods have been reworked in the absence of flushing: len and is_empty now deal with how many entity rows have been allocated (not necessarily the number that have been spawned), and the new count_spawned and any_spawned are similar to the old len and is_empty behavior but are now O(n).

In terms of getting information from Entities, get and contains has been reworked to include non-spawned entities. If you only want spawned entities, get_spawned and contains_spawned are available. Additionally, get now returns Result<Option<EntityLocation>, InvalidEntityError> instead of Option<EntityLocation> for clarity. Entities now may or may not have a location, depending on if it is spawned or not.

EntityDoesNotExistError has been removed and reworked. See the new entity module docs for more, but: When an entity's generation is not up to date with its row, InvalidEntityError is produced. When an entity index's Option<EntityLocation> is None, EntityValidButNotSpawnedError is produced. When an Entity is expected to be spawned but is not (either because its generation is outdated or because its row is not spawned), EntityNotSpawnedError is produced. A few other wrapper error types have slightly changed as well, generally moving from "entity does not exist" to "entity is not spawned".

Entity Ids #

Entity ids previously used "row" terminology, but now use "index" terminology as that more closely specifies its current implementation. As such, all functions and types dealing with the previous EntityRow have had their names 1 to 1 mapped to index. Ex: EntityRow -> EntityIndex, Entity::row -> Entity::index, Entity::from_row -> Entity::from_index, etc. Note that Entity::index did exist before. It served to give the numeric representation of the EntityRow. The same functionality exists, now under Entity::index_u32.

Entity Pointers #

When migrating, entity pointers, like EntityRef, were changed to assume that the entity they point to is spawned. This was not necessarily checked before, so the errors for creating an entity pointer is now EntityNotSpawnedError. This probably will not affect you since creating a pointer to an entity that was not spawned is kinda pointless.

It is still possible to invalidate an EntityWorldMut by despawning it from commands. (Ex: The hook for adding a component to an entity actually despawns the entity it was added to.) If that happens, it may lead to panics, but EntityWorldMut::is_spawned has been added to help detect that.

Entity Commands #

Commands::new_from_entities now also needs &EntitiesAllocator, which can be obtained from UnsafeWorldCell::entities_allocator. Commands::get_entity does not error for non-spawned entities, making it useful to amend an entity you have queued to spawn through commands. If you only want spawned entities, use Commands::get_spawned_entity.

Other entity interactions #

The ArchetypeRow::INVALID and ArchetypeId::INVALID constants have been removed, since they are no longer needed for flushing. If you depended on these, use options instead.

BundleSpawner::spawn_non_existent is now BundleSpawner::construct. World::inspect_entity now errors with EntityNotSpawnedError instead of EntityDoesNotExistError. QueryEntityError::EntityDoesNotExist is now QueryEntityError::NotSpawned. ComputeGlobalTransformError::NoSuchEntity and ComputeGlobalTransformError::MalformedHierarchy now wrap EntityNotSpawnedError.

Removed FontAtlasSets #

PRs:#21345
  • FontAtlasSets has been removed.
  • FontAtlasKey now newtypes a (AssetId<Font>, u32, FontSmoothing).
  • FontAtlasSet is now a resource. It newtypes a HashMap<FontAtlasKey, Vec<FontAtlas>> and derives Deref and DerefMut.
  • Font atlases are looked up directly using a FontAtlasKey, there's no longer a separate AssetId<Font> to FontAtlasKey map.
  • remove_dropped_font_atlas_sets has been renamed to free_unused_font_atlases_system.
  • The FontAtlasSet methods add_glyph_to_atlas, get_glyph_atlas_info, and get_outlined_glyph_texture have been moved into the font_atlas module and reworked into free functions.

get_components, get_components_mut_unchecked now return a Result #

PRs:#21780

get_components, get_components_mut_unchecked, and into_components_mut_unchecked now return a Result<_, QueryAccessError> instead of an Option.

AmbientLight split into a component and a resource #

PRs:#21585

The AmbientLight used to be both a component and a resource. In 0.18, we've split this in two separate structs: AmbientLight and GlobalAmbientLight. The resource GlobalAmbientLight is the default ambient light for the entire world and automatically added by LightPlugin. Meanwhile, AmbientLight is a component that can be added to a Camera in order to override the default GlobalAmbientLight. When appropriate, rename AmbientLight to GlobalAmbientLight.

// 0.17
app.insert_resource(AmbientLight {
    color: Color::WHITE,
    brightness: 2000.,
    ..default()
});

// 0.18
app.insert_resource(GlobalAmbientLight {
    color: Color::WHITE,
    brightness: 2000.,
    ..default()
});

Derive on Resource will fail when using non-static lifetimes #

PRs:#21385

Any type with #[derive(Resource)] that uses non-static lifetime will no longer compile.

// Will no longer compile in 0.18, `'a` should be `'static`.
#[derive(Resource)]
struct Foo<'a> {
   bar: &'a str
}

RenderPipelineDescriptor and ComputePipelineDescriptor now hold a BindGroupLayoutDescriptor #

PRs:#21205

In previous versions of Bevy, RenderPipelineDescriptor and ComputePipelineDescriptor held a BindGroupLayout to describe the layout of shader bind groups, depending directly on wgpu's BindGroupLayout. Now, they hold a new type BindGroupLayoutDescriptor which holds the BindGroupLayoutEntrys directly. The descriptors are used to create BindGroupLayouts when they are first needed by a pipeline, and cached for reuse.

Concretely, this means wherever you were using a RenderDevice to create a BindGroupLayout to store in a RenderPipelineDescriptor or ComputePipelineDescriptor, you will now create a BindGroupLayoutDescriptor:

// 0.17
let bind_group_layout = render_device.create_bind_group_layout(
    // ...
);
commands.insert_resource(MyPipeline {
    bind_group_layout,
    /// ...
});
// ...
let bind_group = render_context.render_device().create_bind_group(
    None
    &my_pipeline.bind_group_layout,
    // ...
);

// 0.18
let bind_group_layout = BindGroupLayoutDescriptor::new(
    // ...
);
commands.insert_resource(MyPipeline {
    bind_group_layout,
    /// ...
});
// ...
let bind_group = render_context.render_device().create_bind_group(
    None
    &pipeline_cache.get_bind_group_layout(&my_pipeline.bind_group),
    // ...
);

Gizmos::cuboid has been renamed to Gizmos::cube #

PRs:#21393

Gizmos::cuboid was renamed to Gizmos::cube.

API for working with Relationships and RelationshipTargets in type-erased contexts #

PRs:#21601

ComponentDescriptor now stores additional data for working with relationships in dynamic contexts. This resulted in changes to ComponentDescriptor::new_with_layout:

  • Now requires additional parameter relationship_accessor, which should be set to None for all existing code creating ComponentDescriptors.

Internal has been removed #

PRs:#21623

The Internal component, previously added as a required component to both one-shot systems and observer entities has been removed.

You can remove all references to it: these entities are no longer hidden by default query filters. If you have tests which rely on a specific number of entities existing in the world, you should refactor them to query for entities with a component that you care about: this is much more robust in general.

This component was previously motivated by two factors:

  1. A desire to protect users from accidentally modifying engine internals, breaking their app in subtle and complex ways.
  2. A unified API for entity inspectors, allowing them to readily distinguish between "engine-internal" and "user-defined" entities.

In practice, we found that this increased user friction and confusion without meaningfully improving robustness. Entity inspectors and similar tools can and should define their own entity categorization functionality: simply lumping all "internal" entities together is rarely helpful.

Automatic Aabb updates for sprites and meshes #

PRs:#18742

Bevy automatically creates an Aabb component for entities containing a mesh or sprite - the Aabb is then used for visibility and picking.

In 0.17 the Aabb was not updated if the mesh or sprite was modified. This has been fixed in 0.18. If you were working around the issue by manually updating or removing the Aabb, then the workaround is no longer needed.

// 0.17: Modify the mesh, and remove the `Aabb` so that it's automatically
// recreated from the modified mesh.
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, new_positions);
entity.remove::<Aabb>();

// 0.18: The `Aabb` will be automatically updated.
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, new_positions);

For users who want more control, 0.18 also adds a NoAutoAabb component. This will disable both automatic creation and automatic update of Aabb components.

entity.insert(NoAutoAabb);

Changes to AssetServer and AssetProcessor creation. #

PRs:#21763

Previously AssetServers new and new_with_method_check methods would take AssetSources. Now, these methods take Arc<AssetSources>.

// 0.17
AssetServer::new(
    sources,
    mode,
    watching_for_changes,
    unapproved_path_mode,
)

// 0.18
AssetServer::new(
    // Wrap the sources in an `Arc`.
    Arc::new(sources),
    mode,
    watching_for_changes,
    unapproved_path_mode,
)

AssetProcessor::new has also changed. It now returns to you the Arc<AssetSources> which can (and should) be shared with the AssetServer.

// 0.17
let processor = AssetProcessor::new(sources);

// 0.18
let (processor, sources_arc) = AssetProcessor::new(
    sources,
    // A bool whether the returned sources should listen for changes as asset processing completes.
    false,
);

Same State Transitions #

Setting the next state will now always trigger state transitions like OnEnter and OnExit, even if the state is already the same. If you depend on the previous behavior, you can use the set_if_neq method instead.

// 0.17
next_state.set(State::Menu);

// 0.18
next_state.set_if_neq(State::Menu);

Image::reinterpret_size and Image::reinterpret_stacked_2d_as_array now return a Result #

PRs:#20797

Image::reinterpret_size and Image::reinterpret_stacked_2d_as_array now return a Result instead of panicking.

Previously, calling this method on image assets that did not conform to certain constraints could lead to runtime panics. The new return type makes the API safer and more explicit about the constraints.

To migrate your code, you will need to handle the Result returned by Image::reinterpret_size or Image::reinterpret_stacked_2d_as_array.

Changes to the Process trait in bevy_asset. #

PRs:#21925

ProcessContext no longer includes asset_bytes. This has been replaced by asset_reader. To maintain current behavior in a Process implementation, you can read all the bytes into a memory Vec.

// 0.17
let bytes = context.asset_bytes();

// 0.18
let reader = context.asset_reader();
let mut bytes = vec![];

reader
    .read_to_end(&mut bytes)
    .await
    .map_err(|err| ProcessError::AssetReaderError {
        path: context.path().clone_owned(),
        err: err.into(),
    })?;

BindGroupLayout labels are no longer optional #

PRs:#21573

In previous versions of Bevy, the label of a BindGroupLayout was optional. This practically only applies when implementing AsBindGroup manually without the AsBindGroup derive.

If you were previously omitting the label implementation from a impl AsBindGroup, you now must implement it:

impl AsBindGroup for CoolMaterial {
    // ...

    fn label() -> &'static str {
        // It is customary to make the label the name of the type.
        "CoolMaterial"
    }
}

Traits AssetLoader, AssetTransformer, AssetSaver, and Process all now require TypePath #

PRs:#21339

The AssetLoader, AssetTransformer, AssetSaver, and Process traits now include a super trait of TypePath. This means if you previously had a loader like:

// 0.17
struct MyFunkyLoader {
    add_funk: u32,
}

You will need to add the following derive:

// 0.18
#[derive(TypePath)]
struct MyFunkyLoader {
    add_funk: u32,
}

TypePath comes from bevy_reflect, so libraries may also need to add a dependency on bevy_reflect.

bevy_gizmos rendering split #

PRs:#21536

The rendering backend of bevy_gizmos has been split off into bevy_gizmos_render. If you were using default-features = false and bevy_gizmos and bevy_render, you may want to enable the bevy_gizmos_render feature now.

Replaced Column with ThinColumn #

PRs:#21427

The low-level Column and ThinColumn types in bevy_ecs have been merged into a single type, now called Column but with the api of ThinColumn. This type does not keep track of its own allocated length, and only provides unsafe methods.

Renamed bevy_platform::HashMap::get_many_* to bevy_platform::HashMap::get_disjoint_* #

PRs:#21898

Matching both hashbrown and the std library, we've renamed all the get_many_* methods on bevy_platform::HashMap to get_disjoint_*. So rename:

  • get_many_mut -> get_disjoint_mut
  • get_many_unchecked_mut -> get_disjoint_unchecked_mut
  • get_many_key_value_mut -> get_disjoint_key_value_mut
  • get_many_key_value_unchecked_mut -> get_disjoint_key_value_unchecked_mut

glTF Coordinate Conversion #

PRs:#20394

Bevy 0.17 added options for coordinate conversion of glTF files - GltfPlugin::use_model_forward_direction and GltfLoaderSettings::use_model_forward_direction. In Bevy 0.18 these options have changed. The options are disabled by default, so if you haven't enabled them then your glTFs will work the same as before.

CAUTION: The options are experimental, and their behavior may change in future versions.

The goal of coordinate conversion is to take objects that face forward in the glTF and change them to match the direction of Bevy's Transform::forward. Conversion is necessary because glTF's standard scene forward is +Z, while Bevy's is -Z (although not all glTF files follow the standard, and there are exceptions for cameras and lights).

In 0.17 the conversion was applied to nodes and meshes within glTF scenes. This worked well for some users, but had bugs and didn't work well for other users. In particular, node conversion caused issues with cameras and lights.

In 0.18 there are two changes. Firstly, the use_model_forward_direction option has been renamed to convert_coordinates, and is now a struct with two separate options.

// 0.17
pub struct GltfPlugin {
    use_model_forward_direction: bool,
    // ...
}

// 0.18
pub struct GltfPlugin {
    convert_coordinates: GltfConvertCoordinates,
    // ...
}

pub struct GltfConvertCoordinates {
    rotate_scene_entity: bool,
    rotate_meshes: bool,
}

Secondly, the conversion behavior has changed. Nodes within the glTF scene are no longer converted - instead a new conversion is applied to the scene entity and mesh primitive entities. Whether these changes affect you will depend on how you're using glTFs.

  • If you never enabled the 0.17 conversion then you don't need to change anything - conversion remains disabled by default in 0.18. To check if you enabled the conversion, search for use_model_forward_direction.

  • If you simply spawn your glTF via SceneRoot and want it to visually match the Transform::forward of the entity it's spawned on, then you're still supported. The internals of the scene will be different in 0.18, but the visual result will be the same. The only option you need to enable is GltfConvertCoordinates::rotate_scene_entity.

  • If you want the Mesh assets in your glTF to be converted then you're supported by the GltfConvertCoordinates::rotate_meshes option. This can be combined with the rotate_scene_entity option if you want both.

  • If you enabled the 0.17 conversion and aren't sure what to enable in 0.18, try enabling both the rotate_scene_entity and rotate_meshes options. This will be closest to the 0.17 behavior.

  • If you tried the 0.17 conversion but found it caused issues with cameras or lights, then the 0.18 conversion should fix these issues.

  • If you relied on node conversion, you'll find that 0.18 no longer applies that conversion. This change was made to avoid bugs and give other users more options.

If you want to try out glTF coordinate conversion, the simplest method is to set GltfPlugin::convert_coordinates - this option can be set on app startup, and is applied to all glTFs when they're loaded. For an app that uses DefaultPlugins, the example below shows how to enable just scene conversion.

App::new()
    .add_plugins(DefaultPlugins.set(GltfPlugin {
        convert_coordinates: GltfConvertCoordinates { rotate_scene_entity: true, ..default() },
        ..default()
    }))
    .run();

If you want finer control, you can choose the option per-glTF with GltfLoaderSettings.

let handle = asset_server.load_with_settings(
    "fox.gltf#Scene0",
    |settings: &mut GltfLoaderSettings| {
        settings.convert_coordinates = Some(GltfConvertCoordinates { rotate_scene_entity: true, ..default() });
    },
);

TrackedRenderPass::set_index_buffer no longer takes buffer offset #

PRs:#20468

TrackedRenderPass::set_index_buffer no longer takes a separate buffer offset argument, which wasn't actually forwarded to wgpu. You have already needed to pass a BufferSlice that is sliced to the desired offset/size.

// 0.17
pass.set_index_buffer(indices.slice(1..), 1, IndexFormat::Uint32);

// 0.18
pass.set_index_buffer(indices.slice(1..), IndexFormat::Uint32);

BorderRect now has Vec2 fields #

PRs:#21581

The directional BorderRect fields (left, right, top, and bottom) have been replaced with min_inset and max_inset Vec2 fields.

Using min_inset and max_inset removes the need to interpret top or bottom relative to the coordinate system, so the same logic will work consistently in both UI and 2D.