Migration Guides

Draft Page

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

Migration Guide: 0.13 to 0.14

Fix Node2d typo #

Node2d::ConstrastAdaptiveSharpening from bevy::core_pipeline::core_2d::graph has been renamed to fix a typo. It was originally Constrast, but is now Contrast.

// 0.13
Node2D::ConstrastAdaptiveSharpening

// 0.14
Node2D::ContrastAdaptiveSharpening

Update to fixedbitset 0.5 #

Access::grow from bevy::ecs::query has been removed. Many operations now automatically grow the capacity.

// 0.13
let mut access = Access::new();
access.grow(1);
// Other operations...

// 0.14
let mut access = Access::new();
// Other operations...

Move WASM panic handler from LogPlugin to PanicHandlerPlugin #

LogPlugin used to silently override the panic handler on WASM targets. This functionality has now been split out into the new PanicHandlerPlugin, which was added to DefaultPlugins.

If you want nicer error messages on WASM but don't use DefaultPlugins, make sure to manually add PanicHandlerPlugin to the app.

App::new()
    .add_plugins((MinimalPlugins, PanicHandlerPlugin))
    .run()

AnimationClip now uses UUIDs and NoOpTypeIdHash is now NoOpHash #

Animation

AnimationClip now uses UUIDs instead of hierarchical paths based on the Name component to refer to bones. This has several consequences:

  • A new component, AnimationTarget, should be placed on each bone that you wish to animate, in order to specify its UUID and the associated AnimationPlayer. The glTF loader automatically creates these components as necessary, so most uses of glTF rigs shouldn’t need to change.
  • Moving a bone around the tree, or renaming it, no longer prevents an AnimationPlayer from affecting it.
  • Dynamically changing the AnimationPlayer component will likely require manual updating of the AnimationTarget components.

Entities with AnimationPlayer components may now possess descendants that also have AnimationPlayer components. They may not, however, animate the same bones.

Furthermore, NoOpTypeIdHash and NoOpTypeIdHasher have been renamed to NoOpHash and NoOpHasher.

Implement the AnimationGraph to blend animations together #

Animation

AnimationPlayers can no longer play animations by themselves: they need to be paired with a Handle<AnimationGraph>. Code that used AnimationPlayer to play animations will need to create an AnimationGraph asset first, add a node for the clip (or clips) you want to play, and then supply the index of that node to the AnimationPlayer’s play method.

// 0.13
fn setup(mut commands: Commands, mut animations: ResMut<Assets<AnimationClip>>) {
    let mut animation = AnimationClip::default();

    // ...

    let mut player = AnimationPlayer::default();
    player.play(animations.add(animation));

    commands.spawn((
        player,
        // ...
    ));
}

// 0.14
fn setup(
    mut commands: Commands,
    mut animations: ResMut<Assets<AnimationClip>>,
    // You now need access to the `AnimationGraph` asset.
    mut graphs: ResMut<Assets<AnimationGraph>>,
) {
    let mut animation = AnimationClip::default();

    // ...

    // Create a new `AnimationGraph` and add the animation handle to it.
    let (graph, animation_index) = AnimationGraph::from_clip(animations.add(animation));

    let mut player = AnimationPlayer::default();
    // Play the animation index, not the handle.
    player.play(animation_index);

    commands.spawn((
        player,
        // Add the new `AnimationGraph` to the assets, and spawn the entity with its handle.
        graphs.add(graph),
        // ...
    ));
}

Furthermore, the AnimationPlayer::play_with_transition() method has been removed and replaced with the AnimationTransitions component. If you were previously using AnimationPlayer::play_with_transition(), add all animations that you were playing to the AnimationGraph and create an AnimationTransitions component to manage the blending between them.

For more information behind this change, you may be interested in RFC 51.

Multiplying LinearRgba by f32 no longer ignores alpha channel #

Animation
Color
Math
Rendering

It was previously possible to multiply and divide a Color by an f32, which is now removed. You must now operate on a specific color space, such as LinearRgba. Furthermore, these operations used to skip the alpha channel, but that is no longer the case.

// 0.13
let color = Color::RgbaLinear {
    red: 1.0,
    green: 1.0,
    blue: 1.0,
    alpha: 1.0,
} * 0.5;

// Alpha is preserved, ignoring the multiplier.
assert_eq!(color.a(), 1.0);

// 0.14
let color = LinearRgba {
    red: 1.0,
    green: 1.0,
    blue: 1.0,
    alpha: 1.0,
} * 0.5;

// Alpha is included in multiplication.
assert_eq!(color.alpha, 0.5);

If you need the alpha channel to remain untouched, consider creating your own helper method:

fn legacy_div_f32(color: &mut LinearRgba, scale: f32) {
    color.red /= scale;
    color.green /= scale;
    color.blue /= scale;
}

let mut color = LinearRgba {
    red: 1.0,
    green: 1.0,
    blue: 1.0,
    alpha: 1.0,
};

legacy_div_f32(&mut color, 2.0);

If you are fine with the alpha changing, but need it to remain within the range of 0.0 to 1.0, consider clamping it:

let mut color = LinearRgba {
    red: 1.0,
    green: 1.0,
    blue: 1.0,
    alpha: 1.0,
} * 10.0;

// Force alpha to be within [0.0, 1.0].
color.alpha = color.alpha.clamp(0.0, 1.0);

Note that in some cases, such as rendering sprites, the alpha is automatically clamped so you do not need to do it manually.

Separate SubApp from App #

App

SubApp has been separated from App, so there are a few larger changes involved when interacting with these types.

Constructing a SubApp #

SubApp no longer contains an App, so you no longer are able to convert an App into a SubApp. Furthermore, the extraction function must now be set outside of the constructor.

// 0.13
#[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug)]
struct MySubApp;

let mut app = App::new();
let mut sub_app = App::empty();

sub_app.add_systems(Main, ...);
sub_app.insert_resource(...);

app.insert_sub_app(MySubApp, SubApp::new(sub_app, |main_world, sub_app| {
    // Extraction function.
}));

// 0.14
#[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug)]
struct MySubApp;

let mut app = App::new();
// Use `SubApp::new()` instead of `App::new()`.
let mut sub_app = SubApp::new();

// Instead of setting the extraction function when you create the `SubApp`, you must set it
// afterwards. If you do not set an extraction function, it will do nothing.
sub_app.set_extract(|main_world, sub_world| {
    // Extraction function.
});

// You can still add systems and resources like normal.
sub_app.add_systems(Main, ...);
sub_app.insert_resource(...);

app.insert_sub_app(MySubApp, sub_app);

App changes #

App is not Send anymore, but SubApp still is.

Due to the separation of App and SubApp, a few other methods have been changed.

First, App::world as a property is no longer directly accessible. Instead use the getters App::world and App::world_mut.

#[derive(Component)]
struct MyComponent;

// 0.13
let mut app = App::new();
println!("{:?}", app.world.id());
app.world.spawn(MyComponent);

// 0.14
let mut app = App::new();
println!("{:?}", app.world().id()); // Notice the added paranthesese.
app.world_mut().spawn(MyComponent);

Secondly, all getters for the sub app now return a SubApp instead of an App. This includes App::sub_app, App::sub_app_mut, App::get_sub_app, and App::get_sub_app_mut.

#[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug)]
struct MySubApp;

let mut app = App::new();
app.insert_sub_app(MySubApp, SubApp::new());

assert_eq!(app.sub_app(MySubApp).type_id(), TypeId::of::<SubApp>());

3rd-party traits on App #

If you implemented an extension trait on App, consider also implementing it on SubApp:

trait SpawnBundle {
    /// Spawns a new `Bundle` into the `World`.
    fn spawn_bundle<T: Bundle>(&mut self, bundle: T) -> &mut Self;
}

impl SpawnBundle for App {
    fn spawn_bundle<T: Bundle>(&mut self, bundle: T) -> &mut Self {
        self.world_mut().spawn(bundle);
        self
    }
}

/// `SubApp` has a very similar API to `App`, so the code will usually look the same.
impl SpawnBundle for SubApp {
    fn spawn_bundle<T: Bundle>(&mut self, bundle: T) -> &mut Self {
        self.world_mut().spawn(bundle);
        self
    }
}

Make AppExit more specific about exit reason #

App

The AppExit event is now an enum that represents whether the code exited successfully or not. If you construct it, you must now specify Success or Error:

// 0.13
fn send_exit(mut writer: EventWriter<AppExit>) {
    writer.send(AppExit);
}

