- 0.16 to 0.17
- Handle::Weak has been replaced by Handle::Uuid.
- Deprecate iter_entities and iter_entities_mut.
- RelativeCursorPosition is now object-centered
- Text2d moved to bevy_sprite
- SyncCell and SyncUnsafeCell moved to bevy_platform
- Renamed Condition to SystemCondition
- Observers and one-shot systems are now marked as Internal
- SceneSpawner methods have been renamed and replaced
- Location is no longer a Component
- Remove scale_value
- Assets::insert and Assets::get_or_insert_with now return Result
- Enable Wayland by default
- Generic Option Parameter
- Stop exposing mp3 support through minimp3
- Schedule API Cleanup
- bevy_render reorganization
- Rename Pointer<Pressed> and Pointer<Released> to Pointer<Press> and Pointer<Release>
- YAxisOrientation has been removed
- Renamed Timer::paused to Timer::is_paused and Timer::finished to Timer::is_finished
- glTF animation loading is now optional
- Fixed UI draw order and stack_z_offsets changes
- RemovedComponents methods renamed to match Event to Message rename
- Window is now split into multiple components
- Removed cosmic_text re-exports
- Rename send_event and similar methods to write_message
- Move UI Debug Options from bevy_ui to bevy_ui_render
- CloneBehavior is no longer PartialEq or Eq
- Interned labels and DynEq
- Split Hdr from Camera
- The render target info from ComputedUiTargetCamera has been removed.
- Reflection-based maps are now unordered
- Rework MergeMeshError
- Use glTF material names for spawned primitive entities
- ViewRangefinder3d::from_world_from_view now takes Affine3A instead of Mat4
- Original target of Pointer picking events is now stored on observers
- Remove the Add/Sub impls on Volume
- Renamed ComputedNodeTarget and update_ui_context_system
- ChromaticAberration LUT is now Option
- Extract PointerInputPlugin members into PointerInputSettings
- DragEnter now includes the dragged entity
- Separate Border Colors
- ScrollPosition now uses logical pixel units and is no longer overwritten during layout updates
- Combine now takes an extra parameter
- Fix From<Rot2> implementation for Mat2
- Extract PickingPlugin members into PickingSettings
- Consistent *Systems naming convention for system sets
- System::run returns Result
- LightVisibilityClass renamed to ClusterVisibilityClass
- Renamed state scoped entities and events
- Renamed JustifyText to Justify
- Remove default implementation of extend_from_iter from RelationshipSourceCollection
- AnimationGraph no longer supports raw AssetIds
- Unify ObserverState and Observer components
- take_extract now returns dyn FnMut instead of dyn Fn
- OpenGL ES wgpu backend is no longer supported by default
- labeled_asset_scope can now return errors
- Make ScrollPosition newtype Vec2
- Move cursor-related types from bevy_winit to bevy_window
- ExtractedUiNode's stack_index has been renamed to z_order and is now an f32.
- Changes to Bevy's system parallelism strategy
- wgpu 25
- Anchor is now a required component on Sprite
- Many render resources now initialized in RenderStartup
- GatedReader and GatedOpener are now private.
- Updated glam, rand and getrandom versions with new failures when building for web
- OverflowClipBox's default is now PaddingBox
- Event trait split / Rename
- Deprecated Simple Executor
- ReflectAsset now uses UntypedAssetId instead of UntypedHandle
- Replaced TextFont constructor methods with From impls
- VectorSpace implementations
- Improve error when using run_system command with a SystemId of wrong type
- Extract UI text colors per glyph
- TextureFormat::pixel_size now returns a Result
- Entities API changes
- Relationship method set_risky
- Unified system state flag
- DynamicBundle
- Exclusive systems may not be used as observers
- EntityClonerBuilder Split
- Opt-Out variant
- Opt-In variant
- Common methods
- Unified id filtering
- Other affected APIs
- Mutation and Base Descriptors
- Composing Specializers
- Misc Changes
- Full Migration Example
- First-party NonSend Resources Replaced
- NonSend Systems
- ComponentsRegistrator no longer implements DerefMut
- Query items can borrow from query state
- Non-generic Access
- Component lifecycle reorganization
- Polylines and Polygons are no longer const-generic
- Changes to the default error handler mechanism
- Removed Deprecated Batch Spawning Methods
- Stop storing access in systems
- New zstd backend
- Migration Guide
- Remove ArchetypeComponentId
- RenderTarget error handling
- Renamed BRP methods
- TAA is no longer experimental
- TextShadow has been moved to bevy::ui::widget::text
- Remove Bundle::register_required_components
- Observer / Event API Changes
- Manual Entity Creation and Representation
- Index
- Generation
- Removed Interfaces
- Functionality
- Length Representation
- Other kinds of entity rows
- RenderTargetInfo's default scale_factor has been changed to 1.
- RenderGraphApp renamed to RenderGraphExt
- Window Resolution Constructors
- Specialized UI transform
- CheckChangeTicks parameter in System::check_change_tick
- Transform and GlobalTransform::compute_matrix rename
- Compressed image saver feature
- Change filters container of LogDiagnosticsState to HashSet
- FULLSCREEN_SHADER_HANDLE replaced with FullscreenShader
- Smooth normals implementation changed
- SpawnableList
- Changes to bevy_tasks feature flags
Migration Guide: 0.16 to 0.17
The most important changes to be aware of this release are:
Handle::Weak
has been replaced by Handle::Uuid
. #
Handle::Weak
had some weird behavior. It allowed for a sprite to be given a handle that is dropped while the sprite is still using it. This also resulted in more complexity in the asset system. The primary remaining use for Handle::Weak
is to store asset UUIDs initialized through the weak_handle!
macro. To address this, Handle::Weak
has been replaced by Handle::Uuid
!
Users using the weak_handle!
macro should switch to the uuid_handle!
macro.
// Before
const IMAGE: Handle<Image> = weak_handle!("b20988e9-b1b9-4176-b5f3-a6fa73aa617f");
// After
const IMAGE: Handle<Image> = uuid_handle!("b20988e9-b1b9-4176-b5f3-a6fa73aa617f");
Users using Handle::clone_weak
can (most likely) just call Handle::clone
instead.
// Somewhere in some startup system.
let my_sprite_image = asset_server.load("monster.png");
// In game code...
// This sprite could be unloaded even if the sprite is still using it!
commands.spawn(Sprite::from_image(my_sprite_image.clone_weak()));
// Just do this instead!
commands.spawn(Sprite::from_image(my_sprite_image.clone()));
Users using the Handle::Weak
variant directly should consider replacing it with AssetId
instead, accessible through Handle::id
. These situations are very case-by-case migrations.
P.S., for users of the weak_handle!
macro: If you are using it for shaders, consider switching to load_shader_library
/load_embedded_asset
instead (especially replacing load_internal_asset
). This enables hot reloading for your shaders - which Bevy internally has done this cycle!
Deprecate iter_entities
and iter_entities_mut
. #
In Bevy 0.17.0 we deprecate world.iter_entities()
and world.iter_entities_mut()
. Use world.query::<EntityMut>().iter(&world)
and world.query::<EntityRef>().iter(&mut world)
instead.
This may not return every single entity, because of default filter queries. If you really intend to query disabled entities too, consider removing the DefaultQueryFilters
resource from the world before querying the elements. You can also add an Allow<Component>
filter to allow a specific disabled Component
to show up in the query.
RelativeCursorPosition
is now object-centered #
When picking objects, RelativeCursorPosition
's coordinates are now object-centered with (0,0) at the center of the node and the corners at (±0.5, ±0.5). Its normalized_visible_node_rect
field has been removed and replaced with a new cursor_over: bool
field which is set to true when the cursor is hovering a visible section of the UI node.
Text2d
moved to bevy_sprite
#
The world-space text types Text2d
and Text2dShadow
have been moved to the bevy_sprite
crate, along with their associated systems. Import them directly or from bevy::sprite
now.
SyncCell
and SyncUnsafeCell
moved to bevy_platform #
bevy_utils::synccell::SyncCell
is now bevy_platform::cell::SyncCell
bevy_utils::syncunsafecell::SyncUnsafeCell
is now bevy_platform::cell::SyncUnsafeCell
Renamed Condition
to SystemCondition
#
The Condition
trait is now called SystemCondition
. Replace all references and imports.
This change was made because Condition
is an overly generic name that collides too often and is rarely used directly, despite appearing in the prelude.
Observers and one-shot systems are now marked as Internal
#
Bevy 0.17 introduces internal entities. Entities tagged by the Internal
component that are hidden from most queries using DefaultQueryFilters
.
Currently, both Observer
s and systems that are registered through World::register_system
are considered internal entities.
If you queried them before, add the Allow<Internal>
filter to the query to bypass the default filter.
SceneSpawner
methods have been renamed and replaced #
Some methods on SceneSpawner
have been renamed: - despawn
-> despawn_dynamic
- despawn_sync
-> despawn_dynamic_sync
- update_spawned_scenes
-> update_spawned_dynamic_scenes
In their place, we've added despawn
, despawn_sync
, and update_spawned_scenes
which all act on Scene
s (as opposed to DynamicScene
s).
Location
is no longer a Component
#
bevy_picking::Location
was erroneously made a Component
. It is no longer one. bevy_picking::PointerLocation
wraps a Location
and is the component you should be using instead.
Remove scale_value
#
The scale_value
function from bevy::text::text2d
has been removed. Multiply by the scale factor instead.
Assets::insert
and Assets::get_or_insert_with
now return Result
#
In previous versions of Bevy, there was a bug where inserting an asset into an AssetId
whose handle was dropped would result in a panic. Now this is an error! Calling Assets::insert
and Assets::get_or_insert_with
returns an error you can inspect.
To match the previous behavior, just unwrap()
the result.
Enable Wayland by default #
Wayland has now been added to the default features of the bevy
crate.
called `Result::unwrap()` on an `Err` value:
pkg-config exited with status code 1
> PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --libs --cflags wayland-client
The system library `wayland-client` required by crate `wayland-sys` was not found.
The file `wayland-client.pc` needs to be installed and the PKG_CONFIG_PATH environment variable must contain its parent directory.
The PKG_CONFIG_PATH environment variable is not set.
HINT: if you have installed the library, try setting PKG_CONFIG_PATH to the directory containing `wayland-client.pc`.
If you've encountered an error message similar to the one above, this means that you will want to make the wayland-client
library available to your build system, or disable default features, in order to successfully build Bevy on Linux.
On Ubuntu, or other Debian-based distributions, install the libwayland-dev
package:
sudo apt install libwayland-dev
On Arch Linux:
sudo pacman -S wayland
On Nix, add the wayland
package to your buildInputs
:
buildInputs = [ pkgs.wayland ];
Generic Option
Parameter #
Option<Single<D, F>>
will now resolve to None
if there are multiple entities matching the query. Previously, it would only resolve to None
if there were no entities, and would skip the system if there were multiple.
We have introduced a blanket impl SystemParam for Option
that resolves to None
if the parameter is invalid. This allows third-party system parameters to work with Option
, and makes the behavior more consistent.
If you want a system to run when there are no matching entities but skip when there are multiple, you will need to use Query<D, F>
and call single()
yourself.
// 0.16
fn my_system(single: Option<Single<&Player>>) {
}
// 0.17
fn my_system(query: Query<&Player>) {
let result = query.single();
if matches!(r, Err(QuerySingleError(MultipleEntities(_)))) {
return;
}
let single: Option<&Player> = r.ok();
}
Stop exposing mp3 support through minimp3 #
The minimp3
feature is no longer exposed from Bevy. Bevy still supports mp3 through the mp3
feature.
If you were relying on something specific to minimp3
, you can still enable it by adding a dependency to rodio
with the minimp3
feature:
[dependencies]
rodio = { version = "0.20", features = ["minimp3"] }
This is best to avoid though, as minimp3
is not actively maintained, doesn't work in wasm, has been known to cause application rejection from the Apple App Store, and has a few security vulnerabilities.
Schedule API Cleanup #
In order to support removing systems from schedules, Vec
s storing System
s and SystemSet
s have been replaced with SlotMap
s which allow safely removing nodes and reusing indices. The maps are respectively keyed by SystemKey
s and SystemSetKey
s.
The following signatures were changed:
DiGraph
andUnGraph
now have an additional, required type parameterN
, which is aGraphNodeId
. UseDiGraph<NodeId>
/UnGraph<NodeId>
for the equivalent to the previous type.NodeId::System
: Now stores aSystemKey
instead of a plainusize
NodeId::Set
: Now stores aSystemSetKey
instead of a plainusize
ScheduleBuildPass::collapse_set
: Now takes the type-specific keys. Wrap them back into aNodeId
if necessary.ScheduleBuildPass::build
: Now takes aDiGraph<SystemKey>
instead ofDiGraph<NodeId>
. Re-wrap the keys back intoNodeId
if necessary.- The following functions now return the type-specific keys. Wrap them back into a
NodeId
if necessary.Schedule::systems
ScheduleGraph::conflicting_systems
ScheduleBuildError
variants now containNodeId
or type-specific keys, rather thanString
s. UseScheduleBuildError::to_string
to render the nodes' names and get the old error messages.ScheduleGraph::build_schedule
now returns aVec<ScheduleBuildWarning>
in addition to the builtSystemSchedule
. Use standardResult
functions to grab just theSystemSchedule
, if needed.
The following functions were replaced. Those that took or returned NodeId
now take or return SystemKey
or SystemSetKey
. Wrap/unwrap them as necessary.
ScheduleGraph::contains_set
: UseScheduleGraph::system_sets
andSystemSets::contains
.ScheduleGraph::get_set_at
: UseScheduleGraph::system_sets
andSystemSets::get
.ScheduleGraph::set_at
: UseScheduleGraph::system_sets
andSystemSets::index
(system_sets[key]
).ScheduleGraph::get_set_conditions_at
: UseScheduleGraph::system_sets
andSystemSets::get_conditions
.ScheduleGraph::system_sets
: UseScheduleGraph::system_sets
andSystemSets::iter
.ScheduleGraph::get_system_at
: UseScheduleGraph::systems
andSystems::get
.ScheduleGraph::system_at
: UseScheduleGraph::systems
andSystems::index
(systems[key]
).ScheduleGraph::systems
: UseScheduleGraph::systems
andSystems::iter
.
The following enum variants were replaced:
ScheduleBuildError::HierarchyRedundancy
withScheduleBuildError::Elevated(ScheduleBuildWarning::HierarchyRedundancy)
ScheduleBuildError::Ambiguity
withScheduleBuildError::Elevated(ScheduleBuildWarning::Ambiguity)
The following functions were removed:
NodeId::index
: You should match on and use theSystemKey
andSystemSetKey
instead.NodeId::cmp
: Use thePartialOrd
andOrd
traits instead.ScheduleGraph::set_conditions_at
: If needing to check presence of conditions, useScheduleGraph::system_sets
andSystemSets::has_conditions
. Otherwise, useSystemSets::get_conditions
.
bevy_render
reorganization #
You must now import bevy_render::NormalizedRenderTargetExt
to use methods on NormalizedRenderTarget
ManualTextureViews
is now in bevy_render::texture
Camera types such as Camera
, Camera3d
, Camera2d
, ClearColor
, ClearColorConfig
, Exposure
, Projection
, PerspectiveProjection
, and OrthographicProjection
have been moved to a new crate, bevy_camera
. Visibility types such as Visibility
, InheritedVisibility
, ViewVisibility
, VisibleEntities
, and RenderLayers
have been moved to bevy_camera::visibility
. Culling primitives such as Frustum
, HalfSpace
, Aabb
, and Sphere
have been moved to bevy_camera::primitives
. Import them directly or from bevy::camera
now.
Shader types such as Shader
, ShaderRef
, ShaderDef
, ShaderCache
, and PipelineCompilationError
have been moved to a new crate, bevy_shader
. Import them directly or from bevy::shader
now.
Light types such AmbientLight
, PointLight
, SpotLight
, DirectionalLight
, EnvironmentMapLight
, GeneratedEnvironmentMapLight
, LightProbe
, IrradianceVolume
, VolumetricFog
, FogVolume
, CascadeShadowConfigBuilder
, NotShadowCaster
, NotShadowReceiver
and light_consts
have been moved to a new crate, bevy_light
. Import them directly or from bevy::light
now.
Mesh types such as Mesh
, Mesh3d
, Mesh2d
, MorphWeights
, MeshBuilder
, Indices
, and Meshable
have been moved to a new crate, bevy_mesh
. Import them directly or from bevy::mesh
now. This crate is actually present in the previous release, but its bevy_render
re-exports have now been removed.
Image types such as Image
, ImagePlugin
, ImageFormat
, ImageSampler
, ImageAddressMode
, ImageSamplerDescriptor
, ImageCompareFunction
, and ImageSamplerBorderColor
have been moved to a new crate, bevy_image
. This crate is actually present in the previous release, but its bevy_render
re-exports have now been removed. Import them directly or from bevy::image
now.
Ui rendering types such as MaterialNode
, UiMaterial
, UiMaterialKey
, and modules bevy_ui::render
and bevy_ui::ui_material
have been moved to a new crate, bevy_ui_render
. Import them directly or from bevy::ui_render
now. Furthermore, UiPlugin
no longer has any fields. To control whether or not UI is rendered, enable or disable UiRenderPlugin
, which is included in the DefaultPlugins. If you were manually enabling "bevy_ui" feature on bevy, you probably want to enable "bevy_ui_render" feature instead now if you are using rendering features.
Sprite rendering types such as Material2d
, Material2dPlugin
, MeshMaterial2d
, AlphaMode2d
, Wireframe2d
, TileData
, TilemapChunk
, and TilemapChunkTileData
have been moved to a new crate, bevy_sprite_render
. Import them directly or from bevy::sprite_render
now. If you were manually enabling "bevy_sprite" feature on bevy, you probably want to enable "bevy_sprite_render" feature instead now if you are using rendering features such as 2d gizmos.
RenderAssetUsages
is no longer re-exported by bevy_render
. Import it from bevy_asset
or bevy::asset
instead.
bevy_core_pipeline
used to be home to many non-core things, including post process effects. They have now been given a new home in bevy_anti_alias
and bevy_post_process
.
If you were importing FxaaPlugin, SmaaPlugin, TemporalAntiAliasPlugin, or CasPlugin from bevy_core_pipeline
or bevy::core_pipeline
, you must now import them from bevy_anti_alias
or bevy::anti_alias
.
If you were importing Bloom, AutoExposure, ChromaticAberration, DepthOfField, or MotionBlur from bevy_core_pipeline
or bevy::core_pipeline
, you must now import them from bevy_post_process
or bevy::post_process
.
Additionally, you may now order rendering passes against the new StartMainPassPostProcessing
node.
Rename Pointer<Pressed>
and Pointer<Released>
to Pointer<Press>
and Pointer<Release>
#
The Pointer<Pressed>
and Pointer<Released>
events have been renamed to Pointer<Press>
and Pointer<Release>
for improved consistency. Pressed
is now a marker component indicating that a button or other UI node is in a pressed or "held down" state.
YAxisOrientation
has been removed #
The YAxisOrientation
component has been removed from bevy_text
. The correct y-axis orientation is now chosen automatically by the text systems.
Renamed Timer::paused
to Timer::is_paused
and Timer::finished
to Timer::is_finished
#
The following changes were made:
Timer::paused
is nowTimer::is_paused
Timer::finished
is nowTimer::is_finished
This change was made to align the Timer
public API with that of Time
and Stopwatch
.
glTF animation loading is now optional #
GltfLoaderSettings
now has a load_animations
field which allows controlling whether animations should load.
Fixed UI draw order and stack_z_offsets
changes #
The draw order of some renderable UI elements relative to others wasn't fixed and depended on system ordering. In particular the ordering of background colors and texture sliced images was sometimes swapped.
The UI draw order is now fixed. The new order is (back-to-front):
- Box shadows
- Node background colors
- Node borders
- Gradients
- Border Gradients
- Images (including texture-sliced images)
- Materials
- Text (including text shadows)
The values of the stack_z_offsets
constants have been updated to enforce the new ordering. Other changes:
NODE
is renamed toBACKGROUND_COLOR
TEXTURE_SLICE
is removed, useIMAGE
.- New
BORDER
,BORDER_GRADIENT
andTEXT
constants.
RemovedComponents
methods renamed to match Event
to Message
rename #
As part of the broader shift to differentiate between buffered events (0.16's EventWriter
/EventReader
) and observer-events, various methods and types related to RemovedComponents
have been renamed.
The implementation of RemovedComponents
uses buffered events (now, messages): and as a result, the following types and messages have been renamed:
Old | New |
---|---|
RemovedComponents::events | RemovedComponents::messages |
RemovedComponents::reader_mut_with_events | RemovedComponents::reader_mut_with_messages |
RemovedComponentEvents | RemovedComponentMessages |
Window is now split into multiple components #
Window
has become a very large component over the last few releases. To improve our internal handling of it and to make it more approachable, we have split it into multiple components, all on the same entity. So far, this affects CursorOptions
:
// old
fn lock_cursor(primary_window: Single<&mut Window, With<PrimaryWindow>>) {
primary_window.cursor_options.grab_mode = CursorGrabMode::Locked;
}
// new
fn lock_cursor(primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>) {
primary_cursor_options.grab_mode = CursorGrabMode::Locked;
}
This split also applies when specifying the initial settings for the primary window:
// old
app.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
cursor_options: CursorOptions {
grab_mode: CursorGrabMode::Locked,
..default()
},
..default()
}),
..default()
}));
// new
app.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_cursor_options: Some(CursorOptions {
grab_mode: CursorGrabMode::Locked,
..default()
}),
..default()
}));
Removed cosmic_text
re-exports #
Previously, bevy_text
re-exported the entirety of cosmic_text
while renaming a few of the most confusing re-exports, using the following code.
pub use cosmic_text::{
self, FamilyOwned as FontFamily, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight,
};
These re-exports commonly conflicted with other types (like Query
!), leading to messy autocomplete errors. Ultimately, these are largely an implementation detail, and were not widely used.
We've completely removed these re-exports (including the renamed types): if you need to use these types, please rely on them directly from cosmic_text
, being sure that the version number you are using matches the version used by your version of bevy_text
.
Rename send_event
and similar methods to write_message
#
Following up on the EventWriter::send
being renamed to EventWriter::write
in 0.16, many similar methods have been renamed. Note that "buffered events" are now known as Messages
, and the naming reflects that here.
This includes both the World
and Commands
message methods. The old methods have been depreciated.
Old | New |
---|---|
World::send_event | World::write_message |
World::send_event_default | World::write_message_default |
World::send_event_batch | World::write_message_batch |
DeferredWorld::send_event | DeferredWorld::write_message |
DeferredWorld::send_event_default | DeferredWorld::write_message_default |
DeferredWorld::send_event_batch | DeferredWorld::write_message_batch |
Commands::send_event | Commands::write_message |
Events::send | Messages::write |
Events::send_default | Messages::write_default |
Events::send_batch | Messages::write_batch |
RemovedComponentEvents::send | RemovedComponentEvents::write |
command::send_event | command::write_message |
SendBatchIds | WriteBatchIds |
Move UI Debug Options from bevy_ui
to bevy_ui_render
#
The UiDebugOptions
resource used for controlling the UI Debug Overlay has been moved from the internal bevy_ui
crate to the bevy_ui_render
crate, and is now accessible from the prelude of bevy_ui_render
and, as before, from the prelude of bevy
:
// 0.16
use bevy::prelude::*;
// or
use bevy::ui::UiDebugOptions;
// 0.17
use bevy::prelude::*;
// or, if you are not using the full `bevy` crate:
// use bevy_ui_render::prelude::*;
let options = world.resource_mut::<UiDebugOptions>();
CloneBehavior
is no longer PartialEq
or Eq
#
CloneBehavior
no longer implements PartialEq
or Eq
and thus does not work with the ==
and !=
operators, as the internal comparisons involve comparing function pointers which may result in unexpected results.
Use pattern matching to check for equality instead:
// 0.16
if clone_behavior == CloneBehavior::Ignore {
...
}
// 0.17
if matches!(clone_behavior, CloneBehavior::Ignore) {
...
}
Interned labels and DynEq
#
DynEq::as_any
has been removed. Use&value as &dyn Any
instead.DynHash::as_dyn_eq
has been removed. Use&value as &dyn DynEq
instead.as_dyn_eq
has been removed from 'label' types such asScheduleLabel
andSystemSet
. CallDynEq::dyn_eq
directly on the label instead.
Split Hdr
from Camera
#
Camera.hdr
has been split out into a new marker component, Hdr
, which can be found at bevy::render::view::Hdr
.
- before:
commands.spawn((Camera3d, Camera { hdr: true, ..default() });
- after:
commands.spawn((Camera3d, Hdr));
- rendering effects can now
#[require(Hdr)]
if they only function with an HDR camera. This is currently implemented forBloom
,AutoExposure
, andAtmosphere
The render target info from ComputedUiTargetCamera
has been removed. #
The render target info, scale factor and physical size, has been removed from ComputedUiTargetCamera
and placed into a new component ComputedUiRenderTargetInfo
.
Reflection-based maps are now unordered #
DynamicMap
is now unordered, and the Map
trait no longer assumes implementors to be ordered. If you previously relied on them being ordered, you should now store a list of keys (Vec<Box<dyn PartialReflect>>
) separately.
Map::get_at
and Map::get_at_mut
are now removed. You should no longer use usize
to index into the map, and instead use &dyn PartialReflect
with Map::get
and Map::get_mut
.
PartialReflect::apply(self, other)
for maps now removes excess entries (entries present in self
which are not present in other
). If you need those entries to be preserved, you will need to re-insert them manually.
Rework MergeMeshError
#
MergeMeshError
was reworked to account for the possibility of the meshes being merged having two different PrimitiveTopology
's, and was renamed to MeshMergeError
to align with the naming of other mesh errors.
- Users will need to rename
MergeMeshError
toMeshMergeError
- When handling
MergeMeshError
(nowMeshMergeError
), users will need to account for the newIncompatiblePrimitiveTopology
variant, as it has been changed from a struct to an enum Mesh::merge
now returnsResult<(), MeshMergeError>
instead of the previousResult<(), MergeMeshError>
Use glTF material names for spawned primitive entities #
When loading a glTF scene in Bevy, each mesh primitive will generate an entity and store a GltfMaterialName
component and Name
component.
The Name
components were previously stored as mesh name plus primitive index - for example, MeshName.0
and MeshName.1
. To make it easier to view these entities in Inspector-style tools, they are now stored as mesh name plus material name - for example, MeshName.Material1Name
and MeshName.Material2Name
.
If you were relying on the previous value of the Name
component on meshes, use the new GltfMeshName
component instead.
ViewRangefinder3d::from_world_from_view
now takes Affine3A
instead of Mat4
#
ViewRangefinder3d::from_world_from_view
now takes Affine3A
instead of Mat4
. If you were supplying a GlobalTransform::to_matrix()
, simply use GlobalTransform::affine()
now. Performance will be better.
Original target of Pointer
picking events is now stored on observers #
The Pointer.target
field, which tracks the original target of the pointer event before bubbling, has been removed. Instead, all "bubbling entity event" observers now track this information, available via the On::original_entity()
method.
If you were using this information via the Pointer API of picking, please migrate to observers. If you cannot for performance reasons, please open an issue explaining your exact use case!
As a workaround, you can transform any entity-event into a Message that contains the targeted entity using an observer than writes messages.
#[derive(Message)]
struct EntityEventMessage<E: EntityEvent> {
entity: Entity,
event: E,
}
// A generic observer that handles this transformation
fn transform_entity_event<E: EntityEvent>(event: On<E>, message_writer: MessageWriter<EntityEventMessage<E>>){
if event.entity() == event.original_entity() {
message_writer.send(EntityEventMessage {
event: event.event().clone(),
entity: event.entity(),
);
}
}
Remove the Add
/Sub
impls on Volume
#
Linear volumes are like percentages, and it does not make sense to add or subtract percentages. As such, use the new increase_by_percentage
function instead of addition or subtraction.
// 0.16
fn audio_system() {
let linear_a = Volume::Linear(0.5);
let linear_b = Volume::Linear(0.1);
let linear_c = linear_a + linear_b;
let linear_d = linear_a - linear_b;
}
// 0.17
fn audio_system() {
let linear_a = Volume::Linear(0.5);
let linear_b = Volume::Linear(0.1);
let linear_c = linear_a.increase_by_percentage(10.0);
let linear_d = linear_a.increase_by_percentage(-10.0);
}
Renamed ComputedNodeTarget
and update_ui_context_system
#
ComputedNodeTarget
has been renamed to ComputedUiTargetCamera
. New name chosen because the component's value is derived from UiTargetCamera
.
update_ui_context_system
has been renamed to propagate_ui_target_cameras
.
ChromaticAberration LUT is now Option #
The ChromaticAberration
component color_lut
field use to be a regular Handle<Image>
. Now, it is an Option<Handle<Image>>
which falls back to the default image when None
. For users assigning a custom LUT, just wrap the value in Some
.
Extract PointerInputPlugin
members into PointerInputSettings
#
Toggling mouse and touch input update for picking should be done through the PointerInputSettings
resource instead of PointerInputPlugin
.
To initialize PointerInputSettings
with non-default values, simply add the resource to the app using insert_resource
with the desired value.
DragEnter
now includes the dragged entity #
DragEnter
events are now triggered when entering any entity, even the originally dragged one. This makes the behavior more consistent.
The old behavior can be achieved by checking if trigger.entity != trigger.dragged
Separate Border Colors #
The BorderColor
struct now contains separate fields for each edge, top
, bottom
, left
, right
. To keep the existing behavior, replace BorderColor(color)
with BorderColor::all(color)
, and border_color.0 = new_color
with *border_color = BorderColor::all(new_color)
.
ScrollPosition
now uses logical pixel units and is no longer overwritten during layout updates #
ScrollPosition
is no longer overwritten during layout updates. Instead the computed scroll position is stored in the new scroll_position
field on ComputedNode
.
Combine now takes an extra parameter #
The Combine::combine
method now takes an extra parameter that needs to be passed mutably to the two given closures. This allows fixing a soundness issue which manifested when the two closures were called re-entrantly.
Fix From<Rot2>
implementation for Mat2
#
Past releases had an incorrect From<Rot2>
implementation for Mat2
, constructing a rotation matrix in the following form:
[ cos, sin ]
[ -sin, cos ]
This was actually the inverse of the rotation matrix, resulting in clockwise rotation when transforming vectors. The correct version is the following:
[ cos, -sin ]
[ sin, cos ]
resulting in counterclockwise rotation.
This error has now been fixed. You may see that rotation matrices created using the From<Rot2>
implementation produce different results than before, as the rotation happens counterclockwise (as intended) rather than clockwise. Invert either the input Rot2
or the resulting Mat2
to get the same results as before.
Extract PickingPlugin
members into PickingSettings
#
Controlling the behavior of picking should be done through the PickingSettings
resource instead of PickingPlugin
.
To initialize PickingSettings
with non-default values, simply add the resource to the app using insert_resource
with the desired value.
Consistent *Systems
naming convention for system sets #
System sets in Bevy now more consistently use a Systems
suffix. Renamed types include:
AccessibilitySystem
→AccessibilitySystems
GizmoRenderSystem
→GizmoRenderSystems
PickSet
→PickingSystems
RunFixedMainLoopSystem
→RunFixedMainLoopSystems
TransformSystem
→TransformSystems
RemoteSet
→RemoteSystems
RenderSet
→RenderSystems
SpriteSystem
→SpriteSystems
StateTransitionSteps
→StateTransitionSystems
RenderUiSystem
→RenderUiSystems
UiSystem
→UiSystems
Animation
→AnimationSystems
AssetEvents
→AssetEventSystems
TrackAssets
→AssetTrackingSystems
UpdateGizmoMeshes
→GizmoMeshSystems
InputSystem
→InputSystems
InputFocusSet
→InputFocusSystems
ExtractMaterialsSet
→MaterialExtractionSystems
ExtractMeshesSet
→MeshExtractionSystems
RumbleSystem
→RumbleSystems
CameraUpdateSystem
→CameraUpdateSystems
ExtractAssetsSet
→AssetExtractionSystems
Update2dText
→Text2dUpdateSystems
TimeSystem
→TimeSystems
EventUpdates
→EventUpdateSystems
To improve consistency within the ecosystem, it is recommended for ecosystem crates and users to also adopt the *Systems
naming convention for their system sets where applicable.
System::run
returns Result
#
In order to support fallible systems and parameter-based system skipping like Single
and If<T>
in more places, System::run
and related methods now return a Result
instead of a plain value.
If you were calling System::run
, System::run_unsafe
, System::run_without_applying_deferred
, or ReadOnlySystem::run_readonly
, the simplest solution is to unwrap()
the resulting Result
. The only case where an infallible system will return Err
is an invalid parameter, such as a missing resource, and those cases used to panic.
If you were calling them from a function that returns Result<T, BevyError>
, you can instead use the ?
operator.
System::run
, System::run_without_applying_deferred
, and ReadOnlySystem::run_readonly
will now call System::validate_param_unsafe
and return Err
if validation fails. If you were calling validate_param
or validate_param_unsafe
before calling one of those, it is no longer necessary. Note that System::run_unsafe
still does not perform validation.
If you were manually implementing System
, the return type to run_unsafe
has changed from Out
to Result<Out, RunSystemError>
. If you are implementing an infallible system, simply wrap the return value in Ok
. If you were implementing a fallible system and had set type Out = Result<T, BevyError>;
, instead set type Out = T;
.
If you have a system function that returns Result
or !
and are not restricting the return type, you may get type inference failures like this:
error[E0283]: type annotations needed
--> lib.rs:100:5
|
100 | IntoSystem::into_system(example_system);
| ^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `Out` declared on the trait `IntoSystem`
|
note: multiple `impl`s satisfying `core::result::Result<(), bevy_error::BevyError>: function_system::IntoResult<_>` found
--> crates\bevy_ecs\src\system\function_system.rs:597:1
or
error[E0283]: type annotations needed
--> lib.rs:100:11
|
100 | world.run_system_cached(system).unwrap();
| ^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `O` declared on the method `run_system_cached`
|
note: multiple `impl`s satisfying `core::result::Result<(), bevy_error::BevyError>: function_system::IntoResult<_>` found
--> crates\bevy_ecs\src\system\function_system.rs:597:1
A function that returns Result<T, BevyError>
may be considered either a fallible system that returns T
or an infallible system that returns Result
, and a function that returns !
may be considered a system that returns any type. You should be able to resolve them by providing an explicit type for System::Out
.
fn example_system() -> Result { Ok(()) }
// 0.16 - Output type is inferred to be `Result`
IntoSystem::into_system(example_system)
// 0.17 - Output type can be either `()` or `Result` and must be written explicitly
IntoSystem::<_, (), _>::into_system(example_system);
IntoSystem::<_, Result, _>::into_system(example_system);
// 0.16 - Output type is inferred to be `Result`
world.run_system_cached(example_system).unwrap().unwrap();
// 0.17 - Output type can be either `()` or `Result` and must be written explicitly
world.run_system_cached::<(), _, _>(example_system).unwrap();
world.run_system_cached::<Result, _, _>(example_system).unwrap().unwrap();
// or it may be inferred if the output type is specified elsewhere
let _: () = world.run_system_cached(example_system).unwrap();
let r: Result = world.run_system_cached(example_system).unwrap();
LightVisibilityClass
renamed to ClusterVisibilityClass
#
When clustered decals were added, they used LightVisibilityClass
to share the clustering infrastructure. This revealed that this visibility class wasn't really about lights, but about clustering. It has been renamed to ClusterVisibilityClass
and moved to live alongside clustering-specific types.
Renamed state scoped entities and events #
Previously, Bevy provided the StateScoped
component and add_state_scoped_event
method as a way to remove entities/events when exiting a state.
However, it can also be useful to have the opposite behavior, where entities/events are removed when entering a state. This is now possible with the new DespawnOnEnter
component and clear_events_on_enter
method.
To support this addition, the previous method and component have been renamed. Also, clear_event_on_exit
(previously clear_event_on_exit_state
) no longer adds the event automatically, so you must call App::add_event
manually.
Before | After |
---|---|
StateScoped | DespawnOnExit |
clear_state_scoped_entities | despawn_entities_on_exit_state |
add_state_scoped_event | add_event + clear_events_on_exit |
Renamed JustifyText
to Justify
#
JustifyText
has been renamed to Justify
.
The -Text
suffix was inconsistent with the names of other bevy_text
types and unnecessary since it's natural to assume Justify
refers to text justification.
Remove default implementation of extend_from_iter
from RelationshipSourceCollection
#
The extend_from_iter
method in the RelationshipSourceCollection
trait no longer has a default implementation. If you have implemented a custom relationship source collection, you must now provide your own implementation of this method.
// Before: method was optional due to default implementation
impl RelationshipSourceCollection for MyCustomCollection {
// ... other required methods
// extend_from_iter was automatically provided
}
// After: method is now required
impl RelationshipSourceCollection for MyCustomCollection {
// ... other required methods
fn extend_from_iter(&mut self, entities: impl IntoIterator<Item = Entity>) {
// Use your collection's native extend method if available
self.extend(entities);
// Or implement manually if needed:
// for entity in entities {
// self.add(entity);
// }
}
}
AnimationGraph
no longer supports raw AssetIds #
In previous versions of Bevy, AnimationGraph
would serialize Handle<AnimationClip>
as an asset path, and if that wasn't available it would fallback to serializing AssetId<AnimationClip>
. In practice, this was not very useful. AssetId
is (usually) a runtime-generated ID. This means for an arbitrary Handle<AnimationClip>
, it was incredibly unlikely that your handle before serialization would correspond to the same asset as after serialization.
This confusing behavior has been removed. As a side-effect, any AnimationGraph
s you previously saved (via AnimationGraph::save
) will need to be re-saved. These legacy AnimationGraph
s can still be loaded until the next Bevy version. Loading and then saving the AnimationGraph
again will automatically migrate the AnimationGraph
.
If your AnimationGraph
contained serialized AssetId
s, you will need to manually load the bytes of the saved graph, deserialize it into SerializedAnimationGraph
, and then manually decide how to migrate those AssetId
s. Alternatively, you could simply rebuild the graph from scratch and save a new instance. We expect this to be a very rare situation.
Unify ObserverState
and Observer
components #
ObserverState
and Observer
have been merged into a single component. now you can use Observer::with_dynamic_runner
to build custom Observe.
let observe = unsafe {
Observer::with_dynamic_runner(|world, trigger_context, event_ptr, trigger_ptr| {
// do something
})
.with_event(event_a)
};
world.spawn(observe);
take_extract
now returns dyn FnMut
instead of dyn Fn
#
Previously, set_extract
accepted any Fn
. Now we accept any FnMut
. For callers of set_extract
, there is no difference since Fn: FnMut
.
However, callers of take_extract
will now be returned Option<Box<dyn FnMut(&mut World, &mut World) + Send>>
instead.
OpenGL ES wgpu
backend is no longer supported by default #
The gles
backend for wgpu
is no longer included as a default feature of bevy_render
. OpenGL support is still available, but must be explicitly enabled by adding the bevy_render/gles
feature to your app. This change reflects the fact that OpenGL support is not tested and that some features may not work as expected or at all. We welcome contributions to improve OpenGL support in the future.
labeled_asset_scope
can now return errors #
labeled_asset_scope
now returns a user-specified error type based on their closure. Previously, users would need to fall back to begin_labeled_asset
and add_loaded_labeled_asset
to handle errors, which is more error-prone. Consider migrating to use labeled_asset_scope
if this was you!
However, labeled_asset_scope
closures that don't return errors now needs to A) return Ok, and B) specify an error type.
If your code previously looked like this:
labeled_asset_scope(label, |mut load_context| {
let my_asset = ...;
my_asset
});
You can migrate it to:
labeled_asset_scope::<_, ()>(label, |mut load_context| {
let my_asset = ...;
Ok(my_asset)
}).unwrap();
Make ScrollPosition
newtype Vec2
#
ScrollPosition
now newtypes Vec2
; its offset_x
and offset_y
fields have been removed.
Move cursor-related types from bevy_winit
to bevy_window
#
In an effort to reduce and untangle dependencies, cursor-related types have been moved from the bevy_winit
crate to the bevy_window
crate. The following types have been moved as part of this change:
CursorIcon
is now located atbevy::window::CursorIcon
.CustomCursor
is now located atbevy::window::CustomCursor
.CustomCursorImage
is now located atbevy::window::CustomCursorImage
.CustomCursorUrl
is now located atbevy::window::CustomCursorUrl
.- on the android platform,
ANDROID_APP
is now located in it's own crate and can be found atbevy::android::ANDROID_APP
.
ExtractedUiNode
's stack_index
has been renamed to z_order
and is now an f32
. #
ExtractedUiNode
’s stack_index
field has been renamed to z_order
and its type changed from u32
to f32
. Previously stack_index
would be converted into an f32
after extraction during the Render
schedule, then offsets would be applied applied to determine draw order before sorting (lowest value rendered first). For example, a node's fill color is given an offset of 0.
and a box shadow is given an offset of -0.1
, so that the shadow will be drawn behind the node.
Changing the field to an f32
, enables finer control of the UI draw order by allowing these offsets to be applied during extraction, and fixes a bug affecting the ordering of texture-sliced nodes.
Changes to Bevy's system parallelism strategy #
The scheduler will now prevent systems from running in parallel if there could be an archetype that they conflict on, even if there aren't actually any. To expand on this, previously, the scheduler would look at the entities that exist to determine if there's any overlap. Now, it determines it solely on the basis of the function signatures of the systems that are run. This was done as a performance optimization: while in theory this throws away potential parallelism, in practice, tests on engine and user code found that scheduling overhead dominates parallelism gains with the current multithreaded executor. Swapping to this simpler, more conservative test allows us to speed up these checks and improve resource utilization. There are more improvements planned here, so stay tuned!
To understand what this means, consider the following example. These systems will now conflict even if no entity has both Player
and Enemy
components:
fn player_system(query: Query<(&mut Transform, &Player)>) {}
fn enemy_system(query: Query<(&mut Transform, &Enemy)>) {}
To allow them to run in parallel, use Without
filters, just as you would to allow both queries in a single system:
// Either one of these changes alone would be enough
fn player_system(query: Query<(&mut Transform, &Player), Without<Enemy>>) {}
fn enemy_system(query: Query<(&mut Transform, &Enemy), Without<Player>>) {}
If you encounter a performance regression in your application due to this change, please open an issue. We would be very interested to understand your use case, see your benchmarking results, and help you mitigate any issues.
wgpu
25 #
wgpu
25 introduces a number of breaking changes, most notably in the way Bevy is required to handle uniforms with dynamic offsets which are used pervasively in the renderer. Dynamic offsets and uniforms of any kind are no longer allowed to be used in the same bind group as binding arrays. As such, the following changes to the default bind group numbering have been made in 3d:
@group(0)
view binding resources@group(1)
view resources requiring binding arrays@group(2)
mesh binding resources@group(3)
material binding resources
Most users who are not using mid-level render APIs will simply need to switch their material bind groups from @group(2)
to @group(#{MATERIAL_BIND_GROUP})
. The MATERIAL_BIND_GROUP
shader def has been added to ensure backwards compatibility in the event the bind group numbering changes again in the future.
Exported float constants from shaders without an explicit type declaration like const FOO = 1.0;
are no longer supported and must be explicitly typed like const FOO: f32 = 1.0;
.
See the full changelog here.
When migrating shaders or other custom rendering code, you may encounter panics like:
wgpu error: Validation Error
Caused by:
In Device::create_render_pipeline, label = 'pbr_opaque_mesh_pipeline'
Error matching ShaderStages(FRAGMENT) shader requirements against the pipeline
Shader global ResourceBinding { group: 2, binding: 100 } is not available in the pipeline layout
Binding is missing from the pipeline layout
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_render::render_resource::pipeline_cache::PipelineCache::process_pipeline_queue_system`!
This error is a result of Bevy's bind group indices changing. Identify the shader by searching for the group and binding mentioned, e.g. @group(2) @binding(100)
, and follow the above advice to fix the binding group index.
Anchor
is now a required component on Sprite
#
The anchor
field has been removed from Sprite
. Instead the Anchor
component is now a required component on Sprite
.
The anchor variants have been moved to associated constants, following the table below:
0.16 | 0.17 |
---|---|
Anchor::Center | Anchor::Center |
Anchor::BottomLeft | Anchor::BOTTOM_LEFT |
Anchor::BottomCenter | Anchor::BOTTOM_CENTER |
Anchor::BottomRight | Anchor::BOTTOM_RIGHT |
Anchor::CenterLeft | Anchor::CENTER_LEFT |
Anchor::CenterRight | Anchor::CENTER_RIGHT |
Anchor::TopLeft | Anchor::TOP_LEFT |
Anchor::TopCenter | Anchor::TOP_CENTER |
Anchor::TopRight | Anchor::TOP_RIGHT |
Anchor::Custom(value) | Anchor(value) |
Many render resources now initialized in RenderStartup
#
Many render resources are no longer present during Plugin::finish
. Instead they are initialized during RenderStartup
(which occurs once the app starts running). If you only access the resource during the Render
schedule, then there should be no change. However, if you need one of these render resources to initialize your own resource, you will need to convert your resource initialization into a system.
The following are the (public) resources that are now initialized in RenderStartup
.
CasPipeline
FxaaPipeline
SmaaPipelines
TaaPipeline
ShadowSamplers
GlobalClusterableObjectMeta
FallbackBindlessResources
AutoExposurePipeline
MotionBlurPipeline
SkyboxPrepassPipeline
BlitPipeline
DepthOfFieldGlobalBindGroupLayout
DepthPyramidDummyTexture
OitBuffers
PostProcessingPipeline
TonemappingPipeline
BoxShadowPipeline
GradientPipeline
UiPipeline
UiMaterialPipeline<M>
UiTextureSlicePipeline
ScreenshotToScreenPipeline
VolumetricFogPipeline
DeferredLightingLayout
CopyDeferredLightingIdPipeline
RenderLightmaps
PrepassPipeline
PrepassViewBindGroup
Wireframe3dPipeline
ScreenSpaceReflectionsPipeline
MaterialPipeline
MeshletPipelines
MeshletMeshManager
ResourceManager
Wireframe2dPipeline
Material2dPipeline
SpritePipeline
Mesh2dPipeline
BatchedInstanceBuffer<Mesh2dUniform>
The vast majority of cases for initializing render resources look like so (in Bevy 0.16):
impl Plugin for MyRenderingPlugin {
fn build(&self, app: &mut App) {
// Do nothing??
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<MyRenderResource>();
render_app.add_systems(Render, my_render_system);
}
}
pub struct MyRenderResource {
...
}
impl FromWorld for MyRenderResource {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let render_adapter = world.resource::<RenderAdapter>();
let asset_server = world.resource::<AssetServer>();
MyRenderResource {
...
}
}
}
The two main things to focus on are:
- The resource implements the
FromWorld
trait which collects all its dependent resources (most commonly,RenderDevice
), and then creates an instance of the resource. - The plugin adds its systems and resources in
Plugin::finish
.
First, we need to rewrite our FromWorld
implementation as a system. This generally means converting calls to World::resource
into system params, and then using Commands
to insert the resource. In the above case, that would look like:
// Just a regular old system!!
fn init_my_resource(
mut commands: Commands,
render_device: Res<RenderDevice>,
render_adapter: Res<RenderAdapter>,
asset_server: Res<AssetServer>,
) {
commands.insert_resource(MyRenderResource {
...
});
}
Each case will be slightly different. Two notes to be wary of:
- Functions that accept
&RenderDevice
for example may no longer compile after switching toRes<RenderDevice>
. This can be resolved by passing&render_device
instead ofrender_device
. - If you are using
load_embedded_asset(world, "my_asset.png")
, you may need to first addasset_server
as a system param, then change this toload_embedded_asset(asset_server.as_ref(), "my_asset.png")
.
Now that we have our initialization system, we just need to add the system to RenderStartup
:
impl Plugin for MyRenderingPlugin {
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.add_systems(RenderStartup, init_my_resource)
.add_systems(Render, my_render_system);
}
// No more finish!!
}
In addition, if your resource requires one of the affected systems above, you will need to use system ordering to ensure your resource initializes after the other system. For example, if your system uses Res<UiPipeline>
, you will need to add an ordering like:
render_app.add_systems(RenderStartup, init_my_resource.after(init_ui_pipeline));
GatedReader
and GatedOpener
are now private. #
The GatedReader
and GatedOpener
for bevy_asset
have been made private. These were really only for testing, but were being compiled even in release builds. Now they are guarded by #[cfg(test)]
!
If you were using this in your own tests, you could fork the GatedReader
(it still exists in the Bevy repo!) into your own code, or write your own version (if more useful to you).
Updated glam
, rand
and getrandom
versions with new failures when building for web #
We've upgraded glam
and the other math crates (encase
, hexasphere
) that move in lockstep to the latest versions. With newer versions of glam
& encase
, the updated versions don't seem to have introduced breakages, though as always, best to consult their docs 1 2 for any changes.
This has also upgraded the version of rand
and getrandom
that Bevy relies on. rand
changes are more extensive, with changes such as thread_rng()
-> rng()
, from_entropy()
-> from_os_rng()
, and so forth. RngCore
is now split into infallible RngCore
and fallible TryRngCore
, and the distributions
module has been renamed to distr
. Most of this affects only internals, and doesn't directly affect Bevy's APIs. For the full set of changes, see rand
migration notes.
getrandom
is also updated, and will require additional configuration when building Bevy for WASM/web browsers. This will affect you even if you are not using rand
or getrandom
directly, as glam
(and thus bevy_math
) will pull it in.
You may encounter an error like:
error: the wasm*-unknown-unknown targets are not supported by default;
to enable support, add this to your `Cargo.toml`:
[dependencies]
getrandom = { version = "0.3", features = ["wasm_js"] }
This is due to a breaking change in how getrandom
handles entropy generation. For security reasons, this is no longer specified via feature flags, as any crate in your dependency tree could quietly enable additional entropy sources.
Quoting from the getrandom
docs on WebAssembly support in getrandom
2:
To enable getrandom's functionality on wasm32-unknown-unknown using the Web Crypto methods described above via wasm-bindgen, do both of the following:
- Use the wasm_js feature flag, i.e. getrandom = { version = "0.3", features = ["wasm_js"] }. On its own, this only makes the backend available. (As a side effect this will make your Cargo.lock significantly larger if you are not already using wasm-bindgen, but otherwise enabling this feature is harmless.)
- Set RUSTFLAGS='--cfg getrandom_backend="wasm_js"' (see above).
Note that if you were previously setting the RUSTFLAGS
environment variable for any reason, this will override any previous settings: you need to add this to your existing list instead.
If you were using the community-provided Bevy CLI to easily create builds of your game for different platforms (including web), make sure to update to v0.1.0-alpha.2 or later, which will automatically configure RUSTFLAGS
for you.
OverflowClipBox
's default is now PaddingBox
#
The default variant for OverflowClipBox
is now PaddingBox
. The default value for OverflowClipMargin::visual_box
is now OverflowClipBox::PaddingBox
.
Event
trait split / Rename #
"Buffered events" (things sent/read using EventWriter
/ EventReader
) are now no longer referred to as "events", in the interest of conceptual clarity and learn-ability (see the release notes for rationale). "Event" as a concept (and the Event
trait) are now used solely for "observable events". "Buffered events" are now known as "messages" and use the Message
trait. EventWriter
, EventReader
, and Events<E>
, are now known as MessageWriter
, MessageReader
, and Messages<M>
. Types can be both "messages" and "events" by deriving both Message
and Event
, but we expect most types to only be used in one context or the other.
Deprecated Simple Executor #
Bevy has deprecated SimpleExecutor
, one of the SystemExecutor
s in Bevy alongside SingleThreadedExecutor
and MultiThreadedExecutor
(which aren't going anywhere any time soon).
The SimpleExecutor
leaves performance on the table compared to the other executors in favor of simplicity. Specifically, SimpleExecutor
applies any commands a system produces right after it finishes, so every system starts with a clean World
with no pending commands. As a result, the default SimpleExecutor
runs all systems in the order they are added to the schedule, though more ordering constraints can be applied, like before
, after
, chain
, etc. In other executors, these ordering onstraints also inform the executor exactly where to apply commands. For example, if system A
produces commands and runs before
system B
, A
's commands will be applied before B
starts. However, the before
ordering is implicit in SimpleExecutor
if A
is added to the schedule before B
.
The dueling behavior between ordering systems based on when they were added to a schedule as opposed to using ordering constraints is difficult to maintain and can be confusing, especially for new users. But, if you have a strong preference for the existing behavior of SimpleExecutor
, please make an issue and we can discuss your needs.
If you were using SimpleExecutor
, consider upgrading to SingleThreadedExecutor
instead, or try MultiThreadedExecutor
if it fits the schedule. The MultiThreadedExecutor
is great at large schedules and async heavy work, and the SingleThreadedExecutor
is good at smaller schedules or schedules that have fewer parallelizable systems. So what was SimpleExecutor
good at? Not much. That's why we plan to remove it. Removing it will reduce some maintenance and consistency burdens, allowing us to focus on more exciting features!
When migrating, you might uncover bugs where one system depends on another's commands but is not ordered to reflect that. These bugs can be fixed by making those implicit orderings explicit via constraints like before
, after
, chain
, etc. If finding all of those implicit but necessary orderings is unrealistic, chain
can also be used to mimic the behavior of the SimpleExecutor
. Again, if you run into trouble migrating, feel free to open an issue!
ReflectAsset
now uses UntypedAssetId
instead of UntypedHandle
#
Previously, ReflectAsset
methods all required having UntypedHandle
. The only way to get an UntypedHandle
through this API was with ReflectAsset::add
. ReflectAsset::ids
was not very useful in this regard.
Now, all methods have been changed to accept impl Into<UntypedAssetId>
, which matches our regular Assets<T>
API. This means you may need to change how you are calling these methods.
For example, if your code previously looked like:
let my_handle: UntypedHandle;
let my_asset = reflect_asset.get_mut(world, my_handle).unwrap();
You can migrate it to:
let my_handle: UntypedHandle;
let my_asset = reflect_asset.get_mut(world, &my_handle).unwrap();
Replaced TextFont
constructor methods with From
impls #
The TextFont::from_font
and TextFont::from_line_height
constructor methods have been removed in favor of From
trait implementations.
// 0.16
let text_font = TextFont::from_font(font_handle);
let text_font = TextFont::from_line_height(line_height);
// 0.17
let text_font = TextFont::from(font_handle);
let text_font = TextFont::from(line_height);
VectorSpace
implementations #
Previously, implementing VectorSpace
for a type required your type to use or at least interface with f32
. This made implementing VectorSpace
for double-precision types (like DVec3
) less meaningful and useful, requiring lots of casting. VectorSpace
has a new required associated type Scalar
that's bounded by a new trait ScalarField
. bevy_math
implements this trait for f64
and f32
out of the box, and VectorSpace
is now implemented for DVec[N]
types.
If you manually implemented VectorSpace
for any type, you'll need to implement Scalar
for it. If you were working with single-precision floating-point types and you want the exact behavior from before, set it to f32
.
Improve error when using run_system
command with a SystemId
of wrong type #
Added a new error to RegisteredSystemError
to inform of use of run_system
command and its variants with a SystemId
of wrong type.
Extract UI text colors per glyph #
The UI renderer now extracts text colors per glyph and transforms per text section. color: LinearRgba
and translation: Vec2
have been added to ExtractedGlyph
. The transform
field has moved from ExtractedGlyph
and ExtractedUiNode
to ExtractedUiItem
. The rect
field has moved from ExtractedUiNode
to ExtractedUiItem
.
TextureFormat::pixel_size now returns a Result #
The TextureFormat::pixel_size()
method now returns a Result<usize, TextureAccessError>
instead of usize
.
This change was made because not all texture formats have a well-defined pixel size (e.g. compressed formats). Previously, calling this method on such formats could lead to runtime panics. The new return type makes the API safer and more explicit about this possibility.
To migrate your code, you will need to handle the Result
returned by pixel_size()
.
Entities
API changes #
Entities::flush
now also asks for metadata about the flush operation that will be stored for the flushed entities. For the source location, MaybeLocation::caller()
can be used; the tick should be retrieved from the world.
Additionally, flush now gives &mut EntityIdLocation
instead of &mut EntityLocation
access. EntityIdLocation
is an alias for Option<EntityLocation>
. This replaces invalid locations with None
. It is possible for an Entity
id to be allocated/reserved but not yet have a location. This is used in commands for example, and this reality is more transparent with an Option
. This extends to other interfaces: Entities::free
now returns Option<EntityIdLocation>
instead of Option<EntityLocation>
. Entities::get
remains unchanged, but you can access an Entity
's EntityIdLocation
through the new Entities::get_id_location
.
Relationship method set_risky #
The trait Relationship
received a new method, set_risky
. It is used to alter the entity ID of the entity that contains its RelationshipTarget
counterpart. This is needed to leave other data you can store in these components unchanged at operations that reassign the relationship target, for example EntityCommands::add_related
. Previously this could have caused the data to be reset to its default value which may not be what you wanted to happen.
Manually overwriting the component is still possible everywhere the full component is inserted:
#[derive(Component)]
#[relationship(relationship_target = CarOwner)]
struct OwnedCar {
#[relationship]
owner: Entity,
first_owner: Option<Entity>, // None if `owner` is the first one
}
#[derive(Component)]
#[relationship_target(relationship = OwnedCar)]
struct CarOwner(Vec<Entity>);
let mut me_entity_mut = world.entity_mut(me_entity);
// if `car_entity` already contains `OwnedCar`, then the first owner remains unchanged
me_entity_mut.add_one_related::<OwnedCar>(car_entity);
// if `car_entity` already contains `OwnedCar`, then the first owner is overwritten with None here
car_entity_mut.insert(OwnedCar {
owner: me_entity,
first_owner: None // I swear it is not stolen officer!
});
The new method should not be called by user code as that can invalidate the relationship it had or will have.
If you implement Relationship
manually (which is strongly discouraged) then this method needs to overwrite the Entity
used for the relationship.
Unified system state flag #
Now the system have a unified SystemStateFlags
to represent its different states.
If your code previously looked like this:
impl System for MyCustomSystem {
// ...
fn is_send(&self) -> bool {
false
}
fn is_exclusive(&self) -> bool {
true
}
fn has_deferred(&self) -> bool {
false
}
// ....
}
You should migrate it to:
impl System for MyCustomSystem{
// ...
fn flags(&self) -> SystemStateFlags {
// non-send , exclusive , no deferred
SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
}
// ...
}
DynamicBundle
#
In order to reduce the stack size taken up by spawning and inserting large bundles, the way the (mostly internal) trait DynamicBundle
gets called has changed significantly:
// 0.16
trait DynamicBundle {
type Effect;
// hidden in the docs
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect;
}
// 0.17
trait DynamicBundle {
type Effect;
unsafe fn get_components(ptr: MovingPtr<'_, Self>, func: &mut impl FnMut(StorageType, MovingPtr<'_>));
unsafe fn apply_effect(ptr: MovingPtr<'_, MaybeUninit<Self>>, entity: &mut EntityWorldMut);
}
To prevent unnecessary copies to the stack, get_components
now takes a MovingPtr<'_, Self>
instead of self
by value.
MovingPtr<T>
is a safe, typed, box-like pointer that owns the data it points to, but not the underlying memory: that means the owner of a MovingPtr<T>
can freely move parts of the data out and doesn't have to worry about de-allocating memory. Much like Box<T>
, MovingPtr<T>
implements Deref
and DerefMut
for easy access to the stored type, when it's safe to do so. To decompose the value inside of the MovingPtr<T>
into its fields without copying them to the stack, you can use the deconstruct_moving_ptr!
macro to give you MovingPtr<U>
s to each field specified:
struct MySpecialBundle<A: Bundle, B: Bundle> {
a: A,
b: B,
}
let my_ptr: MovingPtr<'_, MySpecialBundle<u32, String>> = ...;
deconstruct_moving_ptr!(my_ptr => { a, b, });
let a_ptr: MovingPtr<'_, u32> = a;
let b_ptr: MovingPtr<'_, String> = b;
Similar to Box::into_inner
, MovingPtr<T>
also has a method MovingPtr::read
for moving the whole value out of the pointer onto the stack:
let a: u32 = a_ptr.read();
let b: String = b_ptr.read();
apply_effect
is a new method that takes the job of the old BundleEffect
trait, and gets called once after get_components
for any B::Effect: !NoBundleEffect
. Since get_components
might have already partially moved out some of the fields of the bundle, apply_effect
takes a MovingPtr<'_, MaybeUninit<Self>>
and implementers must make sure not to create any references to fields that are no longer initialized. Likewise, implementers of get_components
must take care not to move out fields that will be needed in apply_effect
. deconstruct_moving_ptr!
can be used to selectively move out fields while ensuring the rest are forgotten, and remain valid for the subsequent call to apply_effect
. The associated type Effect
remains as a vestigial marker to keep track of whether apply_effect
needs to be called for any B::Effect: !NoBundleEffect
.
Exclusive systems may not be used as observers #
Exclusive systems may no longer be used as observers. This was never sound, as the engine keeps references alive during observer invocation that would be invalidated by &mut World
access, but was accidentally allowed. Instead of &mut World
, use either DeferredWorld
if you do not need structural changes, or Commands
if you do.
EntityClonerBuilder Split #
EntityClonerBuilder
is now generic and has different methods depending on the generic.
To get the wanted one, EntityCloner::build
got split too:
EntityCloner::build_opt_out
to getEntityClonerBuilder<OptOut>
EntityCloner::build_opt_in
to getEntityClonerBuilder<OptIn>
The first is used to clone all components possible and optionally opting out of some. The second is used to only clone components as specified by opting in for them.
// 0.16
let mut builder = EntityCloner.build(&mut world);
builder.allow_all().deny::<ComponentThatShouldNotBeCloned>();
builder.clone_entity(source_entity, target_entity);
let mut builder = EntityCloner.build(&mut world);
builder.deny_all().allow::<ComponentThatShouldBeCloned>();
builder.clone_entity(source_entity, target_entity);
// 0.17
let mut builder = EntityCloner.build_opt_out(&mut world);
builder.deny::<ComponentThatShouldNotBeCloned>();
builder.clone_entity(source_entity, target_entity);
let mut builder = EntityCloner.build_opt_in(&mut world);
builder.allow::<ComponentThatShouldBeCloned>();
builder.clone_entity(source_entity, target_entity);
Still, using EntityClonerBuilder::finish
will return a non-generic EntityCloner
. This change is done because the behavior of the two is too different to share the same struct and same methods and mixing them caused bugs.
The methods of the two builder types are different to 0.16 and to each other now:
Opt-Out variant #
- Still offers variants of the
deny
methods. - No longer offers
allow
methods, you need to be exact with denying components. - Offers now the
insert_mode
method to configure if components are cloned if they already exist at the target. - Required components of denied components are no longer considered. Denying
A
, which requiresB
, does not implyB
alone would not be useful at the target. So if you do not want to cloneB
too, you need to deny it explicitly. This also means there is nowithout_required_components
method anymore as that would be redundant. - It is now the other way around: Denying
A
, which is required byC
, will now also denyC
. This can be bypassed with the newwithout_required_by_components
method.
Opt-In variant #
- Still offers variants of the
allow
methods. - No longer offers
deny
methods, you need to be exact with allowing components. - Offers now
allow_if_new
method variants that only clone this component if the target does not contain it. If it does, required components of it will also not be cloned, except those that are also required by one that is actually cloned. - Still offers the
without_required_components
method.
Common methods #
All other methods EntityClonerBuilder
had in 0.16 are still available for both variants:
with_default_clone_fn
move_components
clone_behavior
variantslinked_cloning
Unified id filtering #
Previously EntityClonerBuilder
supported filtering by 2 types of ids: ComponentId
and TypeId
, the functions taking in IntoIterator
for them. Since now EntityClonerBuilder
supports filtering by BundleId
as well, the number of method variations would become a bit too unwieldy. Instead, all id filtering methods were unified into generic deny_by_ids/allow_by_ids(_if_new)
methods, which allow to filter components by TypeId
, ComponentId
, BundleId
and their IntoIterator
variations.
Other affected APIs #
0.16 | 0.17 |
---|---|
EntityWorldMut::clone_with | EntityWorldMut::clone_with_opt_out EntityWorldMut::clone_with_opt_in |
EntityWorldMut::clone_and_spawn_with | EntityWorldMut::clone_and_spawn_with_opt_out EntityWorldMut::clone_and_spawn_with_opt_in |
EntityCommands::clone_with | EntityCommands::clone_with_opt_out EntityCommands::clone_with_opt_in |
EntityCommands::clone_and_spawn_with | EntityCommands::clone_and_spawn_with_opt_out EntityCommands::clone_and_spawn_with_opt_in |
entity_command::clone_with | entity_command::clone_with_opt_out entity_command::clone_with_opt_in |
Changes to type registration for reflection #
Calling .register_type
has long been a nuisance for Bevy users: both library authors and end users. This step was previously required in order to register reflected type information in the TypeRegistry
.
In Bevy 0.17 however, types which implement Reflect
are now automatically registered, with the help of some compiler magic. You should be able to remove almost all of your register_type
calls. This comes with a few caveats however:
- Automatic type registration is gated by feature flags.
- There are two approaches to do this: one has incomplete platform support, while the other relies on a specific project structure.
- Generic types are not automatically registered, and must still be manually registered.
In order for Bevy to automatically register your types, you need to turn on the reflect_auto_register
feature, or the fallback reflect_auto_register_static
. The reflect_auto_register
feature is part of Bevy's default features, and can be overridden by the reflect_auto_register_static
feature flag. Be aware that the reflect_auto_register_static
feature comes with some caveats for project structure: check the docs for load_type_registrations! and follow the auto_register_static
example.
We recommend that you:
- Enable
reflect_auto_register
in your application code, CI and in examples/tests. You can enablebevy
features for tests only by adding a matching copy ofbevy
todev-dependencies
with the needed features enabled. - Do not enable the
reflect_auto_register
feature or the fallbackreflect_auto_register_static
in your library code. - As a library author, you can safely remove all non-generic
.register_type
calls. - As a user, if you run into an unregistered generic type with the correct feature enabled, file a bug with the project that defined the offending type, and workaround it by calling
.register_type
manually. - If you are on an unsupported platform but need reflection support, try the
reflect_autoregister_static
feature, and consider working upstream to add support for your platform ininventory
. As a last resort, you can still manually register all of the needed types in your application code.
Composable Specialization #
The existing pipeline specialization APIs (SpecializedRenderPipeline
etc.) have been replaced with a single Specializer
trait and Variants
collection:
pub trait Specializer<T: Specializable>: Send + Sync + 'static {
type Key: SpecializerKey;
fn specialize(
&self,
key: Self::Key,
descriptor: &mut T::Descriptor,
) -> Result<Canonical<Self::Key>, BevyError>;
}
pub struct Variants<T: Specializable, S: Specializer<T>>{ ... };
For more info on specialization, see the docs for bevy_render::render_resources::Specializer
Mutation and Base Descriptors #
The main difference between the old and new trait is that instead of producing a pipeline descriptor, Specializer
s mutate existing descriptors based on a key. As such, Variants::new
takes in a "base descriptor" to act as the template from which the specializer creates pipeline variants.
When migrating, the "static" parts of the pipeline (that don't depend on the key) should become part of the base descriptor, while the specializer itself should only change the parts demanded by the key. In the full example below, instead of creating the entire pipeline descriptor the specializer only changes the msaa sample count and the bind group layout.
Composing Specializers #
Specializer
s can also be composed with the included derive macro to combine their effects! This is a great way to encapsulate and reuse specialization logic, though the rest of this guide will focus on migrating "standalone" specializers.
pub struct MsaaSpecializer {...}
impl Specialize<RenderPipeline> for MsaaSpecializer {...}
pub struct MeshLayoutSpecializer {...}
impl Specialize<RenderPipeline> for MeshLayoutSpecializer {...}
#[derive(Specializer)]
#[specialize(RenderPipeline)]
pub struct MySpecializer {
msaa: MsaaSpecializer,
mesh_layout: MeshLayoutSpecializer,
}
Misc Changes #
The analogue of SpecializedRenderPipelines
, Variants
, is no longer a Bevy Resource
. Instead, the cache should be stored in a user-created Resource
(shown below) or even in a Component
depending on the use case.
Full Migration Example #
Before:
#[derive(Resource)]
pub struct MyPipeline {
layout: BindGroupLayout,
layout_msaa: BindGroupLayout,
vertex: Handle<Shader>,
fragment: Handle<Shader>,
}
// before
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct MyPipelineKey {
msaa: Msaa,
}
impl FromWorld for MyPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let asset_server = world.resource::<AssetServer>();
let layout = render_device.create_bind_group_layout(...);
let layout_msaa = render_device.create_bind_group_layout(...);
let vertex = asset_server.load("vertex.wgsl");
let fragment = asset_server.load("fragment.wgsl");
Self {
layout,
layout_msaa,
vertex,
fragment,
}
}
}
impl SpecializedRenderPipeline for MyPipeline {
type Key = MyPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
RenderPipelineDescriptor {
label: Some("my_pipeline".into()),
layout: vec![
if key.msaa.samples() > 1 {
self.layout_msaa.clone()
} else {
self.layout.clone()
}
],
vertex: VertexState {
shader: self.vertex.clone(),
..default()
},
multisample: MultisampleState {
count: key.msaa.samples(),
..default()
},
fragment: Some(FragmentState {
shader: self.fragment.clone(),
targets: vec![Some(ColorTargetState {
format: TextureFormat::Rgba8Unorm,
blend: None,
write_mask: ColorWrites::all(),
})],
..default()
}),
..default()
},
}
}
render_app
.init_resource::<MyPipeline>();
.init_resource::<SpecializedRenderPipelines<MySpecializer>>();
After:
#[derive(Resource)]
pub struct MyPipeline {
// the base_descriptor and specializer each hold onto the static
// wgpu resources (layout, shader handles), so we don't need
// explicit fields for them here. However, real-world cases
// may still need to expose them as fields to create bind groups
// from, for example.
variants: Variants<RenderPipeline, MySpecializer>,
}
pub struct MySpecializer {
layout: BindGroupLayout,
layout_msaa: BindGroupLayout,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, SpecializerKey)]
pub struct MyPipelineKey {
msaa: Msaa,
}
impl FromWorld for MyPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let asset_server = world.resource::<AssetServer>();
let layout = render_device.create_bind_group_layout(...);
let layout_msaa = render_device.create_bind_group_layout(...);
let vertex = asset_server.load("vertex.wgsl");
let fragment = asset_server.load("fragment.wgsl");
let base_descriptor = RenderPipelineDescriptor {
label: Some("my_pipeline".into()),
vertex: VertexState {
shader: vertex.clone(),
..default()
},
fragment: Some(FragmentState {
shader: fragment.clone(),
..default()
}),
..default()
},
let variants = Variants::new(
MySpecializer {
layout: layout.clone(),
layout_msaa: layout_msaa.clone(),
},
base_descriptor,
);
Self { variants }
}
}
impl Specializer<RenderPipeline> for MySpecializer {
type Key = MyKey;
fn specialize(
&self,
key: Self::Key,
descriptor: &mut RenderPipeline,
) -> Result<Canonical<Self::Key>, BevyError> {
descriptor.multisample.count = key.msaa.samples();
let layout = if key.msaa.samples() > 1 {
self.layout_msaa.clone()
} else {
self.layout.clone()
};
descriptor.set_layout(0, layout);
Ok(key)
}
}
render_app.init_resource::<MyPipeline>();
Entry
enum is now ComponentEntry
#
The Entry
enum in bevy::ecs::world
has been renamed to ComponentEntry
, to avoid name clashes with hash_map
, hash_table
and hash_set
Entry
types.
Correspondingly, the nested OccupiedEntry
and VacantEntry
enums have been renamed to OccupiedComponentEntry
and VacantComponentEntry
.
State-scoped entities are now always enabled implicitly #
State scoped entities is now always enabled, and as a consequence, app.enable_state_scoped_entities::<State>()
is no longer needed. It has been marked as deprecated and does nothing when called.
The attribute #[states(scoped_entities)]
has been removed. You can safely remove it from your code without replacement.
Required components refactor #
The required components feature has been reworked to be more consistent around the priority of the required components and fix some soundness issues. In particular:
- the priority of required components will now always follow a priority given by the depth-first/preorder traversal of the dependency tree. This was mostly the case before with a couple of exceptions that we are now fixing:
- when deriving the
Component
trait, sometimes required components at depth 1 had priority over components at depth 2 even if they came after in the depth-first ordering; - registering runtime required components followed a breadth-first ordering and used the wrong inheritance depth for derived required components.
- when deriving the
- uses of the inheritance depth were removed from the
RequiredComponent
struct and from the methods for registering runtime required components, as it's not unused for the depth-first ordering; Component::register_required_components
,RequiredComponents::register
andRequiredComponents::register_by_id
are nowunsafe
;RequiredComponentConstructor
's only field is now private for safety reasons.
The Component::register_required_components
method has also changed signature. It now takes the ComponentId
of the component currently being registered and a single other parameter RequiredComponentsRegistrator
which combines the old components
and required_components
parameters, since exposing both of them was unsound. As previously discussed the inheritance_depth
is now useless and has been removed, while the recursion_check_stack
has been moved into ComponentsRegistrator
and will be handled automatically.
view_transformations.wgsl deprecated in favor of view.wgsl #
All functions in view_transformations.wgsl have been replaced and deprecated.
To migrate, a straight-forward copy-paste inlining of the deprecated function's new body suffices, as they all now call the new api internally.
For example, if you had before:
#import bevy_pbr::view_transformations
let world_pos = view_transformations::position_view_to_world(view_pos);
Now it would be:
#import bevy_render::view
let world_pos = view::position_view_to_world(view_pos, view_bindings::view.world_from_view);
This was done to make it possible to pass in custom view bindings, and allow code reuse.
view_transformations.wgsl
will be deleted in 0.18.
Replace Gilrs
, AccessKitAdapters
, and WinitWindows
non-send resources #
We are working to move !Send
data out of the ECS, in order to simplify internal implementation, reduce the risk of soundness problems and unblock features such as resources-as-entities and improved scheduling.
For now, the API for user-provided NonSend
types is unchanged, but we are considering forcing all users to migrate to a solution similar to the one discussed below.
First-party NonSend
Resources Replaced #
Internally, we have replaced the following resources:
Gilrs
- For wasm32 only, other platforms are unchanged - Replaced withbevy_gilrs::GILRS
WinitWindows
- Replaced withbevy_winit::WINIT_WINDOWS
AccessKitAdapters
- Replaced withbevy_winit::ACCESS_KIT_ADAPTERS
Each of these are now using thread_local
s to store the data and are temporary solutions to storing !Send
data. Even though thread_local
s are thread safe, they should not be accessed from other threads. If they are accessed from other threads, the data will be uninitialized in each non-main thread, which isn't very useful.
Here is an example of how the data can now be accessed. This example will use WINIT_WINDOWS
as an example, but the same technique can be applied to the others:
Immutable Access #
use bevy_winit::WINIT_WINDOWS;
...
WINIT_WINDOWS.with_borrow(|winit_windows| {
// do things with `winit_windows`
});
Mutable Access #
use bevy_winit::WINIT_WINDOWS;
...
WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
// do things with `winit_windows`
});
If a borrow is attempted while the data is borrowed elsewhere, the method will panic.
NonSend Systems #
The use of a NonSend
or NonSendMut
resource in a system would force the system to execute on the main thread. However, when using the new thread_local
pattern, we still need to prevent systems from running on non-main threads. To do this, you can now use bevy_ecs::system::NonSendMarker
as a system parameter:
use bevy_ecs::system::NonSendMarker;
fn my_system(
_non_send_marker: NonSendMarker,
) {
ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
// do things with adapters
});
}
To prevent a panic, if any of the !Send
resource replacements mentioned in this document are used in a system, the system should always be marked as !Send
with bevy_ecs::system::NonSendMarker
.
ComponentsRegistrator
no longer implements DerefMut
#
ComponentsRegistrator
no longer implements DerefMut<Target = Components>
, meaning you won't be able to get a &mut Components
from it. The only two methods on Components
that took &mut self
(any_queued_mut
and num_queued_mut
) have been reimplemented on ComponentsRegistrator
, meaning you won't need to migrate them. Other usages of &mut Components
were unsupported.
Query items can borrow from query state #
The QueryData::Item
associated type and the QueryItem
and ROQueryItem
type aliases now have an additional lifetime parameter corresponding to the 's
lifetime in Query
. The QueryData::fetch()
and QueryFilter::filter_fetch()
methods have a new parameter taking a &'s WorldQuery::State
. Manual implementations of WorldQuery
and QueryData
will need to update the method signatures to include the new lifetimes. Other uses of the types will need to be updated to include a lifetime parameter, although it can usually be passed as '_
. In particular, ROQueryItem
is used when implementing RenderCommand
.
Before:
// 0.16
fn render<'w>(
item: &P,
view: ROQueryItem<'w, Self::ViewQuery>,
entity: Option<ROQueryItem<'w, Self::ItemQuery>>,
param: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult;
// 0.17
fn render<'w>(
item: &P,
view: ROQueryItem<'w, '_, Self::ViewQuery>,
entity: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
param: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult;
Methods on QueryState
that take &mut self
may now result in conflicting borrows if the query items capture the lifetime of the mutable reference. This affects get()
, iter()
, and others. To fix the errors, first call QueryState::update_archetypes()
, and then replace a call state.foo(world, param)
with state.query_manual(world).foo_inner(param)
. Alternately, you may be able to restructure the code to call state.query(world)
once and then make multiple calls using the Query
.
let mut state: QueryState<_, _> = ...;
// 0.16
let d1 = state.get(world, e1);
let d2 = state.get(world, e2); // Error: cannot borrow `state` as mutable more than once at a time
println!("{d1:?}");
println!("{d2:?}");
// 0.17
state.update_archetypes(world);
let d1 = state.get_manual(world, e1);
let d2 = state.get_manual(world, e2);
// OR
state.update_archetypes(world);
let d1 = state.query_manual(world).get_inner(e1);
let d2 = state.query_manual(world).get_inner(e2);
// OR
let query = state.query(world);
let d1 = query.get_inner(e1);
let d1 = query.get_inner(e2);
println!("{d1:?}");
println!("{d2:?}");
Non-generic Access
#
Now that archetype_component_id
has been removed, Access
, AccessFilters
, FilteredAccess
, and FilteredAccessSet
were only ever parameterized by ComponentId
. To simplify use of those types, the generic parameter has been removed. Remove the <Component>
generic from any use of those types.
// 0.16
fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) {}
// 0.17
fn update_component_access(state: &Self::State, access: &mut FilteredAccess) {}
Component lifecycle reorganization #
To improve documentation, discoverability and internal organization, we've gathered all of the component lifecycle-related code we could and moved it into a dedicated lifecycle
module.
The lifecycle / observer types (Add
, Insert
, Remove
, Replace
, Despawn
) have been moved from the bevy_ecs::world
to bevy_ecs::lifecycle
.
The same move has been done for the more internal (but public) ComponentId
constants: ADD
, INSERT
, REMOVE
, REPLACE
, DESPAWN
.
The code for hooks (HookContext
, ComponentHook
, ComponentHooks
) has been extracted from the very long bevy_ecs::components
module, and now lives in the bevy_ecs::lifecycle
module.
The RemovedComponents
SystemParam
, along with the public RemovedIter
, RemovedIterWithId
and RemovedComponentEvents
have also been moved into this module as they serve a similar role. All references to bevy_ecs::removal_detection
can be replaced with bevy_ecs::lifecycle
.
Polylines and Polygons are no longer const-generic #
Polyline2d
, Polyline3d
, Polygon
, and ConvexPolygon
are no longer const-generic and now implement Meshable
for direct mesh generation. These types now use Vec
instead of arrays internally and will therefore allocate and are no longer no_std
compatible.
If you need these types to be no_std
and/or const-generic, please file an issue explaining your use case and we can consider creating fixed side-count polygon/polyline variants.
Changes to the default error handler mechanism #
We've improved the implementation of Bevy's default error handling. The performance overhead has been reduced, and as a result it is always enabled. The configurable_error_handler
feature no longer exists: simply remove it from your list of enabled features.
Additionally, worlds can now have different default error handlers, so there is no longer a truly global handler.
Replace uses of GLOBAL_ERROR_HANDLER
with App::set_error_handler(handler)
. For worlds that do not directly belong to an App
/SubApp
, insert the DefaultErrorHandler(handler)
resource.
Removed Deprecated Batch Spawning Methods #
The following deprecated functions have been removed:
Commands::insert_or_spawn_batch
World::insert_or_spawn_batch
World::insert_or_spawn_batch_with_caller
These functions, when used incorrectly, could cause major performance problems and generally violated the privacy of the ECS internals in ways that the Bevy maintainers were not prepared to support long-term. They were deprecated in 0.16 due to their potential for misuse, as the retained render world removed Bevy's own uses of these methods.
Instead of allocating entities with specific identifiers, consider one of the following:
Instead of despawning entities, insert the
Disabled
component, and instead of respawning them at particular ids, usetry_insert_batch
orinsert_batch
and removeDisabled
.Instead of giving special meaning to an entity id, simply use
spawn_batch
and ensure entity references are valid when despawning.Use your own stable identifier and a map to
Entity
identifiers, with the help of theEntityMapper
trait.
Stop storing access in systems #
Bevy used to store component access in all systems, even though it was only used for top-level systems in schedules. To reduce memory usage, the component access is now stored in the schedule instead.
The trait methods System::component_access
and System::component_access_set
have been removed. Instead, the access is returned from System::initialize
. If you were implementing System
manually, the initialize
method should return the access instead of storing it. If you were calling component_access
or component_access_set
on a system that you initialized yourself, you will need to store the access yourself.
let system = IntoSystem::into_system(your_system);
// 0.16
system.initialize(&mut world);
let access = system.component_access();
// 0.17
let component_access_set = system.initialize(&mut world);
let access = component_access_set.combined_access();
SystemMeta
no longer stores FilteredAccessSet<ComponentId>
. It is instead passed as a separate parameter when initializing a SystemParam
.
To better share logic between SystemParam
and SystemParamBuilder
, SystemParam::init_state
has been split into init_state
, which creates the state value, and init_access
, which calculates the access. SystemParamBuilder::build
now only creates the state, and SystemParam::init_access
will be called to calculate the access for built parameters.
If you were implementing SystemParam
manually, you will need to separate the logic into two methods and change any uses of system_meta.component_access_set(_mut)
to the new component_access_set
parameter. Note that init_state
no longer has access to SystemMeta
or component_access_set
, and init_access
only has &state
, so the state can no longer depend on the system.
If you were calling init_state
manually, you will need to call init_access
afterwards.
// 0.16
let param_state = P::init_state(world, &mut meta);
// 0.17
let param_state = P::init_state(world);
let mut component_access_set = FilteredAccessSet::new();
P::init_access(¶m_state, &mut meta, &mut component_access_set, world);
New zstd
backend #
A more performant zstd backend has been added for texture decompression. To enable it, disable default-features and enable feature "zstd_c". If you have default-features disabled and use functionality that requires zstd decompression ("tonemapping_luts" or "ktx2"), you must choose a zstd implementation with one of the following feature flags: "zstd_c" (faster) or "zstd_rust" (safer)
Migration Guide #
If you previously used the zstd
feature explicitly, it has been renamed to zstd_rust
:
# 0.16
[dependencies]
bevy = { version = "0.16", features = ["zstd"] }
# 0.17 - Use the safe Rust implementation:
[dependencies]
bevy = { version = "0.17", features = ["zstd_rust"] }
# 0.17 - Or use the faster C implementation:
[dependencies]
bevy = { version = "0.17", features = ["zstd_c"] }
If you have default-features disabled and use functionality that requires zstd decompression ("tonemapping_luts" or "ktx2"), you must choose a zstd implementation with one of the following feature flags: "zstd_c" (faster) or "zstd_rust" (safer).
Remove ArchetypeComponentId
#
Scheduling no longer uses archetype_component_access
or ArchetypeComponentId
. To reduce memory usage and simplify the implementation, all uses of them have been removed. Since we no longer need to update access before a system runs, Query
now updates it state when the system runs instead of ahead of time.
SystemParam::validate_param
now takes &mut Self::State
instead of &Self::State
so that queries can update their state during validation.
The trait methods System::update_archetype_component_access
and SystemParam::new_archetype
have been removed. They are no longer necessary, so calls to them can be removed. If you were implementing the traits manually, move any logic from those methods into System::validate_param_unsafe
, System::run_unsafe
, SystemParam::validate_param
, or SystemParam::get_param
, which can no longer rely on update_archetype_component_access
being called first.
The following methods on SystemState
have been deprecated:
update_archetypes
- Remove calls, as they no longer do anythingupdate_archetypes_unsafe_world_cell
- Remove calls, as they no longer do anythingget_manual
- Replace withget
, as there is no longer a differenceget_manual_mut
- Replace withget_mut
, as there is no longer a differenceget_unchecked_mut
- Replace withget_unchecked
, as there is no longer a difference
RenderTarget error handling #
NormalizedRenderTargetExt::get_render_target_info
now returns a Result
, with the Err
variant indicating which render target (image, window, etc) failed to load its metadata.
This should mostly be treated as a hard error, since it indicates the rendering state of the app is broken.
Renamed BRP methods #
Most Bevy Remote Protocol methods have been renamed to be more explicit. The word destroy
has also been replaced with despawn
to match the rest of the engine.
Old | New |
---|---|
bevy/query | world.query |
bevy/spawn | world.spawn_entity |
bevy/destroy | world.despawn_entity |
bevy/reparent | world.reparent_entities |
bevy/get | world.get_components |
bevy/insert | world.insert_components |
bevy/remove | world.remove_components |
bevy/list | world.list_components |
bevy/mutate | world.mutate_components |
bevy/get+watch | world.get_components+watch |
bevy/list+watch | world.list_components+watch |
bevy/get_resource | world.get_resources |
bevy/insert_resource | world.insert_resources |
bevy/remove_resource | world.remove_resources |
bevy/list_resources | world.list_resources |
bevy/mutate_resource | world.mutate_resources |
registry/schema | registry.schema |
TAA is no longer experimental #
TAA is no longer experimental.
TemporalAntiAliasPlugin
no longer needs to be added to your app to use TAA. It is now part of DefaultPlugins
, via AntiAliasPlugin
.
As part of this change, the import paths for TemporalAntiAliasNode
, TemporalAntiAliasing
and TemporalAntiAliasPlugin
have changed from bevy::anti_alias::experimental::taa
to bevy::anti_alias::taa
: if you want to add TemporalAntiAliasing
to a Camera, you can now find it at bevy::anti_alias::taa::TemporalAntiAliasing
.
TemporalAntiAliasing
now uses MipBias
as a required component in the main world, instead of overriding it manually in the render world.
TextShadow
has been moved to bevy::ui::widget::text
#
TextShadow
has been moved to bevy::ui::widget::text
.
Remove Bundle::register_required_components
#
This method was effectively dead-code as it was never used by the ECS to compute required components, hence it was removed. if you were overriding its implementation you can just remove it, as it never did anything. If you were using it in any other way, please open an issue.
Observer / Event API Changes #
The observer "trigger" API has changed a bit to improve clarity and type-safety.
// Old
commands.add_observer(|trigger: Trigger<OnAdd, Player>| {
info!("Spawned player {}", trigger.target());
});
// New
commands.add_observer(|add: On<Add, Player>| {
info!("Spawned player {}", add.entity);
});
The Trigger
type used inside observers has been renamed to On
to encourage developers to think about this parameter as the event. We also recommend naming the variable after the event type (ex: add
).
To reduce repetition and improve readability, the OnAdd
, OnInsert
, OnReplace
, OnRemove
, and OnDespawn
observer events have also been renamed to Add
, Insert
, Replace
, Remove
, and Despawn
respectively. In rare cases where the Add
event conflicts with the std::ops::Add
trait, you may need to disambiguate, for example by using ops::Add
for the trait. We encourage removing the "On" from custom events named OnX
.
Types implementing Event
can no longer be triggered from _all contexts. By default Event
is a "global" / "target-less" event.
Events that target an entity should now derive EntityEvent
, and they will now store the target entity on the event type, which is accessible via EntityEvent::event_target
. Additionally, world.trigger_targets
has been removed in favor of a single world.trigger
API:
// Old
#[derive(Event)]
struct Explode;
world.trigger_targets(Explode, entity);
// New
#[derive(EntityEvent)]
struct Explode {
entity: Entity
}
world.trigger(Explode { entity });
Triggering an entity event for multiple entities now requires multiple calls to trigger
:
// Old
world.trigger_targets(Explode, [e1, e2]);
// New - Variant 1
world.trigger(Explode { entity: e1 });
world.trigger(Explode { entity: e2 });
// New - Variant 2
for entity in [e1, e2] {
world.trigger(Explode { entity });
}
On::target()
no longer exists for all event types. Instead, you should prefer accessing the "target entity" field on the events that target entities:
// Old
commands.add_observer(|trigger: Trigger<Explode>| {
info!("{} exploded!", trigger.target());
});
// New
commands.add_observer(|explode: On<Explode>| {
info!("{} exploded!", explode.entity);
// you can also use `EntityEvent::event_target`, but we encourage
// using direct field access when possible, for better documentation and clarity.
info!("{} exploded!", explode.event_target());
});
"Propagation functions", such as On::propagate
are now only available on On<E>
when E: EntityEvent<Trigger = PropagateEntityTrigger>
.
Enabling propagation is now down using, which defaults to ChildOf
propagation:
#[derive(EntityEvent)]
#[entity_event(propagate)]
struct Click {
entity: Entity,
}
Setting a custom propagation Traversal
implementation now uses propagate
instead of traversal
:
// OLd
#[derive(Event)]
#[event(traversal = &'static ChildOf)]
struct Click;
// New
#[derive(EntityEvent)]
#[entity_event(propagate = &'static ChildOf)]
struct Click {
entity: Entity,
}
Animation events (used in AnimationPlayer
) must now derive AnimationEvent
. Accessing the animation player entity is now done via the trigger()
.
// Old
#[derive(Event)]
struct SayMessage(String);
animation.add_event(0.2, SayMessage("hello".to_string()));
world.entity_mut(animation_player).observe(|trigger: Trigger<SayMessage>| {
println!("played on", trigger.target());
})
// New
#[derive(AnimationEvent)]
struct SayMessage(String);
animation.add_event(0.2, SayMessage("hello".to_string()));
world.entity_mut(animation_player).observe(|say_message: On<SayMessage>| {
println!("played on", say_message.trigger().animation_player);
})
For "component lifecycle events", accessing all of the components that triggered the event has changed:
// Old
commands.add_observer(|trigger: Trigger<OnAdd, Player>| {
info!("{}", trigger.components());
});
// New
commands.add_observer(|add: On<Add, Player>| {
info!("{}", add.trigger().components);
});
Manual Entity Creation and Representation #
An entity is made of two parts: and index and a generation. Both have changes:
Index #
Entity
no longer stores its index as a plain u32
but as the new EntityRow
, which wraps a NonMaxU32
. Previously, Entity::index
could be u32::MAX
, but that is no longer a valid index. As a result, Entity::from_raw
now takes EntityRow
as a parameter instead of u32
. EntityRow
can be constructed via EntityRow::new
, which takes a NonMaxU32
. If you don't want to add nonmax as a dependency, use Entity::from_raw_u32
which is identical to the previous Entity::from_raw
, except that it now returns Option
where the result is None
if u32::MAX
is passed.
Bevy made this change because it puts a niche in the EntityRow
type which makes Option<EntityRow>
half the size of Option<u32>
. This is used internally to open up performance improvements to the ECS.
Although you probably shouldn't be making entities manually, it is sometimes useful to do so for tests. To migrate tests, use:
- let entity = Entity::from_raw(1);
+ let entity = Entity::from_raw_u32(1).unwrap();
If you are creating entities manually in production, don't do that! Use Entities::alloc
instead. But if you must create one manually, either reuse a EntityRow
you know to be valid by using Entity::from_raw
and Entity::row
, or handle the error case of None
returning from Entity::from_raw_u32(my_index)
.
Generation #
An entity's generation is no longer a NonZeroU32
. Instead, it is an EntityGeneration
. Internally, this stores a u32
, but that might change later.
Working with the generation directly has never been recommended, but it is sometimes useful to do so in tests. To create a generation do EntityGeneration::FIRST.after_versions(expected_generation)
. To use this in tests, do assert_eq!(entity.generation(), EntityGeneration::FIRST.after_versions(expected_generation))
.
Removed Interfaces #
The identifier
module and all its contents have been removed. These features have been slimmed down and rolled into Entity
.
This means that where Result<T, IdentifierError>
was returned, Option<T>
is now returned.
Functionality #
It is well documented that both the bit format, serialization, and Ord
implementations for Entity
are subject to change between versions. Those have all changed in this version.
For entity ordering, the order still prioritizes an entity's generation, but after that, it now considers higher index entities less than lower index entities.
The changes to serialization and the bit format are directly related. Effectively, this means that all serialized and transmuted entities will not work as expected and may crash. To migrate, invert the lower 32 bits of the 64 representation of the entity, and subtract 1 from the upper bits. Again, this is still subject to change, and serialized scenes may break between versions.
Length Representation #
Because the maximum index of an entity is now NonZeroU32::MAX
, the maximum number of entities (and length of unique entity row collections) is u32::MAX
. As a result, a lot of APIs that returned usize
have been changed to u32
.
These include:
Archetype::len
Table::entity_count
Other kinds of entity rows #
Since the EntityRow
is a NonMaxU32
, TableRow
and ArchetypeRow
have been given the same treatment. They now wrap a NonMaxU32
, allowing more performance optimizations.
Additionally, they have been given new, standardized interfaces:
fn new(NonMaxU32)
fn index(self) -> usize
fn index_u32(self) -> u32
The other interfaces for these types have been removed. Although it's not usually recommended to be creating these types manually, if you run into any issues migrating here, please open an issue. If all else fails, TableRow
and ArchetypeRow
are repr(transparent)
, allowing careful transmutations.
RenderTargetInfo's default scale_factor
has been changed to 1.
#
The default for RenderTargetInfo
's scale_factor
field is now 1.
.
RenderGraphApp
renamed to RenderGraphExt
#
RenderGraphApp
has been renamed to RenderGraphExt
. Rename this for cases where you are explicitly importing this trait.
Window Resolution Constructors #
The WindowResolution
type stores the width and height as u32
. Previously, this type could only be constructed with f32
, which were immediately converted to u32
. Now, WindowResolution
can be constructed with u32
s directly, and the pointless f32
conversion has been removed.
WindowResolution::new(1920.0, 1080.0)
// becomes
WindowResolution::new(1920, 1080)
WindowResolution::new(some_uvec2.x as f32, some_uvec2.y as f32)
// becomes
WindowResolution::from(some_uvec2)
window_resolution: (1920.0, 1080.0).into()
// becomes
window_resolution: (1920, 1080).into()
Specialized UI transform #
Bevy UI now uses specialized 2D UI transform components UiTransform
and UiGlobalTransform
in place of Transform
and GlobalTransform
.
UiTransform
is a 2D-only equivalent of Transform with a responsive translation in Val
s. UiGlobalTransform
newtypes Affine2
and is updated in ui_layout_system
.
Node
now requires UiTransform
instead of Transform
. UiTransform
requires UiGlobalTransform
.
The UiTransform
equivalent of the Transform
:
Transform {
translation: Vec3 { x, y, z },
rotation:Quat::from_rotation_z(radians),
scale,
}
is
UiTransform {
translation: Val2::px(x, y),
rotation: Rot2::from_rotation(radians),
scale: scale.xy(),
}
In previous versions of Bevy ui_layout_system
would overwrite UI node's Transform::translation
each frame. UiTransform
s aren't overwritten and there is no longer any need for systems that cache and rewrite the transform for translated UI elements.
If you were relying on the z
value of the GlobalTransform
, this can be derived from UiStack
instead.
CheckChangeTicks
parameter in System::check_change_tick
#
System::check_change_tick
took a Tick
parameter to update internal ticks. This is needed to keep queried components filtered by their change tick reliably not be matched if their last change or add and the system's last run was very long ago. This is also needed for similar methods involving the system's ticks for the same reason.
This parameter is now a CheckChangeTicks
type that can be passed to the now-public Tick::check_tick
in case you maintain these yourself in manual System
implementations.
If you need a CheckChangeTicks
value, for example because you call one of the above methods manually, you can observe it. Here is an example where it is used on a schedule stored in a resource, which will pass it on to the System::check_change_tick
of its systems.
use bevy_ecs::prelude::*;
use bevy_ecs::component::CheckChangeTicks;
#[derive(Resource)]
struct CustomSchedule(Schedule);
let mut world = World::new();
world.add_observer(|check: On<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
schedule.0.check_change_ticks(*check);
});
The observers are triggered by World::check_change_ticks
which every schedule calls before running. This method also returns an Option<CheckChangeTicks>
which is Some
in case it was time to check the ticks.
Transform and GlobalTransform::compute_matrix rename #
GlobalTransform::compute_matrix
has been renamed to GlobalTransform::to_matrix
because it does not compute anything, it simply moves data into a different type. Transform::compute_matrix
has been renamed to Transform::to_matrix
for consistency with GlobalTransform
.
Compressed image saver feature #
The compressed image saver has been gated behind its own dedicated feature flag now. If you were using it, you need to enable the "compressed_image_saver" feature.
Change filters container of LogDiagnosticsState
to HashSet
#
LogDiagnosticsState
's filter container and the argument of LogDiagnosticPlugin::filtered
is now a HashSet
rather than a Vec
.
FULLSCREEN_SHADER_HANDLE
replaced with FullscreenShader
#
FULLSCREEN_SHADER_HANDLE
and fullscreen_shader_vertex_state
have been replaced by the FullscreenShader
resource. Users of either of these will need to call FullscreenShader::shader
or FullscreenShader::to_vertex_state
respectively. You may need to clone FullscreenShader
out of the render world to store an instance that you can use later (e.g., if you are attempting to use the fullscreen shader inside a SpecializedRenderPipeline
implementation).
For example, if your previous code looked like this:
struct MyPipeline {
some_bind_group: BindGroupLayout,
}
impl FromWorld for MyPipeline {
fn from_world(render_world: &mut World) -> Self {
let some_bind_group = /* ... RenderDevice stuff */;
Self {
some_bind_group,
}
}
}
impl SpecializedRenderPipeline for MyPipeline {
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
RenderPipelineDescriptor {
vertex: fullscreen_shader_vertex_state(),
// ... other stuff
}
}
}
You can migrate your code to:
struct MyPipeline {
some_bind_group: BindGroupLayout,
fullscreen_shader: FullscreenShader,
}
impl FromWorld for MyPipeline {
fn from_world(render_world: &mut World) -> Self {
let some_bind_group = /* ... RenderDevice stuff */;
Self {
some_bind_group,
fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
}
}
}
impl SpecializedRenderPipeline for MyPipeline {
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
RenderPipelineDescriptor {
vertex: self.fullscreen_shader.to_vertex_state(),
// ... other stuff
}
}
}
This is just one example. Pipelines may be initialized in different ways, but the primary strategy is clone out the FullscreenShader
resource from the render world, and call to_vertex_state
to use it as the vertex shader.
Smooth normals implementation changed #
In Bevy 0.16, Mesh
smooth normal calculation used a triangle area-weighted algorithm. In 0.17, the area-weighted algorithm was moved to separate methods, the default implementation was switched to a corner angle-weighted algorithm, and Mesh::compute_custom_smooth_normals
was added for other cases.
The angle-weighted method is more suitable for growing or shrinking a mesh along its vertex normals, such as when generating an outline mesh. It also results in more expected lighting behavior for some meshes. In most cases, the difference will be small and no change is needed. However, the new default is somewhat slower, and does not always produce the result desired by an artist. If you preferred the lighting in 0.16, or have a significant performance regression, or needed area-weighted normals for any other reason, you can switch to the new dedicated area-weighted methods.
// Only if the new smooth normals algorithm is unsatisfactory:
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, default())
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
- .with_computed_smooth_normals();
+ .with_computed_area_weighted_normals;
- mesh.compute_smooth_normals();
+ mesh.compute_area_weighted_normals();
As part of this change, the helper functions face_normal
and face_area_normal
, were renamed to triangle_normal
and triangle_area_normal
respectively to better reflect the fact that they do not take an entire geometric face into account.
- use bevy::render::mesh::face_normal;
+ use bevy::render::mesh::triangle_normal;
- let normal = face_normal(a, b, c);
+ let normal = triangle_normal(a, b, c);
- use bevy::render::mesh::face_area_normal;
+ use bevy::render::mesh::triangle_area_normal;
- let normal = face_area_normal(a, b, c);
+ let normal = triangle_area_normal(a, b, c);
SpawnableList
#
In order to reduce the stack size taken up by spawning and inserting large bundles, SpawnableList
now takes a MovingPtr<T>
as the self-type for the spawn
function:
// 0.16
fn spawn(self, world: &mut World, entity: Entity);
let list = Spawn(my_bundle);
list.spawn(world, entity);
// 0.17
fn spawn(self: MovingPtr<'_, Self>, world: &mut World, entity: Entity);
let list = Spawn(my_bundle);
move_as_ptr!(list);
SpawnableList::spawn(list, world, entity);
This change also means that SpawnableList
must now also be Sized
!
MovingPtr<T>
is a safe, typed, box-like pointer that owns the data it points to, but not the underlying memory: that means the owner of a MovingPtr<T>
can freely move parts of the data out and doesn't have to worry about de-allocating memory. Much like Box<T>
, MovingPtr<T>
implements Deref
and DerefMut
for easy access to the stored type, when it's safe to do so. To decompose the value inside of the MovingPtr<T>
into its fields without copying them to the stack, you can use the deconstruct_moving_ptr!
macro to give you MovingPtr<U>
s to each field specified:
struct MySpawnableList<A: Bundle, B: Bundle> {
a: A,
b: B,
}
let my_ptr: MovingPtr<'_, MySpawnableList<u32, String>> = ...;
deconstruct_moving_ptr!(my_ptr => { a, b, });
let a_ptr: MovingPtr<'_, u32> = a;
let b_ptr: MovingPtr<'_, String> = b;
Similar to Box::into_inner
, MovingPtr<T>
also has a method MovingPtr::read
for moving the whole value out of the pointer onto the stack:
let a: u32 = a_ptr.read();
let b: String = b_ptr.read();
To create a MovingPtr<T>
from a value, you can use the move_as_ptr!
macro:
let my_value = MySpawnableList { a: 42u32, b: "Hello".to_string() };
move_as_ptr!(my_value);
let _: MovingPtr = my_value;
This macro works by shadowing the original variable name with the newly created MovingPtr<T>
. MovingPtr<T>
can also be created manually with the unsafe
method MovingPtr::from_value
, which takes a &mut
to an initialized MaybeUninit<T>
, which the MovingPtr<T>
takes ownership of: the MaybeUninit<T>
should be treated as uninitialized after the MovingPtr<T>
has been used!
To migrate your implementations of SpawnableList
to the new API, you will want to read the this
parameter to spawn or insert any bundles stored within:
impl<R: Relationship> SpawnableList<R> for MySpawnableList<A: Bundle, B: Bundle> {
// 0.16
fn spawn(self, world: &mut World, entity: Entity) {
let MySpawnableList { a, b } = self;
world.spawn((R::from(entity), a, b));
}
// 0.17
fn spawn(this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
let MySpawnableList { a, b } = this.read();
world.spawn((R::from(entity), a, b));
}
}
or only read the fields you need with deconstruct_moving_ptr!
:
fn spawn(this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
unsafe {
// Only `a` is kept, `b` will be forgotten without being dropped!
deconstruct_moving_ptr!(this => { a, });
let a = a.read();
world.spawn((R::from(entity), a));
}
}
Changes to bevy_tasks
feature flags #
Various feature flags in bevy_tasks
have been simplified, thanks to the addition of bevy_platform::cfg
:
- Removed
critical-section
feature (it was just a re-export of bevy_platform anyway) - Removed
std
andweb
features, relying onbevy_platform::cfg
to check for availability. - Added
futures-lite
feature to provide access to theblock_on
implementation fromfutures-lite
. - Added a fallback implementation of
block_on
that just busy-waits. - Moved
wasm-bindgen
related dependencies out ofbevy_tasks
and moved them intobevy_platform
under a new exports module. - Made
async-io
implicit feature explicit.
Any std
, web
or critical-section
feature flags that you've enabled for bevy_tasks
in your project can simply be removed.
If you need access to wasm-bindgen
functionality that was previously in bevy_tasks
, you can find them in bevy_platform
. However, note that the re-exports of various web-related crates (js_sys
, wasm_bindgen
and wasm_bindgen_futures
) are not intended for external consumption. Instead, pull your own dependencies to these crates, making sure the version used matches to ensure interoperability.