Draft Page
-
0.13 to 0.14
- Fix Node2d typo
- Update to fixedbitset 0.5
- Move WASM panic handler from LogPlugin to PanicHandlerPlugin
- AnimationClip now uses UUIDs and NoOpTypeIdHash is now NoOpHash
- Implement the AnimationGraph to blend animations together
- Multiplying LinearRgba by f32 no longer ignores alpha channel
- Separate SubApp from App
- Make AppExit more specific about exit reason
- Deprecate dynamic plugins
- Move state initialization methods to bevy::state
- Remove the UpdateAssets and AssetEvents schedules
- Use async fn in traits rather than BoxedFuture
- Add Ignore variant to ProcessResult
- Removed Into<AssedId<T>> for Handle<T>
- Add AsyncSeek trait to Reader to be able to seek inside asset loaders
- Add error info to LoadState::Failed
- Make AssetMetaCheck a field of AssetPlugin
- Make LoadContext use the builder pattern
- Use RenderAssetUsages to configure gLTF meshes & materials during load
- Consolidate RenderMaterials and similar into RenderAssets, implement RenderAsset for destination type
- Fix leftover references to children when despawning audio entities
- Move WGSL math constants and color operations from bevy_pbr to bevy_render
- Overhaul Color
- Remove old color space utilities
- Use LinearRgba in ColorAttachment
- Remove close_on_esc
- Make sysinfo diagnostic plugin optional
- Improve tracing layer customization
- Immediately apply deferred system params in System::run
- Move Command and CommandQueue into bevy::ecs::world
- Make Component::Storage a constant
- Don't store Access<ArchetypeComponentId> within QueryState
- Remove WorldCell
- Return iterator instead of slice for QueryState::matched_tables and QueryState::matches_archtypes
- Remove system stepping from default features
- Optimize event updates and virtual time
- Make SystemParam::new_archetype and QueryState::new_archetype unsafe
- Better SystemId and Entity conversion
- Make NextState an enum
- Separate states from core ECS
- Constrain WorldQuery::get_state() to only accept Components
- Unify state transition names to exited and entered
- Make apply_state_transition private
- Replace FromWorld requirement with FromReflect on ReflectResource
- Make ReflectComponentFns and ReflectBundleFns methods work with EntityMut
- Require TypeRegistry in ReflectBundle::insert()
- Rename multi-threaded feature to multi_threaded
- Moves intern and label modules from bevy::utils to bevy::ecs
- Gizmo line joints
- Gizmo line styles
- Rename segments() methods to resolution()
- Make gizmos take primitives as a reference
- More gizmos builders
- Rename touchpad input to gesture
- Deprecate ReceivedCharacter
- Add WinitEvent::KeyboardFocusLost
- Move direction types out of bevy::math::primitives
- Rename Direction2d/3d to Dir2/3
- Make cardinal splines include endpoints
- Replace Point with VectorSpace
- UV-mapping change for Triangle2d
- Use Vec3A for 3D bounding volumes and raycasts
- Update glam to 0.27
- Common MeshBuilder trait
- Additional options to mesh primitives
- Add subdivisions to PlaneMeshBuilder
- Make Transform::rotate_axis and Transform::rotate_local_axis use Dir3
- Use Dir3 for local axis methods in GlobalTransform
- Fix Ord and PartialOrd differing for FloatOrd and optimize implementation
- Move FloatOrd into bevy_math
- reflect: treat proxy types correctly when serializing
- bevy_reflect: Recursive registration
- Clean up type registrations
- bevy_reflect: Rename UntypedReflectDeserializer to ReflectDeserializer
- Implement Reflect for Result<T, E> as enum
- Fix TypeRegistry use in dynamic scene
- rename Camera3dBundle's 'dither' field to 'deband_dither' to align with Camera2dBundle
- Add support for KHR_texture_transform
- Move AlphaMode into bevy_render
- Prefer UVec2 when working with texture dimensions
- Fix CameraProjectionPlugin not implementing Plugin in some cases
- Add random shader utils, fix cluster_debug_visualization
- Intern mesh vertex buffer layouts so that we don't have to compare them over and over.
- Batching: replace GpuArrayBufferIndex::index with a u32
- Solve some oklaba inconsistencies
- Add setting to enable/disable shadows to MaterialPlugin
- Implement maths and Animatable for Srgba
- Remove needless color specializaion for SpritePipeline
- Improve performance by binning together opaque items instead of sorting them.
- Implement GPU frustum culling
- remove DeterministicRenderingConfig
- Micro-optimize queue_material_meshes, primarily to remove bit manipulation.
- Disable RAY_QUERY and RAY_TRACING_ACCELERATION_STRUCTURE by default
- Add previous_view_uniforms.inverse_view
- Generate MeshUniforms on the GPU via compute shader where available.
- flipping texture coords methods has been added to the StandardMaterial
- Implement percentage-closer filtering (PCF) for point lights.
- Divide the single VisibleEntities list into separate lists for 2D meshes, 3D meshes, lights, and UI elements, for performance.
- Fix rendering of sprites, text, and meshlets after #12582.
- Expose desired_maximum_frame_latency through window creation
- Fix CameraProjection panic and improve CameraProjectionPlugin
- Implement filmic color grading.
- Add BufferVec, an higher-performance alternative to StorageBuffer, and make GpuArrayBuffer use it.
- Implement clearcoat per the Filament and the KHR_materials_clearcoat specifications.
- Clean up 2d render phases
- #12502 Remove limit on RenderLayers.
- Add emissive_exposure_weight to the StandardMaterial
- Make render phases render world resources instead of components.
- More idiomatic texture atlas builder
- Move clustering-related types and functions into their own module.
- Normalise matrix naming
- Rename "point light" to "clusterable object" in cluster contexts.
- Make Mesh::merge() take a reference of Mesh
- Store ClearColorConfig instead of LoadOp<Color> in CameraOutputMode
- wgpu 0.20
- Deprecate SpriteSheetBundle and AtlasImageBundle
- Decouple BackgroundColor from UiImage
- Fix UI elements randomly not appearing after #13277.
- Make default behavior for BackgroundColor and BorderColor more intuitive
- FIX12527: Changes to make serde optional for bevy_color
- configure_surface needs to be on the main thread on iOS
- Introduce a WindowWrapper to extend the lifetime of the window when using pipelined rendering
- Add an index argument to parallel iteration helpers in bevy_tasks
- Restore pre 0.13.1 Root Node Layout behavior
- Rename Rect inset() method to inflate()
- Updates default Text font size to 24px
- Disentangle bevy_utils/bevy_core's reexported dependencies
- Fix fit_canvas_to_parent
- Clean up WinitWindows::remove_window
- Ensure clean exit
- fix: upgrade to winit v0.30
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
#
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 associatedAnimationPlayer
. 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 theAnimationTarget
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
#
AnimationPlayer
s 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
#
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
#
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
#
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 match
ing 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 #
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:
- Bevy Assets - Scripting: Scripting and modding libraries for Bevy
- Bevy Assets - Development tools: Hot reloading and other development functionality
stabby
: Stable Rust ABI
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
#
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
#
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
#
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
#
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>
#
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
#
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
#
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
#
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
#
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
#
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
#
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 #
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
#
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
#
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
, andColor::a
are nowColor::set_alpha
,Color::with_alpha
, andColor::alpha
. These are part of the newAlpha
trait.- Additionally,
Color::is_fully_transparent
is now part of theAlpha
.
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.13 | 0.14 |
---|---|
CYAN | AQUA |
DARK_GRAY | Srgba::gray(0.25) |
DARK_GREEN | Srgba::rgb(0.0, 0.5, 0.0) |
GREEN | LIME |
LIME_GREEN | LIMEGREEN |
PINK | DEEP_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 #
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
#
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
#
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
#
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
#
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
#
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
#
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
#
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;
// ...
}
Before | After |
---|---|
TableStorage | StorageType::Table |
SparseStorage | StorageType::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
#
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 surroundingSystemState
s instead.QueryState::new_archetype
andQueryState::update_archetype_component_access
now require an&mut Access<ArchetypeComponentId>
as a parameter.
Remove WorldCell
#
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
#
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 #
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 #
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
#
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
#
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
#
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.13 | 0.14 |
---|---|
NextState(Some(S)) | NextState::Pending(S) |
NextState(None) | NextState::Unchanged |
Separate states from core 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
#
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
#
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
#
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
#
#[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
#
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()
#
ReflectBundle::insert
now requires an additional &TypeRegistry
parameter.
Rename multi-threaded
feature to multi_threaded
#
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
#
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 #
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 #
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()
#
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::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::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 #
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 tobevy::input::gestures
.TouchpadMagnify
has been renamed toPinchGesture
.TouchpadRotate
has been renamed toRotationGesture
.
Deprecate ReceivedCharacter
#
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
#
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
#
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
#
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 #
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
#
The Point
trait has been replaced by VectorSpace
. These traits are very similar, with a few minor changes:
VectorSpace
implementations must now provide theZERO
constant.VectorSpace
now requires theDiv<f32, Output = Self>
andNeg
trait bounds.VectorSpace
no longer requires theAdd<f32, Output = Self>
,Sum
, andPartialEq
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
#
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
#
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
#
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
#
When calling .build()
you need to import bevy_render::mesh::primitives::MeshBuilder
Additional options to mesh primitives #
Add subdivisions to PlaneMeshBuilder #
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
#
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
#
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
#
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 #
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 #
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 #
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 #
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
#
UntypedReflectDeserializer
has been renamed to ReflectDeserializer
. Usages will need to be updated accordingly.
- let reflect_deserializer = UntypedReflectDeserializer::new(®istry);
+ let reflect_deserializer = ReflectDeserializer::new(®istry);
Implement Reflect for Result<T, E> as enum #
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
andE
no longer require to beClone
and now require to beFromReflect
<Result<T, E> as Reflect>::reflect_*
now returns aReflectKind::Enum
, so any code that previously relied on it being aValue
kind will have to be adapted.Result<T, E>
now implementsEnum
Since the migration is highly dependent on the previous usage, no automatic upgrade path can be given.
Fix TypeRegistry use in dynamic scene #
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 withTypeRegistryArc::read()
, e.g.
let registry_arc: TypeRegistryArc = [...];
- let serializer = SceneSerializer(&scene, ®istry_arc);
+ let registry = registry_arc.read();
+ let serializer = SceneSerializer(&scene, ®istry);
- Rename
DynamicScene::serialize_ron()
toserialize()
.
rename Camera3dBundle's 'dither' field to 'deband_dither' to align with Camera2dBundle #
use the new deband_dither
field name with Camera3dBundle
, rather than the old field name, dither
Add support for KHR_texture_transform #
Rename affine_to_square
to affine3_to_square
Move AlphaMode into bevy_render #
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
#
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
#
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 #
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. #
Duplicate MeshVertexBufferLayout
s 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 #
GpuArrayBufferIndex::index
is now a u32 instead of a NonMaxU32
. Remove any calls to NonMaxU32::get
on the member.
Solve some oklaba inconsistencies #
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 #
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
#
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
#
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 u32
s you’ll have to account for that.
Improve performance by binning together opaque items instead of sorting them. #
PhaseItem
has been split into BinnedPhaseItem
and SortedPhaseItem
. If your code has custom PhaseItem
s, 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 #
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
#
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.
#
- The
primitive_topology
field onGpuMesh
is now an accessor method:GpuMesh::primitive_topology()
. - For performance reasons,
MeshPipelineKey
has been split intoBaseMeshPipelineKey
, which lives inbevy_render
, andMeshPipelineKey
, which lives inbevy_pbr
. These two should be combined with bitwise-or to produce the finalMeshPipelineKey
.
Disable RAY_QUERY and RAY_TRACING_ACCELERATION_STRUCTURE by default #
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 #
- Renamed
prepass_bindings::previous_view_proj
toprepass_bindings::previous_view_uniforms.view_proj
. - Renamed
PreviousViewProjectionUniformOffset
toPreviousViewUniformOffset
. - Renamed
PreviousViewProjection
toPreviousViewData
.
Generate MeshUniform
s on the GPU via compute shader where available.
#
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 #
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. #
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.
#
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. #
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
#
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
#
VisibilitySystems
’s UpdateOrthographicFrusta
, UpdatePerspectiveFrusta
, and UpdateProjectionFrusta
variants were removed, they were replaced with VisibilitySystems::UpdateFrusta
Implement filmic color grading. #
ColorGrading::gamma
andColorGrading::pre_saturation
are now set separately for theshadows
,midtones
, andhighlights
sections. You can migrate code with theColorGrading::all_sections
andColorGrading::all_sections_mut
functions, which access and/or update all sections at once.ColorGrading::post_saturation
andColorGrading::exposure
are now fields ofColorGrading::global
.
Add BufferVec, an higher-performance alternative to StorageBuffer, and make GpuArrayBuffer use it. #
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.
#
-
The lighting functions in the
pbr_lighting
WGSL module now have clearcoat parameters, ifSTANDARD_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 #
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. #
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 #
Make render phases render world resources instead of components. #
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 #
- 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();
Move clustering-related types and functions into their own module. #
- Clustering-related types and functions (e.g.
assign_lights_to_clusters
) have moved underbevy_pbr::cluster
, in preparation for the ability to cluster objects other than lights.
Normalise matrix naming #
Frustum
’sfrom_view_projection
,from_view_projection_custom_far
andfrom_view_projection_no_far
were renamed tofrom_clip_from_world
,from_clip_from_world_custom_far
andfrom_clip_from_world_no_far
.ComputedCameraValues::projection_matrix
was renamed toclip_from_view
.CameraProjection::get_projection_matrix
was renamed toget_clip_from_view
(this affects implementations onProjection
,PerspectiveProjection
andOrthographicProjection
).ViewRangefinder3d::from_view_matrix
was renamed tofrom_world_from_view
.PreviousViewData
’s members were renamed toview_from_world
andclip_from_world
.ExtractedView
’sprojection
,transform
andview_projection
were renamed toclip_from_view
,world_from_view
andclip_from_world
.ViewUniform
’sview_proj
,unjittered_view_proj
,inverse_view_proj
,view
,inverse_view
,projection
andinverse_projection
were renamed toclip_from_world
,unjittered_clip_from_world
,world_from_clip
,world_from_view
,view_from_world
,clip_from_view
andview_from_clip
.GpuDirectionalCascade::view_projection
was renamed toclip_from_world
.MeshTransforms
’transform
andprevious_transform
were renamed toworld_from_local
andprevious_world_from_local
.MeshUniform
’stransform
,previous_transform
,inverse_transpose_model_a
andinverse_transpose_model_b
were renamed toworld_from_local
,previous_world_from_local
,local_from_world_transpose_a
andlocal_from_world_transpose_b
(theMesh
type in WGSL mirrors this, howevertransform
andprevious_transform
were namedmodel
andprevious_model
).Mesh2dTransforms::transform
was renamed toworld_from_local
.Mesh2dUniform
’stransform
,inverse_transpose_model_a
andinverse_transpose_model_b
were renamed toworld_from_local
,local_from_world_transpose_a
andlocal_from_world_transpose_b
(theMesh2d
type in WGSL mirrors this).- In WGSL, in
bevy_pbr::mesh_functions
,get_model_matrix
andget_previous_model_matrix
were renamed toget_world_from_local
andget_previous_world_from_local
. - In WGSL,
bevy_sprite::mesh2d_functions::get_model_matrix
was renamed toget_world_from_local
.
Rename "point light" to "clusterable object" in cluster contexts. #
- In the PBR shaders,
point_lights
is now known asclusterable_objects
,PointLight
is now known asClusterableObject
, andcluster_light_index_lists
is now known asclusterable_object_index_lists
.
Make Mesh::merge()
take a reference of Mesh
#
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
#
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) |
Load | None |
ClearColorConfig
has an additional variant, Default
, which inherits the clear color from the ClearColor
resource.
wgpu
0.20
#
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
#
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
#
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 intoExtractBackgrounds
,ExtractImages
,ExtractBorders
, andExtractText
.bevy_ui::extract_uinodes
has been split intobevy_ui::extract_uinode_background_colors
andbevy_ui::extract_uinode_images
.bevy_ui::extract_text_uinodes
has been renamed toextract_uinode_text
.
Fix UI elements randomly not appearing after #13277. #
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
#
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 #
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 #
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
#
Windowing backends now need to store their window in the new WindowWrapper
.
Add an index argument to parallel iteration helpers in bevy_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 #
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()
#
Replace Rect::inset()
, IRect::inset()
and URect::inset()
calls with inflate()
.
Updates default Text font size to 24px #
- 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 #
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 #
Cancels the migration from https://github.com/bevyengine/bevy/pull/11057
Clean up WinitWindows::remove_window #
WinitWindows::get_window_entity
now returns None
after a window is closed, instead of a dead entity.
Ensure clean exit #
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 #
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 yetRunning
(previously calledStarted
): the loop is runningWillSuspend
: the loop is going to be suspendedSuspended
: the loop is suspendedWillResume
: 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.