// 0.14
fn send_exit(mut writer: EventWriter<AppExit>) {
    writer.send(AppExit::Success);
    // Or...
    writer.send(AppExit::Error(NonZeroU8::new(1).unwrap()));
}

If you subscribed to this event in a system, consider matching whether it was a success or an error:

// 0.13
fn handle_exit(mut reader: EventReader<AppExit>) {
    for app_exit in reader.read() {
        // Something interesting here...
    }
}

// 0.14
fn handle_exit(mut reader: EventReader<AppExit>) {
    for app_exit in reader.read() {
        match *app_exit {
            AppExit::Success => {
                // Something interesting here...
            },
            AppExit::Error(exit_code) => panic!("App exiting with an error! (Code: {exit_code})"),
        }
    }
}

Furthermore, App::run now returns AppExit instead of the unit type (). Since AppExit implements Termination, you can now return it from the main function.

// 0.13
fn main() {
    App::new().run()
}

// 0.14
fn main() -> AppExit {
    App::new().run()
}

// 0.14 (alternative)
fn main() {
    // If you want to ignore `AppExit`, you can add a semicolon instead. :)
    App::new().run();
}

Deprecate dynamic plugins #

App

Dynamic plugins are now deprecated. If possible, remove all usage them from your code:

// 0.13
// This would be compiled into a separate dynamic library.
#[derive(DynamicPlugin)]
pub struct MyPlugin;

impl Plugin for MyPlugin {
    // ...
}

// This would be compiled into the main binary.
App::new()
    .load_plugin("path/to/plugin")
    .run()

// 0.14
// This would now be compiled into the main binary as well.
pub struct MyPlugin;

impl Plugin for MyPlugin {
    // ...
}

App::new()
    .add_plugins(MyPlugin)
    .run()

If you are unable to do that, you may temporarily silence the deprecation warnings by annotating all usage with #[allow(deprecated)]. Please note that the current dynamic plugin system will be removed by the next major Bevy release, so you will have to migrate eventually. You may be interested in these safer, related links:

If you truly cannot go without dynamic plugins, you may copy the code from Bevy and add it to your project locally.

Move state initialization methods to bevy::state #

App
ECS

State has been moved to bevy::state. With it, App::init_state has been moved from a normal method to an extension trait. You may now need to import AppExtStates in order to use this method. (This trait is behind the bevy_app feature flag, which you may need to enable.)

// 0.13
App::new()
    .init_state::<MyState>()
    .run()

// 0.14
use bevy::state::app::AppExtStates as _;

App::new()
    .init_state::<MyState>()
    .run()

If you import the prelude (such as with use bevy::prelude::*), you do not need to do this.

Remove the UpdateAssets and AssetEvents schedules #

Assets

The UpdateAssets schedule has been removed. If you add systems to this schedule, move them to run on PreUpdate. (You may need to configure the ordering with system.before(...) and system.after(...).)

// 0.13
App::new()
    .add_plugins(DefaultPlugins)
    .add_systems(UpdateAssets, my_system)
    .run()

// 0.14
App::new()
    .add_plugins(DefaultPlugins)
    .add_systems(PreUpdate, my_system)
    .run()

Furthermore, AssetEvents has been changed from a ScheduleLabel to a SystemSet within the First schedule.

// 0.13
App::new()
    .add_plugins(DefaultPlugins)
    .add_systems(AssetEvents, my_system)
    .run()

// 0.14
App::new()
    .add_plugins(DefaultPlugins)
    .add_systems(First, my_system.in_set(AssetEvents))
    .run()

Use async fn in traits rather than BoxedFuture #

Assets

In Rust 1.75, async fn was stabilized for traits. Some traits have been switched from returning BoxedFuture to be an async fn, specifically:

  • AssetReader
  • AssetWriter
  • AssetLoader
  • AssetSaver
  • Process

Please update your trait implementations:

// 0.13
impl AssetLoader for MyAssetLoader {
    // ...

    fn load<'a>(
        &'a self,
        reader: &'a mut Reader,
        _settings: &'a (),
        _load_context: &'a mut LoadContext,
    ) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
        // Note that you had to pin the future.
        Box::pin(async move {
            let mut bytes = Vec::new();
            reader.read_to_end(&mut bytes).await?;
            Ok(bytes)
        })
    }
}

// 0.14
impl AssetLoader for MyAssetLoader {
    // ...

    async fn load<'a>(
        &'a self,
        reader: &'a mut Reader<'_>,
        _settings: &'a (),
        _load_context: &'a mut LoadContext<'_>,
    ) -> Result<Self::Asset, Self::Error> {
        // No more need to pin the future, just write it!
        let mut bytes = Vec::new();
        reader.read_to_end(&mut bytes).await?;
        Ok(bytes)
    }
}

Because these traits now use async, they are no longer object safe. If you need to receive or store &dyn Trait, use the &dyn ErasedTrait variant instead. For instance:

// 0.13
struct MyReader(Box<dyn AssetReader>);

// 0.14
struct MyReader(Box<dyn ErasedAssetReader>);

Add Ignore variant to ProcessResult #

Assets

The ProcessResult enum, used in asset loading, has a new Ignore variant. You may need to update your match statements.

Removed Into<AssedId<T>> for Handle<T> #

Assets

Converting from a Handle to an AssetId using Into was removed because it was a footgun that could potentially drop the asset if the Handle was a strong reference. If you need the AssetId, please use Handle::id() instead.

// 0.13
let id: AssetId<T> = handle.into();

// 0.14
let id = handle.id();

Add AsyncSeek trait to Reader to be able to seek inside asset loaders #

Assets

The asset loader's Reader type alias now requires the new AsyncSeek trait. Please implement AsyncSeek for any structures that must be a Reader, or use an alternative if seeking is not supported.

Add error info to LoadState::Failed #

Assets

Rust prides itself on its error handling, and Bevy has been steadily catching up. Previously, when checking if an asset was loaded using AssetServer::load_state (and variants), the only information returned on an error was the empty LoadState::Failed. Not very useful for debugging!

Now, a full AssetLoadError is included inside Failed to tell you exactly what went wrong. You may need to update your match and if let statements to handle this new value:

// 0.13
match asset_server.load_state(asset_id) {
    // ...
    LoadState::Failed => eprintln!("Could not load asset!"),
}

// 0.14
match asset_server.load_state(asset_id) {
    // ...
    LoadState::Failed(error) => eprintln!("Could not load asset! Error: {}", error),
}

Furthermore, the Copy, PartialOrd, and Ord implementations have been removed from LoadState. You can explicitly call .clone() instead of copying the enum, and you can manually re-implement Ord as a helper method if required.

Make AssetMetaCheck a field of AssetPlugin #

Assets

AssetMetaCheck is used to configure how the AssetPlugin reads .meta files. It was previously a resource, but now has been changed to a field in AssetPlugin. If you use DefaultPlugins, you can use .set to configure this field.

// 0.13
App::new()
    .add_plugins(DefaultPlugins)
    .insert_resource(AssetMetaCheck::Never)
    .run()

// 0.14
App::new()
    .add_plugins(DefaultPlugins.set(AssetPlugin {
        meta_check: AssetMetaCheck::Never,
        ..default()
    }))
    .run()

Make LoadContext use the builder pattern #

Assets

LoadContext, used by AssetLoader, has been updated so all of its load_* methods have been merged into a builder struct.

// 0.13
load_context.load_direct(path);
// 0.14
load_context.loader().direct().untyped().load(path);

// 0.13
load_context.load_direct_with_reader(reader, path);
// 0.14
load_context.loader().direct().with_reader(reader).untyped().load(path);

// 0.13
load_context.load_untyped(path);
// 0.14
load_context.loader().untyped().load(path);

// 0.13
load_context.load_with_settings(path, settings);
// 0.14
load_context.loader().with_settings(settings).load(path);

Use RenderAssetUsages to configure gLTF meshes & materials during load #

Assets
Rendering

It is now possible configure whether meshes and materials should be loaded in the main world, the render world, or both with GltfLoaderSettings. The load_meshes field has been changed from a bool to a RenderAssetUsages bitflag, and a new load_materials field as been added.

You may need to update any gLTF .meta files:

// 0.13
load_meshes: true

// 0.14
load_meshes: ("MAIN_WORLD | RENDER_WORLD")

If you use AssetServer::load_with_settings instead when loading gLTF files, you will also have to update:

// 0.13
asset_server.load_with_settings("model.gltf", |s: &mut GltfLoaderSettings| {
    s.load_meshes = true;
});

// 0.14
asset_server.load_with_settings("model.gltf", |s: &mut GltfLoaderSettings| {
    s.load_meshes = RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD;
});

Consolidate RenderMaterials and similar into RenderAssets, implement RenderAsset for destination type #

Assets
Rendering

RenderMaterials, RenderMaterials2d, and RenderUiMaterials have all been replaced with the RenderAssets resource. If you need access a PreparedMaterial<T> using an AssetId, use RenderAssets::get instead.

Furthermore, the RenderAsset trait should now be implemented for destination types rather than source types. If you need to access the source type, use the RenderAsset::SourceAsset associated type.

// 0.13
impl RenderAsset for Image {
    type PreparedAsset = GpuImage;

    // ...
}

// 0.14
impl RenderAsset for GpuImage {
    type SourceAsset = Image;

    // ...
}

Fix leftover references to children when despawning audio entities #

Audio

You can configure the behavior of spawned audio with the PlaybackMode enum. One of its variants, PlaybackMode::Despawn, would despawn the entity when the audio finished playing.

There was previously a bug where this would only despawn the entity and not its children. This has been fixed, so now despawn_recursive() is called when the audio finishes.

If you relied on this behavior, consider using PlaybackMode::Remove to just remove the audio components from the entity or AudioSink::empty() to check whether any audio is finished and manually despawn() it.

Move WGSL math constants and color operations from bevy_pbr to bevy_render #

Color

Mathematical constants and color conversion functions for shaders have been moved from bevy_pbr::utils to bevy_render::maths and bevy_render::color_operations. If you depended on these in your own shaders, please update your import statements:

// 0.13
#import bevy_pbr::utils::{PI, rgb_to_hsv}

// 0.14
#import bevy_render::{maths::PI, color_operations::rgb_to_hsv}

Overhaul Color #

Color
Gizmos
Rendering
Text
UI

Bevy's color support has received a major overhaul, and with it the new bevy::color module. Buckle up, many things have been changed!

Color space representation #

Bevy's main Color enum is used to represent color in many different color spaces (such as RGB, HSL, and more). Before, these color spaces were all represented inline as variants:

enum Color {
    Rgba {
        red: f32,
        green: f32,
        blue: f32,
        alpha: f32,
    },
    Hsla {
        hue: f32,
        saturation: f32,
        lightness: f32,
        alpha: f32,
    },
    // ...
}

This has been changed so now each color space has its own dedicated struct:

struct Srgba {
    red: f32,
    green: f32,
    blue: f32,
    alpha: f32,
}

struct Hsla {
    hue: f32,
    saturation: f32,
    lightness: f32,
    alpha: f32,
}

enum Color {
    Srgba(Srgba),
    Hsla(Hsla),
    // ...
}

This makes it easier to organize and manage different color spaces, and many more color spaces have been added too! To handle this change, you may need to update your match statements:

// 0.13
match color {
    Color::Rgba { red, green, blue, alpha } => {
        // Something cool here!
    },
    _ => {},
}

// 0.14
match color {
    Color::Srgba(Srgba { red, green, blue, alpha }) => {
        // Something cool here!
    },
    // If you explicitly match every possible color space, you may need to handle more variants.
    // Color::Xyza(Xyza { x, y, z, alpha }) => {
    //     // Something else even cooler here!
    // },
    _ => {}
}

Additionally, you must now use the From and Into implementations when converting between color spaces, as compared to the old helper methods such as as_rgba and as_hsla.

// 0.13
let color = Color::rgb(1.0, 0.0, 1.0).as_hsla();

// 0.14
let color: Hsla = Srgba::rgb(1.0, 0.0, 1.0).into();

Color methods #

Any mention of RGB has been renamed to sRGB. This includes the variant Color::Rgba turning into Color::Srgba as well as methods such as Color::rgb and Color::rgb_u8 turning into Color::srgb and Color::srgb_u8.

Methods to access specific channels of Color have been removed due to causing silent, relatively expensive conversions. This includes Color::r, Color::set_r, Color::with_r, and all of the equivalents for g, b h, s and l. Convert your Color into the desired color space, perform your operation there, and then convert it back.

// 0.13
let mut color = Color::rgb(0.0, 0.0, 0.0);
color.set_b(1.0);

// 0.14
let color = Color::srgb(0.0, 0.0, 0.0);
let srgba = Srgba {
    blue: 1.0,
    ..Srgba::from(color),
};
let color = Color::from(srgba);

Color::hex has been moved to Srgba::hex. Call .into() or construct a Color::Srgba variant manually to convert it.

Color::rgb_linear and Color::rgba_linear have been renamed Color::linear_rgb and Color::linear_rgba to fit the naming scheme of the LinearRgba struct.

Color::as_linear_rgba_f32 and Color::as_linear_rgba_u32 have been removed. Call LinearRgba::to_f32_array and LinearRgba::to_u32 instead, converting if necessary.

Several other color conversion methods to transform LCH or HSL colors into float arrays or Vec types have been removed. Please reimplement these externally or open a PR to re-add them if you found them particularly useful.

Vector field arithmetic operations on Color (add, subtract, multiply and divide by a f32) have been removed. Instead, convert your colors into LinearRgba space and perform your operations explicitly there. This is particularly relevant when working with emissive or HDR colors, whose color channel values are routinely outside of the ordinary 0 to 1 range.

Alpha #

Alpha, also known as transparency, used to be referred to by the letter a. It is now called by its full name within structs and methods.

  • Color::set_a, Color::with_a, and Color::a are now Color::set_alpha, Color::with_alpha, and Color::alpha. These are part of the new Alpha trait.
  • Additionally, Color::is_fully_transparent is now part of the Alpha.

CSS Constants #

The various CSS color constants are no longer stored directly on Color. Instead, they’re defined in the Srgba color space, and accessed via bevy::color::palettes. Call .into() on them to convert them into a Color for quick debugging use.

// 0.13
let color = Color::BLUE;

// 0.14
use bevy::color::palettes::css::BLUE;

let color = BLUE;

Please note that palettes::css is not necessarily 1:1 with the constants defined previously as some names and colors have been changed to conform with the CSS spec. If you need the same color as before, consult the table below or use the color values from the old constants.

0.130.14
CYANAQUA
DARK_GRAYSrgba::gray(0.25)
DARK_GREENSrgba::rgb(0.0, 0.5, 0.0)
GREENLIME
LIME_GREENLIMEGREEN
PINKDEEP_PINK

Switch to LinearRgba #

WireframeMaterial, ExtractedUiNode, ExtractedDirectionalLight, ExtractedPointLight, ExtractedSpotLight, and ExtractedSprite now store a LinearRgba rather than a polymorphic Color. Furthermore, Color no longer implements AsBindGroup. You should store a LinearRgba instead to avoid conversion costs.

Remove old color space utilities #

Color
Rendering

The SrgbColorSpace trait, HslRepresentation struct, and LchRepresentation struct have been removed in favor of the specific color space structs.

For SrgbColorSpace, use Srgba::gamma_function() and Srgba::gamma_function_inverse(). If you used the SrgbColorSpace implementation for u8, convert it to an f32 first:

// 14 is random, this could be any number.
let nonlinear: u8 = 14;

// Apply gamma function, converting `u8` to `f32`.
let linear: f32 = Srgba::gamma_function(nonlinear as f32 / 255.0);

// Convert back to a `u8`.
let linear: u8 = (linear * 255.0) as u8;

Note that this conversion can be costly, especially if called during the Update schedule. Consider just using f32 instead.

HslRepresentation and LchRepresentation can be replaced with the From implementations between Srgba, Hsla, and Lcha.

// 0.13
let srgb = HslRepresentation::hsl_to_nonlinear_srgb(330.0, 0.7, 0.8);
let lch = LchRepresentation::nonlinear_srgb_to_lch([0.94, 0.66, 0.8]);

// 0.14
let srgba: Srgba = Hsla::new(330.0, 0.7, 0.8, 1.0).into();
let lcha: Lcha = Srgba::new(0.94, 0.66, 0.8, 1.0).into();

Use LinearRgba in ColorAttachment #

Color
Rendering

ColorAttachment::new() now takes Option<LinearRgba> instead of Option<Color> for the clear_color. You can use the From<Color> implementation to convert your color.

let clear_color: Option<LinearRgba> = Some(color.into());

Remove close_on_esc #

Dev-Tools

The close_on_esc system was removed because it was too opiniated and lacked customization. If you used this system, you may copy its contents below:

pub fn close_on_esc(
    mut commands: Commands,
    focused_windows: Query<(Entity, &Window)>,
    input: Res<ButtonInput<KeyCode>>,
) {
    for (window, focus) in focused_windows.iter() {
        if !focus.focused {
            continue;
        }

        if input.just_pressed(KeyCode::Escape) {
            commands.entity(window).despawn();
        }
    }
}

You may be interested in using the built-in keybinds provided by the operating system instead, such as Alt+F4 and Command+Q.

Make sysinfo diagnostic plugin optional #

Diagnostics

bevy::diagnostic depends on the sysinfo to track CPU and memory usage using SystemInformationDiagnosticsPlugin, but compiling and polling system information can be very slow. sysinfo is now behind the sysinfo_plugin feature flag, which is enabled by default for bevy for not for bevy_diagnostic.

If you depend on bevy_diagnostic directly, toggle the flag in Cargo.toml:

[dependencies]
bevy_diagnostic = { version = "0.14", features = ["sysinfo_plugin"] }

If you set default-features = false for bevy, do the same in Cargo.toml:

[dependencies]
bevy = { version = "0.14", default-features = false, features = ["sysinfo_plugin"] }

Improve tracing layer customization #

Diagnostics

Bevy uses tracing to handle logging and spans through LogPlugin. This could be customized with the update_subscriber field, but it was highly restrictive. This has since been amended, replacing the update_subscriber field with the more flexible custom_layer, which returns a Layer.

// 0.13
fn update_subscriber(_app: &mut App, subscriber: BoxedSubscriber) -> BoxedSubscriber {
    Box::new(subscriber.with(CustomLayer))
}

App::new()
    .add_plugins(LogPlugin {
        update_subscriber: Some(update_subscriber),
        ..default()
    })
    .run();

// 0.14
use bevy::log::tracing_subscriber;

fn custom_layer(_app: &mut App) -> Option<BoxedLayer> {
    // You can provide a single layer:
    return Some(CustomLayer.boxed());

    // Or you can provide multiple layers, since `Vec<Layer>` also implements `Layer`:
    Some(Box::new(vec![
        tracing_subscriber::fmt::layer()
            .with_file(true)
            .boxed(),
        CustomLayer.boxed(),
    ]))
}

App::new()
    .add_plugins(LogPlugin {
        custom_layer,
        ..default()
    })
    .run();

The BoxedSubscriber type alias has also been removed, it was replaced by the BoxedLayer type alias.

Immediately apply deferred system params in System::run #

ECS

The default implementation of System::run will now always immediately run System::apply_deferred. If you were manually calling System::apply_deferred in this situation, you may remove it. Please note that System::run_unsafe still does not call apply_deferred because it cannot guarantee it will be safe.

// 0.13
system.run(world);

// Sometime later:
system.apply_deferred(world);

// 0.14
system.run(world);

// `apply_deferred` no longer needs to be called!

Move Command and CommandQueue into bevy::ecs::world #

ECS

Command and CommandQueue have been moved from bevy::ecs::system to bevy::ecs::world. If you import them directly, you will need to update your import statements.

// 0.13
use bevy::ecs::system::{Command, CommandQueue};

// 0.14
use bevy::ecs::world::{Command, CommandQueue};

Make Component::Storage a constant #

ECS

The Component::Storage associated type has been replaced with the associated constant STORAGE_TYPE, making the ComponentStorage trait unnecessary. If you were manually implementing Component instead of using the derive macro, update your definitions:

// 0.13
impl Component for MyComponent {
    type Storage = TableStorage;
}

// 0.14
impl Component for MyComponent {
    const STORAGE_TYPE: StorageType = StorageType::Table;

    // ...
}
BeforeAfter
TableStorageStorageType::Table
SparseStorageStorageType::SparseSet

Component is also now no longer object safe. If you were using dyn Component, please consider filing an issue describing your use-case.

Don't store Access<ArchetypeComponentId> within QueryState #

ECS

QueryState no longer stores an Access<ArchetypeComponentId>, you must now pass it as an argument to each method that uses it. To account for this change:

  • QueryState::archetype_component_access has been removed. You can work around this by accessing the surrounding SystemStates instead.
  • QueryState::new_archetype and QueryState::update_archetype_component_access now require an &mut Access<ArchetypeComponentId> as a parameter.

Remove WorldCell #

ECS

WorldCell has been removed due to its incomplete, less performant, and potentially confusing nature. If you were using it to fetch multiple distinct values, consider using a SystemState instead with the SystemState::get() method.

If SystemState does not fit your use-case and unsafe is tolerable, you can use UnsafeWorldCell. It is more performant and featureful, but lacks the runtime checks.

Return iterator instead of slice for QueryState::matched_tables and QueryState::matches_archtypes #

ECS

QueryState::matched_tables and QueryState::matched_archetypes now return iterators instead of slices. If possible, use the combinators available from the Iterator trait. In a worst-case scenario you may call Iterator::collect() into a Vec, which can then be converted into a slice.

Remove system stepping from default features #

ECS

The system stepping feature is now disabled by default. It generally should not be included in shipped games, and adds a small but measurable performance overhead. To enable it, add the bevy_debug_stepping feature to your Cargo.toml:

[dependencies]
bevy = { version = "0.14", features = ["bevy_debug_stepping"] }

Code using Stepping will still compile with the feature disabled, but will print an error message at runtime if the application calls Stepping::enable().

Optimize event updates and virtual time #

ECS

Events::update() has been optimized to be O(1) for the amount of events registered. In doing so, a few systems and run conditions have been changed.

Events are registered to a World using EventRegistry instead of the Events resource:

// 0.13
world.insert_resource(Events::<MyEvent>::default());

// 0.14
EventRegistry::register_event::<MyEvent>(&mut world);

A few systems and run conditions have been changed as well:

  • event_update_system no longer uses generics and now has different arguments.
  • signal_event_update_system now has different arguments.
  • reset_event_update_signal_system has been removed.
  • event_update_condition now has different arguments.

While not related to events, the virtual_time_system has been changed as well. It has been converted from a system to a regular function, and now takes &T and &mut T instead of Res<T> and ResMut<T>.

Make SystemParam::new_archetype and QueryState::new_archetype unsafe #

ECS

QueryState::new_archetype and SystemParam::new_archetype are now unsafe functions because they do not ensure that the provided Archetype is from the same World that the state was initialized from. You will need to wrap any usage inside of an unsafe block, and you may need to write additional assertions to verify correct usage.

Better SystemId and Entity conversion #

ECS

If you need to access the underlying Entity for a one-shot system's SystemId, use the new SystemId::entity() method.

// 0.13
let system_id = world.register_system(my_system);
let entity = Entity::from(system_id);

// 0.14
let system_id = world.register_system(my_system);
let entity = system_id.entity();

Make NextState an enum #

ECS

NextState has been converted from a unit struct to an enum. If you accessed the internal Option directly, whether through NextState::0 or matching, you will have to update your code to handle this change.

// 0.13
let state = next_state.0.unwrap();

// 0.14
let NextState::Pending(state) = next_state else { panic!("No pending next state!") };
0.130.14
NextState(Some(S))NextState::Pending(S)
NextState(None)NextState::Unchanged

Separate states from core ECS #

ECS

States were moved to a separate crate which is gated behind the bevy_state feature. Projects that use state but don't use Bevy's default-features will need to add this feature to their Cargo.toml.

Projects that use bevy_ecs directly and use states will need to add the bevy_state crate as a dependency.

Projects that use bevy_app directly and use states will need to add the bevy_state feature.

If you do not use DefaultPlugins, you will need to add the StatesPlugin manually to your app.

Users should update imports that referenced the old location.

// 0.13
use bevy::ecs::schedule::{NextState, OnEnter, OnExit, OnTransition, State, States};
use bevy::ecs::schedule::common_conditions::in_state;

// 0.14
use bevy::state::state::{NextState, OnEnter, OnExit, OnTransition, State, States}
use bevy::state::condition::in_state;

Constrain WorldQuery::get_state() to only accept Components #

ECS

A few methods of WorldQuery and QueryState were unsound because they were passed an &World. They are now restricted to just take an &Components. The affected methods are:

  • WorldQuery::get_state()
  • QueryState::transmute()
  • QueryState::transmute_filtered()
  • QueryState::join()
  • QueryState::join_filtered()

To access Components from a World, call World::components().

If you manually implemented WorldQuery, you need to update get_state() to only use the information provided by Components.

Unify state transition names to exited and entered #

ECS

StateTransitionEvent's before and after fields have been renamed to exited and entered for consistency. You will have to update your usage if you access these fields or construct StateTransitionEvent.

Make apply_state_transition private #

ECS

The apply_state_transition system is no longer public. The easiest way to migrate your systems that depended on it for ordering is to create a custom schedule.

// 0.13
App::new()
    .add_plugins(DefaultPlugins)
    .add_systems(StateTransition, my_system.after(apply_state_transition))
    .run()

// 0.14
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
struct AfterStateTransition;

let mut app = App::new();

app.add_plugins(DefaultPlugins)
    .add_systems(AfterStateTransition, my_system);

// Create a new schedule and add it to the app.
let after_state_transition = Schedule::new(AfterStateTransition);
app.add_schedule(after_state_transition);

// Modify the schedule order to make this run after `StateTransition`.
app.world_mut()
    .resource_mut::<MainScheduleOrder>()
    .insert_after(StateTransition, AfterStateTransition);

app.run()

Replace FromWorld requirement with FromReflect on ReflectResource #

ECS
Reflection

#[reflect(Resource)] now requires the FromReflect trait to be implemented for your resource. This is done by default if you use #[derive(Reflect)], but you structs that opt-out of this behavior will have to write their own implementation. FromReflect was added to replace the FromWorld requirement, though FromReflect is fallible. You may wish to add #[reflect(FromWorld)] to your resources to maintain an infallible variant.

Finally, if you use the ReflectResource struct you will need to pass a &TypeRegistry to its insert, apply_or_insert, and copy methods.

Make ReflectComponentFns and ReflectBundleFns methods work with EntityMut #

ECS
Reflection

ReflectComponentFns and ReflectBundleFns have been updated to work with EntityMut, as compared to the more restricting EntityWorldMut. You will have to update your usage of ReflectComponentFns::apply, ReflectComponentFns::reflect_mut, and ReflectBundleFns::apply.

If you just use ReflectComponent and ReflectBundle, you will not have change your code because EntityWorldMut implements Into<EntityMut>.

Require TypeRegistry in ReflectBundle::insert() #

ECS
Reflection

ReflectBundle::insert now requires an additional &TypeRegistry parameter.

Rename multi-threaded feature to multi_threaded #

ECS
Tasks

The multi-threaded feature has been renamed to multi_threaded for bevy, bevy_asset, bevy_ecs, bevy_render, bevy_tasks, and bevy_internal. Please update your Cargo.toml if you manually specify Bevy features.

Moves intern and label modules from bevy::utils to bevy::ecs #

ECS
Utils

The bevy::utils::label and bevy::utils::intern modules have been moved to bevy::ecs, as well as the bevy::utils::define_label macro as part of an active effort to shrink bevy::utils. You will have to update your import statements to use the new paths.

Gizmo line joints #

Gizmos

Line joins have been added for gizmos, allowing for smooth or sharp corners between lines. If you manually created your own GizmoConfig, you will have to specify the type of line joins with the line_joins field.

The Default implementation of GizmoLineJoint is None, but you may be interested in Miter for sharp joints or Round for smooth joints.

Gizmo line styles #

Gizmos

It is now possible to configure the line style (such as solid or dotted) of gizmos using GizmoConfig::line_style. If you manually create a GizmoConfig, you will have to specify this field.

Rename segments() methods to resolution() #

Gizmos

All gizmo methods named segments() have been rename to resolution() in order to be consistent with bevy::render.

Make gizmos take primitives as a reference #

Gizmos

Gizmos::primitive_2d() and Gizmos::primitive_3d() now take the primitive as a reference so that non-Copy primitives do not need to be cloned each time they are drawn.

// 0.13
fn draw(mut gizmos: Gizmos) {
    let polygon = Polygon {
        vertices: [
            // ...
        ],
    };

    // Since `Polygon` is not `Copy`, you would need to clone it if you use it more than once.
    gizmos.primitive_2d(polygon.clone(), Vec2::ZERO, 0.0, Color::WHITE);
    gizmos.primitive_2d(polygon, Vec2::ONE, 0.0, Color::BLACK);
}

// 0.14
fn draw(mut gizmos: Gizmos) {
    let polygon = Polygon {
        vertices: [
            // ...
        ],
    };

    // No need to clone the polygon anymore!
    gizmos.primitive_2d(&polygon, Vec2::ZERO, 0.0, Color::WHITE);
    gizmos.primitive_2d(&polygon, Vec2::ONE, 0.0, Color::BLACK);
}

More gizmos builders #

Gizmos

Gizmos::primitive_2d(CIRLCE), Gizmos::primitive_2d(ELLIPSE), Gizmos::primitive_2d(ANNULUS), and Gizmos::primitive_3d(SPHERE) now return their corresponding builders instead of the unit type (). Furthermore, SphereBuilder::circle_segments() has been renamed to resolution().

Rename touchpad input to gesture #

Input

In a recent winit update, touchpad events can now be triggered on mobile. To account for this, touchpad-related items have been renamed to gestures:

  • bevy::input::touchpad has been renamed to bevy::input::gestures.
  • TouchpadMagnify has been renamed to PinchGesture.
  • TouchpadRotate has been renamed to RotationGesture.

Deprecate ReceivedCharacter #

Input
Windowing

ReceivedCharacter is now deprecated due to winit reworking their keyboard system, switch to using KeyboardInput instead.

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

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

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

Add WinitEvent::KeyboardFocusLost #

Input
Windowing

WinitEvent has a new enum variant: WinitEvent::KeyboardFocusLost. This was added as part of a fix where key presses would stick when losing focus of the Bevy window, such as with Alt + Tab. Please update any match statements.

Move direction types out of bevy::math::primitives #

Math

The Direction2d, Direction3d, and InvalidDirectionError types have been moved from bevy::math::primitives to bevy::math.

// 0.13
use bevy::math::primitives::{Direction2d, Direction3d, InvalidDirectionError};

// 0.14
use bevy::math::{Direction2d, Direction3d, InvalidDirectionError};

Rename Direction2d/3d to Dir2/3 #

Math

The Direction2d and Direction3d types have been renamed to Dir2 and Dir3. They have been shortened to make them easier to type, and to make them consistent with glam's shorter naming scheme (e.g. Vec2, Mat4).

Make cardinal splines include endpoints #

Math

There was a bug in CubicCardinalSpline where the curve would only pass through the interior control points, not the points at the beginning and end. (For an in-depth analysis, see this issue.) This has been fixed so that the curve passes through all control points, but it may break behavior you were depending on.

If you rely on the old behavior of CubicCardinalSpline, you will have to truncate any parametrizations you used in order to access a curve identical to the one you had previously. This can be done by chopping off a unit-distance segment from each end of the parametrizing interval. For instance, if your code looks as follows:

fn interpolate(t: f32) -> Vec2 {
    let points = [
        vec2(-1.0, -20.0),
        vec2(3.0, 2.0),
        vec2(5.0, 3.0),
        vec2(9.0, 8.0),
    ];
    let my_curve = CubicCardinalSpline::new(0.3, points).to_curve();
    my_curve.position(t)
}

Then in order to obtain similar behavior, t will need to be shifted up by 1 (since the output of CubicCardinalSpline::to_curve has introduced a new segment in the interval [0,1]), displacing the old segment from [0,1] to [1,2]:

fn interpolate(t: f32) -> Vec2 {
    let points = [
        vec2(-1.0, -20.0),
        vec2(3.0, 2.0),
        vec2(5.0, 3.0),
        vec2(9.0, 8.0),
    ];
    let my_curve = CubicCardinalSpline::new(0.3, points).to_curve();
    // Add 1 here to restore original behavior.
    my_curve.position(t + 1)
}

(Note that this does not provide identical output for values of t outside of the interval [0,1].)

On the other hand, any user who was specifying additional endpoint tangents simply to get the curve to pass through the right points (i.e. not requiring exactly the same output) can simply omit the endpoints that were being supplied only for control purposes.

Replace Point with VectorSpace #

Math

The Point trait has been replaced by VectorSpace. These traits are very similar, with a few minor changes:

  • VectorSpace implementations must now provide the ZERO constant.
  • VectorSpace now requires the Div<f32, Output = Self> and Neg trait bounds.
  • VectorSpace no longer requires the Add<f32, Output = Self>, Sum, and PartialEq trait bounds.

For most cases you can replace all Point usage with VectorSpace, but you may have to make further changes if you depend on anything in the list above.

UV-mapping change for Triangle2d #

Math

The UV-mapping of Triangle2d has changed with this PR: the main difference is that the UVs are no longer dependent on the triangle’s absolute coordinates but instead follow translations of the triangle itself in its definition. If you depended on the old UV-coordinates for Triangle2d, then you will have to update affected areas to use the new ones which can be briefly described as follows:

  • The first coordinate is parallel to the line between the first two vertices of the triangle.
  • The second coordinate is orthogonal to this, pointing in the direction of the third point.

Generally speaking, this means that the first two points will have coordinates [_, 0.], while the third coordinate will be [_, 1.], with the exact values depending on the position of the third point relative to the first two. For acute triangles, the first two vertices always have UV-coordinates [0., 0.] and [1., 0.] respectively. For obtuse triangles, the third point will have coordinate [0., 1.] or [1., 1.], with the coordinate of one of the two other points shifting to maintain proportionality.

For example:

  • The default Triangle2d has UV-coordinates [0., 0.], [0., 1.], [0.5, 1.].
  • The triangle with vertices vec2(0., 0.), vec2(1., 0.), vec2(2., 1.) has UV-coordinates [0., 0.], [0.5, 0.], [1., 1.].
  • The triangle with vertices vec2(0., 0.), vec2(1., 0.), vec2(-2., 1.) has UV-coordinates [2./3., 0.], [1., 0.], [0., 1.].

Use Vec3A for 3D bounding volumes and raycasts #

Math

Aabb3d, BoundingSphere, and RayCast3d now use Vec3A instead of Vec3 internally. Vec3A is the SIMD-accelerated form of Vec3, so it should provide performance improvements without visible changes in behavior.

If you manually construct any of the affected structs, you will have to convert into a Vec3A.

// 0.13
let x = Vec3::new(5.0, -2.0);

let aabb = Aabb3d {
    min: Vec3::ZERO,
    max: x,
};

// 0.14
let x = Vec3::new(5.0, -2.0);

let aabb = Aabb3d {
    // Both variants are very similar, so you can usually replace `Vec3` with `Vec3A`.
    min: Vec3A::ZERO,
    // In cases where you cannot, use the `From` and `Into` traits.
    max: x.into(),
};

Update glam to 0.27 #

Math

glam has been updated from 0.25 to 0.27. Please view [the changelong] for both 0.26 and 0.27 to update your code.

The largest breaking change is that the fract() method for vector types now evaluates as self - self.trunc() instead of self - self.floor(). If you require the old behavior, use the fract_gl() method instead.

Common MeshBuilder trait #

Math
Rendering

When calling .build() you need to import bevy_render::mesh::primitives::MeshBuilder

Additional options to mesh primitives #

Math
Rendering

Add subdivisions to PlaneMeshBuilder #

Math
Rendering

If you were using Plane subdivisions, you now need to use Plane3d::default().mesh().subdivisions(10)

fixes https://github.com/bevyengine/bevy/issues/13258

Make Transform::rotate_axis and Transform::rotate_local_axis use Dir3 #

Math
Transform

All calls to Transform::rotate_axis and Transform::rotate_local_axis will need to be updated to use a Dir3 for the axis parameter rather than a Vec3. For a general input, this means calling Dir3::new and handling the Result, but if the previous vector is already known to be normalized, Dir3::new_unchecked can be called instead. Note that literals like Vec3::X also have corresponding Dir3 literals; e.g. Dir3::X, Dir3::NEG_Y and so on.

Use Dir3 for local axis methods in GlobalTransform #

Math
Transform

The GlobalTransform component’s directional axis methods (e.g., right(), left(), up(), down(), back(), forward()) have been updated from returning Vec3 to Dir3.

Fix Ord and PartialOrd differing for FloatOrd and optimize implementation #

Math
Utils

If you were depending on the PartialOrd behaviour of FloatOrd, it has changed from matching f32 to matching FloatOrd’s Ord ordering, never returning None.

Move FloatOrd into bevy_math #

Math
Utils

FloatOrd has been moved to bevy_math.

// 0.13
use bevy::utils::FloatOrd;
// 0.14
use bevy::math::FloatOrd;

reflect: treat proxy types correctly when serializing #

Reflection

If ReflectSerialize is registered on a type, but TypePath or FromReflect implementations are omitted (perhaps by #[reflect(type_path = false) or #[reflect(from_reflect = false)]), the traits must now be implemented.

bevy_reflect: Recursive registration #

Reflection

All types that derive Reflect will now automatically add GetTypeRegistration as a bound on all (unignored) fields. This means that all reflected fields will need to also implement GetTypeRegistration.

If all fields derive Reflect or are implemented in bevy_reflect, this should not cause any issues. However, manual implementations of Reflect that excluded a GetTypeRegistration impl for their type will need to add one.

#[derive(Reflect)]
struct Foo<T: FromReflect> {
  data: MyCustomType<T>
}

// OLD
impl<T: FromReflect> Reflect for MyCustomType<T> {/* ... */}

// NEW
impl<T: FromReflect + GetTypeRegistration> Reflect for MyCustomType<T> {/* ... */}
impl<T: FromReflect + GetTypeRegistration> GetTypeRegistration for MyCustomType<T> {/* ... */}

Clean up type registrations #

Reflection

External types are no longer registered into the type registry automatically unless they are used by other Bevy types (due to the new recursive registration). If you were depending on types from std, glam, or similar types being in the type registry you may need to manually register them.

App::new().register_type::<DMat3>();

bevy_reflect: Rename UntypedReflectDeserializer to ReflectDeserializer #

Reflection

UntypedReflectDeserializer has been renamed to ReflectDeserializer. Usages will need to be updated accordingly.

- let reflect_deserializer = UntypedReflectDeserializer::new(&registry);
+ let reflect_deserializer = ReflectDeserializer::new(&registry);

Implement Reflect for Result<T, E> as enum #

Reflection

Result<T, E> has had its Reflect implementation changed to align it with Option<T> and its intended semantics: A carrier of either an Ok or Err value, and the ability to access it. To achieve this it is no longer a ReflectKind::Value but rather a ReflectKind::Enum and as such carries these changes with it:

For Result<T, E>

  • Both T and E no longer require to be Clone and now require to be FromReflect
  • <Result<T, E> as Reflect>::reflect_* now returns a ReflectKind::Enum, so any code that previously relied on it being a Value kind will have to be adapted.
  • Result<T, E> now implements Enum

Since the migration is highly dependent on the previous usage, no automatic upgrade path can be given.

Fix TypeRegistry use in dynamic scene #

Reflection
Scenes
  • SceneSerializer and all related serializing helper types now take a &TypeRegistry instead of a &TypeRegistryArc. You can upgrade by getting the former from the latter with TypeRegistryArc::read(), e.g.
  let registry_arc: TypeRegistryArc = [...];
- let serializer = SceneSerializer(&scene, &registry_arc);
+ let registry = registry_arc.read();
+ let serializer = SceneSerializer(&scene, &registry);
  • Rename DynamicScene::serialize_ron() to serialize().

rename Camera3dBundle's 'dither' field to 'deband_dither' to align with Camera2dBundle #

Rendering

use the new deband_dither field name with Camera3dBundle, rather than the old field name, dither

Add support for KHR_texture_transform #

Rendering

Rename affine_to_square to affine3_to_square

Move AlphaMode into bevy_render #

Rendering

AlphaMode has been moved from bevy_pbr to bevy_render. If you import them directly, you will need to update your import statements.

// 0.13
use bevy::pbr::AlphaMode;

// 0.14
use bevy::render::alpha::AlphaMode;

Prefer UVec2 when working with texture dimensions #

Rendering

Change floating point types (Vec2, Rect) to their respective unsigned integer versions (UVec2, URect) when using GpuImage, TextureAtlasLayout, TextureAtlasBuilder, DynamicAtlasTextureBuilder or FontAtlas.

Fix CameraProjectionPlugin not implementing Plugin in some cases #

Rendering

CameraProjectionPlugin<T>’s trait bounds now require T to implement CameraProjection, Component, and GetTypeRegistration. This shouldn’t affect most existing code as CameraProjectionPlugin<T> never implemented Plugin unless those bounds were met.

Add random shader utils, fix cluster_debug_visualization #

Rendering

The bevy_pbr::utils::random1D shader function has been replaced by the similar bevy_pbr::utils::rand_f.

Intern mesh vertex buffer layouts so that we don't have to compare them over and over. #

Rendering

Duplicate MeshVertexBufferLayouts are now combined into a single object, MeshVertexBufferLayoutRef, which contains an atomically-reference-counted pointer to the layout. Code that was using MeshVertexBufferLayout may need to be updated to use MeshVertexBufferLayoutRef instead.

Batching: replace GpuArrayBufferIndex::index with a u32 #

Rendering

GpuArrayBufferIndex::index is now a u32 instead of a NonMaxU32. Remove any calls to NonMaxU32::get on the member.

Solve some oklaba inconsistencies #

Rendering

If you were creating a Oklaba instance directly, instead of using L, you should use lightness

// 0.13
let oklaba = Oklaba { l: 1., ..Default::default() };

// 0.14
let oklaba = Oklaba { lightness: 1., ..Default::default() };

if you were using the function Oklaba::lch, now the method is named Oklaba::lab

Add setting to enable/disable shadows to MaterialPlugin #

Rendering

MaterialPlugin now has a shadows_enabled setting, if you didn’t spawn the plugin using ::default() or ..default(), you’ll need to set it. shadows_enabled: true is the same behavior as the previous version, and also the default value.

Implement maths and Animatable for Srgba #

Rendering

The previously existing implementation of mul/div for Srgba did not modify alpha but these operations do modify alpha now. Users need to be aware of this change.

Remove needless color specializaion for SpritePipeline #

Rendering

The raw values for the HDR, TONEMAP_IN_SHADER and DEBAND_DITHER flags have changed, so if you were constructing the pipeline key from raw u32s you’ll have to account for that.

Improve performance by binning together opaque items instead of sorting them. #

Rendering

PhaseItem has been split into BinnedPhaseItem and SortedPhaseItem. If your code has custom PhaseItems, you will need to migrate them to one of these two types. SortedPhaseItem requires the fewest code changes, but you may want to pick BinnedPhaseItem if your phase doesn’t require sorting, as that enables higher performance.

Implement GPU frustum culling #

Rendering

For phase items, the dynamic_offset: Option<NonMaxU32> field is now extra_index: PhaseItemExtraIndex, which wraps a u32. Instead of None, use PhaseItemExtraIndex::NONE.

This change affects AlphaMask3d, AlphaMask3dDeferred, AlphaMask3dPrepass, Opaque2d, Opaque3dDeferred, Opaque3dPrepass, Shadow, Transmissive3d, Transparent2d, Transparent3d, and TransparentUi.

remove DeterministicRenderingConfig #

Rendering

Removed DeterministicRenderingConfig. There shouldn’t be any z fighting anymore in the rendering even without setting stable_sort_z_fighting

Micro-optimize queue_material_meshes, primarily to remove bit manipulation. #

Rendering
  • The primitive_topology field on GpuMesh is now an accessor method: GpuMesh::primitive_topology().
  • For performance reasons, MeshPipelineKey has been split into BaseMeshPipelineKey, which lives in bevy_render, and MeshPipelineKey, which lives in bevy_pbr. These two should be combined with bitwise-or to produce the final MeshPipelineKey.

Disable RAY_QUERY and RAY_TRACING_ACCELERATION_STRUCTURE by default #

Rendering

If you need wgpu::Features::RAY_QUERY or wgpu::Features::RAY_TRACING_ACCELERATION_STRUCTURE, enable them explicitly using WgpuSettings::features

Add previous_view_uniforms.inverse_view #

Rendering
  • Renamed prepass_bindings::previous_view_proj to prepass_bindings::previous_view_uniforms.view_proj.
  • Renamed PreviousViewProjectionUniformOffset to PreviousViewUniformOffset.
  • Renamed PreviousViewProjection to PreviousViewData.

Generate MeshUniforms on the GPU via compute shader where available. #

Rendering

Custom render phases now need multiple systems beyond just batch_and_prepare_render_phase. Code that was previously creating custom render phases should now add a BinnedRenderPhasePlugin or SortedRenderPhasePlugin as appropriate instead of directly adding batch_and_prepare_render_phase.

flipping texture coords methods has been added to the StandardMaterial #

Rendering

Instead of using Quad::flip field, call flipped(true, false) method on the StandardMaterial instance when adding the mesh.

Implement percentage-closer filtering (PCF) for point lights. #

Rendering

ShadowFilteringMethod::Castano13 and ShadowFilteringMethod::Jimenez14 have been renamed to ShadowFilteringMethod::Gaussian and ShadowFilteringMethod::Temporal respectively.

Divide the single VisibleEntities list into separate lists for 2D meshes, 3D meshes, lights, and UI elements, for performance. #

Rendering

check_visibility and VisibleEntities now store the four types of renderable entities–2D meshes, 3D meshes, lights, and UI elements–separately. If your custom rendering code examines VisibleEntities, it will now need to specify which type of entity it’s interested in using the WithMesh2d, WithMesh, WithLight, and WithNode types respectively. If your app introduces a new type of renderable entity, you’ll need to add an instance of the check_visibility system with the appropriate query filter to the main world schedule to accommodate your new component or components. For example:

app
    .add_systems(
        PostUpdate,
        check_visibility::<With<MyCustomRenderable>>
            .in_set(VisibilitySystems::CheckVisibility)
    );

Fix rendering of sprites, text, and meshlets after #12582. #

Rendering

Text now requires a SpriteSource marker component in order to appear. This component has been added to Text2dBundle.

Expose desired_maximum_frame_latency through window creation #

Rendering

The desired_maximum_frame_latency field must be added to instances of Window and ExtractedWindow where all fields are explicitly specified.

Fix CameraProjection panic and improve CameraProjectionPlugin #

Rendering

VisibilitySystems’s UpdateOrthographicFrusta, UpdatePerspectiveFrusta, and UpdateProjectionFrusta variants were removed, they were replaced with VisibilitySystems::UpdateFrusta

Implement filmic color grading. #

Rendering
  • ColorGrading::gamma and ColorGrading::pre_saturation are now set separately for the shadows, midtones, and highlights sections. You can migrate code with the ColorGrading::all_sections and ColorGrading::all_sections_mut functions, which access and/or update all sections at once.
  • ColorGrading::post_saturation and ColorGrading::exposure are now fields of ColorGrading::global.

Add BufferVec, an higher-performance alternative to StorageBuffer, and make GpuArrayBuffer use it. #

Rendering

BufferVec has been renamed to RawBufferVec and a new similar type has taken the BufferVec name.

Implement clearcoat per the Filament and the KHR_materials_clearcoat specifications. #

Rendering
  • The lighting functions in the pbr_lighting WGSL module now have clearcoat parameters, if STANDARD_MATERIAL_CLEARCOAT is defined.

  • The R reflection vector parameter has been removed from some lighting functions, as it was unused.

Clean up 2d render phases #

Rendering

If you were using Node2d::MainPass to order your own custom render node. You now need to order it relative to Node2d::StartMainPass or Node2d::EndMainPass.

#12502 Remove limit on RenderLayers. #

Rendering

RenderLayers::all() no longer exists. Entities expecting to be visible on all layers, e.g. lights, should compute the active layers that are in use.

Add emissive_exposure_weight to the StandardMaterial #

Rendering

Make render phases render world resources instead of components. #

Rendering

The BinnedRenderPhase and SortedRenderPhase render world components have been replaced with ViewBinnedRenderPhases and ViewSortedRenderPhases resources. Instead of querying for the components, look the camera entity up in the ViewBinnedRenderPhases/ViewSortedRenderPhases tables.

More idiomatic texture atlas builder #

Rendering
- let mut texture_atlas_builder = TextureAtlasBuilder::default().padding(UVec2::default()).format(..);
+ let mut texture_atlas_builder = TextureAtlasBuilder::default();
+ texture_atlas_builder.padding(UVec2::default()).format(..);

- let (texture_atlas_layout, texture) = texture_atlas_builder.finish().unwrap();
+ let (texture_atlas_layout, texture) = texture_atlas_builder.build().unwrap();
Rendering
  • Clustering-related types and functions (e.g. assign_lights_to_clusters) have moved under bevy_pbr::cluster, in preparation for the ability to cluster objects other than lights.

Normalise matrix naming #

Rendering
  • Frustum’s from_view_projection, from_view_projection_custom_far and from_view_projection_no_far were renamed to from_clip_from_world, from_clip_from_world_custom_far and from_clip_from_world_no_far.
  • ComputedCameraValues::projection_matrix was renamed to clip_from_view.
  • CameraProjection::get_projection_matrix was renamed to get_clip_from_view (this affects implementations on Projection, PerspectiveProjection and OrthographicProjection).
  • ViewRangefinder3d::from_view_matrix was renamed to from_world_from_view.
  • PreviousViewData’s members were renamed to view_from_world and clip_from_world.
  • ExtractedView’s projection, transform and view_projection were renamed to clip_from_view, world_from_view and clip_from_world.
  • ViewUniform’s view_proj, unjittered_view_proj, inverse_view_proj, view, inverse_view, projection and inverse_projection were renamed to clip_from_world, unjittered_clip_from_world, world_from_clip, world_from_view, view_from_world, clip_from_view and view_from_clip.
  • GpuDirectionalCascade::view_projection was renamed to clip_from_world.
  • MeshTransformstransform and previous_transform were renamed to world_from_local and previous_world_from_local.
  • MeshUniform’s transform, previous_transform, inverse_transpose_model_a and inverse_transpose_model_b were renamed to world_from_local, previous_world_from_local, local_from_world_transpose_a and local_from_world_transpose_b (the Mesh type in WGSL mirrors this, however transform and previous_transform were named model and previous_model).
  • Mesh2dTransforms::transform was renamed to world_from_local.
  • Mesh2dUniform’s transform, inverse_transpose_model_a and inverse_transpose_model_b were renamed to world_from_local, local_from_world_transpose_a and local_from_world_transpose_b (the Mesh2d type in WGSL mirrors this).
  • In WGSL, in bevy_pbr::mesh_functions, get_model_matrix and get_previous_model_matrix were renamed to get_world_from_local and get_previous_world_from_local.
  • In WGSL, bevy_sprite::mesh2d_functions::get_model_matrix was renamed to get_world_from_local.

Rename "point light" to "clusterable object" in cluster contexts. #

Rendering
  • In the PBR shaders, point_lights is now known as clusterable_objects, PointLight is now known as ClusterableObject, and cluster_light_index_lists is now known as clusterable_object_index_lists.

Make Mesh::merge() take a reference of Mesh #

Rendering

Mesh::merge() now takes &Mesh instead of Mesh. Because of this, you can now share the same Mesh across multiple merge() calls without cloning it.

Store ClearColorConfig instead of LoadOp<Color> in CameraOutputMode #

Rendering

CameraOutputMode::Write now stores a ClearColorConfig instead of a LoadOp<Color>. Use the following table to convert between the two enums:

LoadOp<Color>ClearColorConfig
Clear(color)Custom(color)
LoadNone

ClearColorConfig has an additional variant, Default, which inherits the clear color from the ClearColor resource.

wgpu 0.20 #

Rendering

Bevy now depends on wgpu 0.20, naga 0.20, and naga_oil 0.14. If you manually specify any of these crates in your Cargo.toml, make sure to update their versions to prevent them from being duplicated.

Furthermore, timestamps inside of encoders are now disallowed on WebGPU (though they still work on native). Use the TIMESTAMP_QUERY_INSIDE_ENCODERS feature to check for support.

Deprecate SpriteSheetBundle and AtlasImageBundle #

Rendering
UI

SpriteSheetBundle has been deprecated. Insert the TextureAtlas component alongside a SpriteBundle instead.

// 0.13
commands.spawn(SpriteSheetBundle {
    texture,
    atlas: TextureAtlas {
        layout,
        ..default()
    },
    ..default()
});
// 0.14
commands.spawn((
    SpriteBundle {
        texture,
        ..default()
    },
    TextureAtlas {
        layout,
        ..default()
    },
));

AtlasImageBundle has been deprecated. Insert the TextureAtlas component alongside an ImageBundle instead.

// 0.13
commands.spawn(AtlasImageBundle {
    image,
    atlas: TextureAtlas {
        layout,
        ..default()
    },
    ..default()
});
// 0.14
commands.spawn((
    ImageBundle {
        image,
        ..default()
    },
    TextureAtlas {
        layout,
        ..default()
    },
));

Decouple BackgroundColor from UiImage #

Rendering
UI

The BackgroundColor component now renders a solid-color background behind UiImage instead of tinting its color. Use the color field of UiImage for tinting.

// 0.13
ButtonBundle {
    background_color: my_color.into(),
    ..default()
}
// 0.14
ButtonBundle {
    image: UiImage::default().with_color(my_color),
    ..default()
}
// 0.13
fn button_system(
    mut query: Query<(&Interaction, &mut BackgroundColor), (Changed<Interaction>, With<Button>)>,
) {
    for (interaction, mut color) in &mut query {
        match *interaction {
            Interaction::Pressed => {
                *color = my_color.into();
            }
            // ...
        }
    }
}
// 0.14
fn button_system(
    mut query: Query<(&Interaction, &mut UiImage), (Changed<Interaction>, With<Button>)>,
) {
    for (interaction, mut image) in &mut query {
        match *interaction {
            Interaction::Pressed => {
                image.color = my_color;
            }
            // ...
        }
    }
}

Some UI systems have been split or renamed.

  • bevy_ui::RenderUiSystem::ExtractNode has been split into ExtractBackgrounds, ExtractImages, ExtractBorders, and ExtractText.
  • bevy_ui::extract_uinodes has been split into bevy_ui::extract_uinode_background_colors and bevy_ui::extract_uinode_images.
  • bevy_ui::extract_text_uinodes has been renamed to extract_uinode_text.

Fix UI elements randomly not appearing after #13277. #

Rendering
UI

The bevy_ui::render::extract_default_ui_camera_view system is no longer parameterized over the specific type of camera and is hard-wired to either Camera2d or Camera3d components.

Make default behavior for BackgroundColor and BorderColor more intuitive #

Rendering
UI

BackgroundColor no longer tints the color of images in ImageBundle or ButtonBundle. Set UiImage::color to tint images instead. Furthermore, the new default texture for UiImage is now a transparent white square. Use UiImage::solid_color to quickly draw debug images. Finally, the default value for BackgroundColor and BorderColor is now transparent. Set the color to white manually to return to previous behavior.

FIX12527: Changes to make serde optional for bevy_color #

Rendering
Utils

If user wants color data structures to be serializable, then application needs to be build with flag ‘serialize’

configure_surface needs to be on the main thread on iOS #

Rendering
Windowing

The need_new_surfaces system has been renamed need_surface_configuration.

Introduce a WindowWrapper to extend the lifetime of the window when using pipelined rendering #

Rendering
Windowing

Windowing backends now need to store their window in the new WindowWrapper.

Add an index argument to parallel iteration helpers in bevy_tasks #

Tasks

Functions passed as arguments to par_chunk_map, par_splat_map, par_chunk_map_mut, and par_splat_map_mut now take an additional index argument.

// 0.13
items.par_chunk_map(&task_pool, 100, |chunk| { /* ... */ });

// 0.14
items.par_chunk_map(&task_pool, 100, |_index, chunk| { /* ... */ });

Restore pre 0.13.1 Root Node Layout behavior #

UI

If you were affected by the 0.13.1 regression and added position_type: Absolute to all your root nodes you might be able to reclaim some LOC by removing them now that the 0.13 behavior is restored.

Rename Rect inset() method to inflate() #

UI

Replace Rect::inset(), IRect::inset() and URect::inset() calls with inflate().

Updates default Text font size to 24px #

UI
  • The default font size has been increased to 24px from 12px. Make sure you set the font to the appropriate values in places you were using Default text style.

Disentangle bevy_utils/bevy_core's reexported dependencies #

Utils

bevy_utils no longer re-exports petgraph, uuid, nonmax, smallvec, or thiserror.

bevy_core no longer re-exports bytemuck’s bytes_of, cast_slice, Pod, and Zeroable.

You can add these as dependencies in your own Cargo.toml instead.

Fix fit_canvas_to_parent #

Windowing

Cancels the migration from https://github.com/bevyengine/bevy/pull/11057

Clean up WinitWindows::remove_window #

Windowing

WinitWindows::get_window_entity now returns None after a window is closed, instead of a dead entity.

Ensure clean exit #

Windowing

Ensure custom exit logic does not rely on the app exiting the same frame as a window is closed.

fix: upgrade to winit v0.30 #

Windowing

The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new custom_user_event shows this behavior.

The internal UpdateState has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event.

The UpdateMode now accepts only two values: Continuous and Reactive, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous UpdateMode::Reactive is now equivalent to UpdateMode::reactive(), while UpdateMode::ReactiveLowPower to UpdateMode::reactive_low_power().

The ApplicationLifecycle has been renamed as AppLifecycle, and now contains the possible values of the application state inside the event loop:

  • Idle: the loop has not started yet
  • Running (previously called Started): the loop is running
  • WillSuspend: the loop is going to be suspended
  • Suspended: the loop is suspended
  • WillResume: the loop is going to be resumed

Note: the Resumed state has been removed since the resumed app is just running.

Finally, now that winit enables this, it extends the WinitPlugin to support custom events.