- 0.11 to 0.12
- API updates to the AnimationPlayer
- Fix run-once runners
- Add support for KHR_materials_emissive_strength
- Bevy Asset V2
- Copy on Write AssetPaths
- Removed anyhow
- Non-blocking load_untyped using a wrapper asset
- reflect: TypePath part 2
- More ergonomic spatial audio
- Simplify parallel iteration methods
- Fix safety invariants for WorldQuery::fetch and simplify cloning
- Opt-out multi-threaded feature flag
- Refactor build_schedule and related errors
- Add system.map(...) for transforming the output of a system
- Replaced EntityMap with HashMap
- Rename ManualEventIterator
- Replaced EntityCommand Implementation for FnOnce
- Move schedule name into Schedule
- Refactor EventReader::iter to read
- Replace IntoSystemSetConfig with IntoSystemSetConfigs
- Moved get_component(_unchecked_mut) from Query to QueryState
- Fix naming on "tick" Column and ComponentSparseSet methods
- Return a boolean from set_if_neq
- Rename RemovedComponents::iter/iter_with_id to read/read_with_id
- Remove States::variants and remove enum-only restriction its derive
- Replace all labels with interned labels
- Add configure_schedules to App and Schedules to apply ScheduleBuildSettings to all schedules
- Only run event systems if they have tangible work to do
- Allow disjoint mutable world access via EntityMut
- Make builder types take and return Self
- Change AxisSettings livezone default
- Rename bevy_math::rects conversion methods
- Rename Bezier to CubicBezier for clarity
- Add Cubic prefix to all cubic curve generators
- Remove the bevy_dylib feature
- Refactor path module of bevy_reflect
- Make it so ParsedPath can be passed to GetPath
- Remove TypeRegistry re-export rename
- Provide getters for fields of ReflectFromPtr
- bevy_reflect: Fix ignored/skipped field order
- Return URect instead of (UVec2, UVec2) in Camera::physical_viewport_rect
- Update bevy_window::PresentMode to mirror wgpu::PresentMode
- Use GpuArrayBuffer for MeshUniform
- Reduce the size of MeshUniform to improve performance
- Reorder render sets, refactor bevy_sprite to take advantage
- Split ComputedVisibility into two components to allow for accurate change detection and speed up visibility propagation
- Cleanup visibility module
- Allow other plugins to create renderer resources
- Use EntityHashMap<Entity, T> for render world entity storage for better performance
- PCF For DirectionalLight/SpotLight Shadows
- use Material for wireframes
- Deferred Renderer
- pbr shader cleanup
- *_PREPASS Shader Def Cleanup
- Allow extensions to StandardMaterial
- Variable MeshPipeline View Bind Group Layout
- Update shader imports
- Bind group entries
- Detect cubemap for dds textures
- Add convenient methods for Image
- Use “specular occlusion” term to consistently extinguish fresnel on Ambient and Environment Map lights
- Fix fog color being inaccurate
- Image Sampler Improvements
- StandardMaterial Light Transmission
- Increase default normal bias to avoid common artifacts
- Make DirectionalLight Cascades computation generic over CameraProjection
- Move skin code to a separate module
- Move scene spawner systems to SpawnScene schedule
- Remove Resource and add Debug to TaskPoolOptions
- Global TaskPool API improvements
- Unify FixedTime and Time while fixing several problems
- Change the default for the measure_func field of ContentSize to None.
- Change UiScale to a tuple struct
- Cleanup some bevy_text pipeline.rs
- Make GridPlacement's fields non-zero and add accessor functions.
- Remove Val's try_* arithmetic methods
- Rename Val evaluate to resolve and implement viewport variant support
- TextLayoutInfo::size should hold the drawn size of the text, and not a scaled value.
- Have a separate implicit viewport node per root node + make viewport node Display::Grid
- Rename num_font_atlases to len.
- Various accessibility API updates.
- Add some more docs for bevy_text.
- Update UI alignment docs
- Add option to toggle window control buttons
- Improve bevy_winit documentation
- Work around naga/wgpu WGSL instance_index -> GLSL gl_InstanceID bug on WebGL2
- Remove IntoIterator impl for &mut EventReader
- Update default `ClearColor`` to better match Bevy's branding
- View Transformations
Migration Guide: 0.11 to 0.12
Bevy relies heavily on improvements in the Rust language and compiler. As a result, the Minimum Supported Rust Version (MSRV) is "the latest stable release" of Rust.
API updates to the AnimationPlayer #
Some methods on AnimationPlayer
have changed.
elapsed
was removed. Useseek_time
.set_elapsed
was removed. Useseek_to
.stop_repeating
was removed. Useset_repeat(RepeatAnimation::Never)
.
If you were manually resetting animation state, you can use the new replay
method instead.
Fix run-once runners #
app.ready()
has been replaced by app.plugins_state()
which will return more details on the current state of plugins in the app
Add support for KHR_materials_emissive_strength #
The GLTF asset loader will now factor in emissiveStrength
when converting to Bevy’s StandardMaterial::emissive
. Blender will export emissive materials using this field. Remove the field from your GLTF files or manually modify your materials post-asset-load to match how Bevy would load these files in previous versions.
Bevy Asset V2 #
Migrating a custom asset loader #
Existing asset loaders will need a few small changes to get them to work with Bevy Assets V2.
First, you’ll need to add the asset type as an associated type of the loader. This type is called Asset
and represents the type of the “default asset” produced by the loader.
You’ll also need to add a Settings
type which represents options that can be passed to the loader when you request an asset. If your asset has no settings, then you can just set it to the unit type.
pub struct MyAssetLoader;
impl AssetLoader for MyAssetLoader {
type Asset = MyAsset;
type Settings = ();
You’ll need to make a couple small changes to the load
function as well. The load function now takes a settings
parameter whose type is, you guessed it, Settings
:
fn load<'a>(
&'a self,
reader: &'a mut Reader,
settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
Again, if you are not using settings, then you can just ignore the parameter (prefix it with “_”).
Also, the second argument is now a reader rather than vector of bytes. If your existing code expects bytes, you can simply read the entire stream:
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
Finally, you’ll need to write the code which returns the default asset. This used to be done via a call to load_context.set_default_asset()
, however in V2 you simply return the asset from the load
function:
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let mut asset: MyAsset =
serde_json::from_slice(&bytes).expect("unable to decode asset");
Ok(asset)
}
To use the new loader, make sure you register both the loader and the asset type:
app.register_asset_loader(MyAssetLoader)
.init_asset::<MyAsset>()
Asset hot-reloading #
The feature filesystem_watcher
has been renamed to file_watcher
. In addition, you no longer need to manually configure the ChangeWatcher
in the AssetPlugin
as it is now configured automatically when the feature is enabled.
Labeled assets #
If your loader allows labeled assets, there are a couple of different ways to handle them. The simplest is to call load_context.labeled_asset_scope
:
// Assume `asset.children` is a HashMap or something.
// Using `drain` here so that we take ownership and don't end up with
// multiple references to the same asset.
asset.children.drain().for_each(|(label, mut item)| {
load_context.labeled_asset_scope(label, |lc| {
// Do any additional processing on the item
// Use 'lc' to load dependencies
item
});
});
You can use the provided load context (lc
) to load additional assets. These will automatically be registered as dependencies of the labeled asset.
Using assets #
The actual call to load
hasn’t changed:
let handle = server.load("path/to/my/asset.json");
// ...
let data = assets.get(&handle).unwrap();
Asset events #
There are a few changes to asset events. The event no longer contains a handle
field, instead the event contains a field called id
:
for ev in ev_template.read() {
match ev {
AssetEvent::Added { id } => {
println!("Asset added");
}
AssetEvent::LoadedWithDependencies { id } => {
println!("Asset loaded");
}
AssetEvent::Modified { id } => {
println!("Asset modified");
}
AssetEvent::Removed { id } => {
println!("Asset removed");
}
}
}
The id
can be used to get access to the asset data, the asset’s path or load status. Asset handles also contain an id
field which can be used to compare for equality:
AssetEvent::Modified { id } => {
for cmp in query.iter() {
if cmp.handle.id() == id {
println!("Found it!");
}
}
}
Also, as you may have noticed, the set of events has changed. The most important of these is LoadedWithDependencies
which tells you that the asset and all its dependencies have finished loading into memory.
UntypedHandle
#
HandleUntyped
has been renamed to UntypedHandle
. HandleId
has been replaced with UntypedAssetId
and its typed equivalent AssetId<T>
.
The new way to construct an untyped handle looks like this:
// 0.11
const MESH_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Mesh::TYPE_UUID, 0x1f40128bac02a9b);
// 0.12
const MESH_HANDLE: UntypedHandle =
UntypedHandle::Weak(UntypedAssetId::Uuid { type_id: TypeId::of::<Mesh>(), uuid: Uuid::from_u128(0x1f40128bac02a9b) });
Copy on Write AssetPaths #
// 0.11
AssetPath::new("logo.png", None);
// 0.12
AssetPath::from("logo.png");
// 0.11
AssetPath::new("scene.gltf", Some("Mesh0"));
// 0.12
AssetPath::from("scene.gltf").with_label("Mesh0");
AssetPath
now serializes as AssetPath("some_path.extension#Label")
instead of as AssetPath { path: "some_path.extension", label: Some("Label) }
Removed anyhow
#
anyhow
is no longer exported bybevy_asset
; Add it to your own project (if required).AssetLoader
andAssetSaver
have an associated typeError
; Define an appropriate error type (e.g., usingthiserror
), or use a pre-made error type (e.g.,anyhow::Error
). Note that usinganyhow::Error
is a drop-in replacement.AssetLoaderError
has been removed; Define a new error type, or use an alternative (e.g.,anyhow::Error
)- All the first-party
AssetLoader
s andAssetSaver
s now return relevant (and narrow) error types instead of a single ambiguous type; Match over the specific error type, or encapsulate (Box<dyn>
,thiserror
,anyhow
, etc.)
Non-blocking load_untyped using a wrapper asset #
Whenever possible use the typed API in order to directly get a handle to your asset. If you do not know the type or need to use load_untyped
for a different reason, Bevy 0.12 introduces an additional layer of indirection. The asset server will return a handle to a LoadedUntypedAsset
, which will load in the background. Once it is loaded, the untyped handle to the asset file can be retrieved from the LoadedUntypedAsset
s field handle
.
reflect: TypePath
part 2 #
- Rely on
TypePath
instead ofstd::any::type_name
for all stability guarantees and for use in all reflection contexts, this is used through with one of the following APIs:TypePath::type_path
if you have a concrete type and not a value.DynamicTypePath::reflect_type_path
if you have andyn Reflect
value without a concrete type.TypeInfo::type_path
for use through the registry or if you want to work with the represented type of aDynamicFoo
.
- Remove
type_name
from manualReflect
implementations. - Use
type_path
andtype_path_table
in place oftype_name
onTypeInfo
-like structs. - Use
get_with_type_path(_mut)
overget_with_type_name(_mut)
.
More ergonomic spatial audio #
Spatial audio now automatically uses the transform of the AudioBundle
and of an entity with a SpatialListener
component.
If you were manually scaling emitter/listener positions, you can use the spatial_scale
field of AudioPlugin
instead.
// 0.11
commands.spawn(
SpatialAudioBundle {
source: asset_server.load("sounds/Windless Slopes.ogg"),
settings: PlaybackSettings::LOOP,
spatial: SpatialSettings::new(listener_position, gap, emitter_position),
},
);
fn update(
emitter_query: Query<(&Transform, &SpatialAudioSink)>,
listener_query: Query<&Transform, With<Listener>>,
) {
let listener = listener_query.single();
for (transform, sink) in &emitter_query {
sink.set_emitter_position(transform.translation);
sink.set_listener_position(*listener, gap);
}
}
// 0.12
commands.spawn((
SpatialBundle::from_transform(Transform::from_translation(emitter_position)),
AudioBundle {
source: asset_server.load("sounds/Windless Slopes.ogg"),
settings: PlaybackSettings::LOOP.with_spatial(true),
},
));
commands.spawn((
SpatialBundle::from_transform(Transform::from_translation(listener_position)),
SpatialListener::new(gap),
));
Simplify parallel iteration methods #
The method QueryParIter::for_each_mut
has been deprecated and is no longer functional. Use for_each
instead, which now supports mutable queries.
// 0.11
query.par_iter_mut().for_each_mut(|x| ...);
// 0.12
query.par_iter_mut().for_each(|x| ...);
The method QueryParIter::for_each
now takes ownership of the QueryParIter
, rather than taking a shared reference.
// 0.11
let par_iter = my_query.par_iter().batching_strategy(my_batching_strategy);
par_iter.for_each(|x| {
// ...Do stuff with x...
par_iter.for_each(|y| {
// ...Do nested stuff with y...
});
});
// 0.12
my_query.par_iter().batching_strategy(my_batching_strategy).for_each(|x| {
// ...Do stuff with x...
my_query.par_iter().batching_strategy(my_batching_strategy).for_each(|y| {
// ...Do nested stuff with y...
});
});
Fix safety invariants for WorldQuery::fetch
and simplify cloning #
fetch
invariants
The function WorldQuery::fetch
has had the following safety invariant added:
If
update_component_access
includes any mutable accesses, then the caller must ensure thatfetch
is called no more than once for eachentity
/table_row
in each archetype. IfSelf
implementsReadOnlyWorldQuery
, then this can safely be called multiple times.
This invariant was always required for soundness, but was previously undocumented. If you called this function manually anywhere, you should check to make sure that this invariant is not violated.
Removed clone_fetch
The function WorldQuery::clone_fetch
has been removed. The associated type WorldQuery::Fetch
now has the bound Clone
.
// 0.11
struct MyFetch<'w> { ... }
unsafe impl WorldQuery for MyQuery {
...
type Fetch<'w> = MyFetch<'w>
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
MyFetch {
field1: fetch.field1,
field2: fetch.field2.clone(),
...
}
}
}
// 0.12
#[derive(Clone)]
struct MyFetch<'w> { ... }
unsafe impl WorldQuery for MyQuery {
...
type Fetch<'w> = MyFetch<'w>;
}
Opt-out multi-threaded
feature flag #
The multi-threaded
feature in bevy_ecs
and bevy_tasks
is no longer enabled by default. However, this remains a default feature for the umbrella bevy
crate.
if you are using bevy
without default-features
, or if you depend on bevy_ecs
or bevy_tasks
directly, you most likely want to enable this to allow systems to run in parallel.
Refactor build_schedule and related errors #
ScheduleBuildError
now has strings in more of its variants. You may need to adjust code that is handling these variants.
Add system.map(...)
for transforming the output of a system #
The system_adapter
functions have been deprecated: use .map
instead, which is a lightweight alternative to .pipe
.
// 0.11
my_system.pipe(system_adapter::ignore)
my_system.pipe(system_adapter::unwrap)
my_system.pipe(system_adapter::new(T::from))
// 0.12
my_system.map(std::mem::drop)
my_system.map(Result::unwrap)
my_system.map(T::from)
// 0.11
my_system.pipe(system_adapter::info)
my_system.pipe(system_adapter::dbg)
my_system.pipe(system_adapter::warn)
my_system.pipe(system_adapter::error)
// 0.12
my_system.map(bevy_utils::info)
my_system.map(bevy_utils::dbg)
my_system.map(bevy_utils::warn)
my_system.map(bevy_utils::error)
Replaced EntityMap with HashMap #
- Calls to
EntityMap::world_scope
can be directly replaced with the following:map.world_scope(&mut world)
->world.world_scope(&mut map)
- Calls to legacy
EntityMap
methods such asEntityMap::get
must explicitly include de/reference symbols:let entity = map.get(parent);
->let &entity = map.get(&parent);
Rename ManualEventIterator
#
The type ManualEventIterator
has been renamed to EventIterator
. Additionally, ManualEventIteratorWithId
has been renamed to EventIteratorWithId
.
Replaced EntityCommand
Implementation for FnOnce
#
1. New-Type FnOnce
Create an EntityCommand
type which implements the method you previously wrote:
pub struct ClassicEntityCommand<F>(pub F);
impl<F> EntityCommand for ClassicEntityCommand<F>
where
F: FnOnce(Entity, &mut World) + Send + 'static,
{
fn apply(self, id: Entity, world: &mut World) {
(self.0)(id, world);
}
}
commands.add(ClassicEntityCommand(|id: Entity, world: &mut World| {
/* ... */
}));
2. Extract (Entity, &mut World)
from EntityMut
The method into_world_mut
can be used to gain access to the World
from an EntityMut
.
let old = |id: Entity, world: &mut World| {
/* ... */
};
let new = |mut entity: EntityWorldMut| {
let id = entity.id();
let world = entity.into_world_mut();
/* ... */
};
Move schedule name into Schedule
#
Schedule::new
and App::add_schedule
// 0.11
let schedule = Schedule::new();
app.add_schedule(MyLabel, schedule);
// 0.12
let schedule = Schedule::new(MyLabel);
app.add_schedule(schedule);
if you aren’t inserting the schedule into the world and are using the schedule directly you can use the default constructor which reuses a default label.
// 0.11
let schedule = Schedule::new();
schedule.run(world);
// 0.12
let schedule = Schedule::default();
schedule.run(world);
Schedules::insert
// 0.11
let schedule = Schedule::new();
schedules.insert(MyLabel, schedule);
// 0.12
let schedule = Schedule::new(MyLabel);
schedules.insert(schedule);
World::add_schedule
// 0.11
let schedule = Schedule::new();
world.add_schedule(MyLabel, schedule);
// 0.12
let schedule = Schedule::new(MyLabel);
world.add_schedule(schedule);
Refactor EventReader::iter
to read
#
- Existing usages of
EventReader::iter
andEventReader::iter_with_id
will have to be changed toEventReader::read
andEventReader::read_with_id
respectively. - Existing usages of
ManualEventReader::iter
andManualEventReader::iter_with_id
will have to be changed toManualEventReader::read
andManualEventReader::read_with_id
respectively.
Replace IntoSystemSetConfig
with IntoSystemSetConfigs
#
- Use
App::configure_sets
instead ofApp::configure_set
- Use
Schedule::configure_sets
instead ofSchedule::configure_set
Moved get_component(_unchecked_mut)
from Query
to QueryState
#
use bevy_ecs::system::QueryComponentError;
-> use bevy_ecs::query::QueryComponentError;
Fix naming on "tick" Column and ComponentSparseSet methods #
The following method names were renamed, from foo_ticks_bar
to foo_tick_bar
(ticks
is now singular, tick
):
ComponentSparseSet::get_added_ticks
→get_added_tick
ComponentSparseSet::get_changed_ticks
→get_changed_tick
Column::get_added_ticks
→get_added_tick
Column::get_changed_ticks
→get_changed_tick
Column::get_added_ticks_unchecked
→get_added_tick_unchecked
Column::get_changed_ticks_unchecked
→get_changed_tick_unchecked
Return a boolean from set_if_neq
#
The trait method DetectChangesMut::set_if_neq
now returns a boolean value indicating whether or not the value was changed. If you were implementing this function manually, you must now return true
if the value was overwritten and false
if the value was not.
Rename RemovedComponents::iter/iter_with_id to read/read_with_id #
fn react_on_removal(mut removed: RemovedComponents<MyComponent>) {
// 0.11
for entity in removed.iter() { /* ... */ }
for (entity, id) in removed.iter_with_id() { /* ... */ }
for entity in &mut removed { /* ... */ }
// 0.12
for entity in removed.read() { /* ... */ }
for (entity, id) in removed.read_with_id() { /* ... */ }
for entity in removed.read() { /* ... */ }
}
Remove States::variants and remove enum-only restriction its derive #
States::variants
no longer exists. If you relied on this function, consider using a library that provides enum iterators.
Replace all labels with interned labels #
Replace
BoxedScheduleLabel
andBox<dyn ScheduleLabel>
withInternedScheduleLabel
orInterned<dyn ScheduleLabel>
.Replace
BoxedSystemSet
andBox<dyn SystemSet>
withInternedSystemSet
orInterned<dyn SystemSet>
.Replace
AppLabelId
withInternedAppLabel
orInterned<dyn AppLabel>
.Types manually implementing
ScheduleLabel
,AppLabel
orSystemSet
need to implement:dyn_hash
directly instead of implementingDynHash
as_dyn_eq
Pass labels to
World::try_schedule_scope
,World::schedule_scope
,World::try_run_schedule
.World::run_schedule
,Schedules::remove
,Schedules::remove_entry
,Schedules::contains
,Schedules::get
andSchedules::get_mut
by value instead of by reference.
Add configure_schedules to App and Schedules to apply ScheduleBuildSettings
to all schedules #
- No breaking changes.
- Adds
Schedule::get_build_settings()
getter for the schedule’sScheduleBuildSettings
. - Can replaced manual configuration of all schedules:
// 0.11
for (_, schedule) in app.world.resource_mut::<Schedules>().iter_mut() {
schedule.set_build_settings(build_settings);
}
// 0.l2
app.configure_schedules(build_settings);
Only run event systems if they have tangible work to do #
Events<T>::update_system
has been split off from the type and can be found at bevy_ecs::event::event_update_system
.
Allow disjoint mutable world access via EntityMut
#
Removed the method EntityRef::world
, to fix a soundness issue with queries. If you need access to &World
while using an EntityRef
, consider passing the world as a separate parameter.
EntityMut
can no longer perform ‘structural’ world mutations, such as adding or removing components, or despawning the entity. Additionally, EntityMut::world
, EntityMut::world_mut
, EntityMut::into_world_mut
, and EntityMut::world_scope
have been removed. Instead, use the newly-added type EntityWorldMut
, which is a helper type for working with &mut World
.
Make builder types take and return Self
#
When using bevy_ecs::DynamicSceneBuilder
and bevy_ecs::SceneBuilder
, instead of binding the builder to a variable, directly use it. Methods on those types now consume Self
, so you will need to re-bind the builder if you don’t build
it immediately.
// 0.11
let mut scene_builder = DynamicSceneBuilder::from_world(&world);
let scene = scene_builder.extract_entity(a).extract_entity(b).build();
// 0.12
let scene = DynamicSceneBuilder::from_world(&world)
.extract_entity(a)
.extract_entity(b)
.build();
Change AxisSettings
livezone default #
The default live zone bounds have been changed from -0.95..=0.95
to -1.0..=1.0
to align with more common usage. If you were relying on the old default, you can change this by modifying GamepadSettings::default_axis_settings
.
Rename bevy_math::rects conversion methods #
Replace Rect::as_urect
with Rect::as_irect
, Rect::as_rect
with Rect::as_urect
, and URect::as_urect
with URect::as_irect
.
Rename Bezier
to CubicBezier
for clarity #
Change all Bezier
references to CubicBezier
Add Cubic
prefix to all cubic curve generators #
- Rename:
BSpline
->CubicBSpline
- Rename:
CardinalSpline
->CubicCardinalSpline
- Rename:
Hermite
->CubicHermite
Remove the bevy_dylib feature #
If you were using Bevy’s bevy_dylib
feature, use Bevy’s dynamic_linking
feature instead.
# 0.11
cargo run --features bevy/bevy_dylib
# 0.12
cargo run --features bevy/dynamic_linking
[dependencies]
# 0.11
bevy = { version = "0.11", features = ["bevy_dylib"] }
# 0.12
bevy = { version = "0.12", features = ["dynamic_linking"] }
Refactor path
module of bevy_reflect
#
If you were matching on the Err(ReflectPathError)
value returned by GetPath
and ParsedPath
methods, now only the parse-related errors and the offset are publicly accessible. You can always use the fmt::Display
to get a clear error message, but if you need programmatic access to the error types, please open an issue.
Make it so ParsedPath
can be passed to GetPath #
GetPath
now requires Reflect
. This reduces a lot of boilerplate on bevy’s side. If you were implementing manually GetPath
on your own type, please get in touch!
ParsedPath::element[_mut]
isn’t an inherent method of ParsedPath
, you must now import ReflectPath
. This is only relevant if you weren’t importing the bevy prelude.
-use bevy::reflect::ParsedPath;
+use bevy::reflect::{ParsedPath, ReflectPath};
parsed_path.element(reflect_type).unwrap()
Remove TypeRegistry re-export rename #
TypeRegistry
as re-exported by the wrapperbevy
crate is nowTypeRegistryArc
TypeRegistryInternal
as re-exported by the wrapperbevy
crate is nowTypeRegistry
Provide getters for fields of ReflectFromPtr #
ReflectFromPtr::as_reflect_ptr
is nowReflectFromPtr::as_reflect
ReflectFromPtr::as_reflect_ptr_mut
is nowReflectFromPtr::as_reflect_mut
bevy_reflect: Fix ignored/skipped field order #
- Fields marked
#[reflect(skip_serializing)]
now must implementDefault
or specify a custom default function with#[reflect(default = "path::to::some_func")]
#[derive(Reflect)]
struct MyStruct {
#[reflect(skip_serializing)]
#[reflect(default = "get_foo_default")]
foo: Foo, // <- `Foo` does not impl `Default` so requires a custom function
#[reflect(skip_serializing)]
bar: Bar, // <- `Bar` impls `Default`
}
#[derive(Reflect)]
struct Foo(i32);
#[derive(Reflect, Default)]
struct Bar(i32);
fn get_foo_default() -> Foo {
Foo(123)
}
SerializationData::new
has been changed to expect an iterator of(usize, SkippedField)
rather than one of justusize
// 0.11
SerializationData::new([0, 3].into_iter());
// 0.12
SerializationData::new([
(0, SkippedField::new(field_0_default_fn)),
(3, SkippedField::new(field_3_default_fn)),
].into_iter());
Serialization::is_ignored_field
has been renamed toSerialization::is_field_skipped
- Fields marked
#[reflect(skip_serializing)]
are now included in deserialization output. This may affect logic that expected those fields to be absent.
Return URect instead of (UVec2, UVec2) in Camera::physical_viewport_rect #
// 0.11
fn view_physical_camera_rect(camera_query: Query<&Camera>) {
let camera = camera_query.single();
let Some((min, max)) = camera.physical_viewport_rect() else { return };
dbg!(min, max);
}
// 0.12
fn view_physical_camera_rect(camera_query: Query<&Camera>) {
let camera = camera_query.single();
let Some(URect { min, max }) = camera.physical_viewport_rect() else { return };
dbg!(min, max);
}
Update bevy_window::PresentMode
to mirror wgpu::PresentMode
#
Handle bevy_window::PresentMode::FifoRelaxed
when tweaking window present mode manually.
Use GpuArrayBuffer for MeshUniform #
Accessing the model
member of an individual mesh object’s shader Mesh
struct the old way where each MeshUniform
was stored at its own dynamic offset:
struct Vertex {
@location(0) position: vec3<f32>,
};
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
out.clip_position = mesh_position_local_to_clip(
mesh.model,
vec4<f32>(vertex.position, 1.0)
);
return out;
}
The new way where one needs to index into the array of Mesh
es for the batch:
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
};
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
out.clip_position = mesh_position_local_to_clip(
mesh[vertex.instance_index].model,
vec4<f32>(vertex.position, 1.0)
);
return out;
}
Note that using the instance_index is the default way to pass the per-object index into the shader, but if you wish to do custom rendering approaches you can pass it in however you like.
Reduce the size of MeshUniform to improve performance #
Sphere::intersects_obb
and Frustum::intersects_obb
now take an Affine3A
instead of a Mat4
. You can use Affine3A::from_mat4
or Transform::compute_affine
to get an Affine3A
.
MeshUniform
now stores its current and previous model transforms as 4x3 matrices. Helper functions were added to bevy_pbr::mesh_functions to unpack the data.
// 0.11
var model = mesh[instance_index].model;
// 0.12
#import bevy_pbr::mesh_functions::affine_to_square
var model = affine_to_square(mesh[instance_index].model);
Reorder render sets, refactor bevy_sprite to take advantage #
- Assets such as materials and meshes should now be created in
PrepareAssets
e.g.prepare_assets<Mesh>
- Queueing entities to
RenderPhase
s continues to be done inQueue
e.g.queue_sprites
- Preparing resources (textures, buffers, etc.) should now be done in
PrepareResources
, e.g.prepare_prepass_textures
,prepare_mesh_uniforms
- Prepare bind groups should now be done in
PrepareBindGroups
e.g.prepare_mesh_bind_group
- Any batching or instancing can now be done in
Prepare
where the order of the phase items is known e.g.prepare_sprites
Split ComputedVisibility
into two components to allow for accurate change detection and speed up visibility propagation #
The ComputedVisibility
component has been split into InheritedVisibility
and ViewVisibility
. Replace any usages of ComputedVisibility::is_visible_in_hierarchy
with InheritedVisibility::get
, and replace ComputedVisibility::is_visible_in_view
with ViewVisibility::get
.
// 0.11:
commands.spawn(VisibilityBundle {
visibility: Visibility::Inherited,
computed_visibility: ComputedVisibility::default(),
});
// 0.12:
commands.spawn(VisibilityBundle {
visibility: Visibility::Inherited,
inherited_visibility: InheritedVisibility::default(),
view_visibility: ViewVisibility::default(),
});
// 0.11:
fn my_system(q: Query<&ComputedVisibility>) {
for vis in &q {
if vis.is_visible_in_hierarchy() {
// 0.12:
fn my_system(q: Query<&InheritedVisibility>) {
for inherited_visibility in &q {
if inherited_visibility.get() {
// 0.11:
fn my_system(q: Query<&ComputedVisibility>) {
for vis in &q {
if vis.is_visible_in_view() {
// 0.12:
fn my_system(q: Query<&ViewVisibility>) {
for view_visibility in &q {
if view_visibility.get() {
// 0.11:
fn my_system(mut q: Query<&mut ComputedVisibility>) {
for vis in &mut q {
vis.set_visible_in_view();
// 0.12:
fn my_system(mut q: Query<&mut ViewVisibility>) {
for view_visibility in &mut q {
view_visibility.set();
Cleanup visibility
module #
The check_visibility
system’s Option<&NoFrustumCulling>
parameter has been replaced by Has<NoFrustumCulling>
, if you were calling it manually, you should change the type to match it
Allow other plugins to create renderer resources #
The RenderPlugin
now takes a RenderCreation
enum instead of WgpuSettings
. RenderSettings::default()
returns RenderSettings::Automatic(WgpuSettings::default())
. RenderSettings
also implements From<WgpuSettings>
.
// 0.11
RenderPlugin {
wgpu_settings: WgpuSettings {
...
},
}
// 0.12
RenderPlugin {
render_creation: RenderCreation::Automatic(WgpuSettings {
...
}),
}
// or
RenderPlugin {
render_creation: WgpuSettings {
...
}.into(),
}
Use EntityHashMap<Entity, T> for render world entity storage for better performance #
Previously the render app extracted mesh entities and their component data from the main world and stored them as entities and components in the render world. Now they are extracted into essentially EntityHashMap<Entity, T>
where T
are structs containing an appropriate group of data. This means that while extract set systems will continue to run extract queries against the main world they will store their data in hash maps. Also, systems in later sets will either need to look up entities in the available resources such as RenderMeshInstances
, or maintain their own EntityHashMap<Entity, T>
for their own data.
// 0.11
fn queue_custom(
material_meshes: Query<(Entity, &MeshTransforms, &Handle<Mesh>), With<InstanceMaterialData>>,
) {
...
for (entity, mesh_transforms, mesh_handle) in &material_meshes {
...
}
}
// 0.12
fn queue_custom(
render_mesh_instances: Res<RenderMeshInstances>,
instance_entities: Query<Entity, With<InstanceMaterialData>>,
) {
...
for entity in &instance_entities {
let Some(mesh_instance) = render_mesh_instances.get(&entity) else { continue; };
// The mesh handle in `AssetId<Mesh>` form, and the `MeshTransforms` can now
// be found in `mesh_instance` which is a `RenderMeshInstance`
...
}
}
PCF For DirectionalLight/SpotLight Shadows #
Shadows cast by directional lights or spotlights now have smoother edges. To revert to the old behavior, add ShadowFilteringMethod::Hardware2x2
to your cameras.
use Material
for wireframes #
WireframePipeline
was removed. If you were using it directly, please create an issue explaining your use case.
Deferred Renderer #
pbr shader cleanup #
in custom material shaders:
pbr_functions::pbr
no longer calls topbr_functions::alpha_discard
. if you were using thepbr
function in a custom shader with alpha mask mode you now also need to call alpha_discard manually- rename imports of
bevy_pbr::mesh_vertex_output
tobevy_pbr::forward_io
- rename instances of
MeshVertexOutput
toVertexOutput
in custom material prepass shaders:
- rename instances of
VertexOutput::clip_position
toVertexOutput::position
*_PREPASS
Shader Def Cleanup #
When using functions from bevy_pbr::prepass_utils
(prepass_depth()
, prepass_normal()
, prepass_motion_vector()
) in contexts where these prepasses might be disabled, you should now wrap your calls with the appropriate #ifdef
guards, (#ifdef DEPTH_PREPASS
, #ifdef NORMAL_PREPASS
, #ifdef MOTION_VECTOR_PREPASS
) providing fallback logic where applicable.
Allow extensions to StandardMaterial #
Manual implementations of AsBindGroup
will need to be adjusted, the changes are pretty straightforward and can be seen in the diff for e.g. the texture_binding_array
example.
Variable MeshPipeline
View Bind Group Layout #
MeshPipeline::view_layout
and MeshPipeline::view_layout_multisampled
have been replaced with a private array to accommodate for variable view bind group layouts. To obtain a view bind group layout for the current pipeline state, use the new MeshPipeline::get_view_layout()
or MeshPipeline::get_view_layout_from_key()
methods.
Update shader imports #
naga_oil 0.10 reworks the import mechanism to support more syntax to make it more rusty, and test for item use before importing to determine which imports are modules and which are items, which allows:
- use rust-style imports
#import bevy_pbr::{
pbr_functions::{alpha_discard as discard, apply_pbr_lighting},
mesh_bindings,
}
- import partial paths:
#import part::of::path
// ...
path::remainder::function();
which will call to part::of::path::remainder::function
- use fully qualified paths without importing:
// #import bevy_pbr::pbr_functions
bevy_pbr::pbr_functions::pbr()
- use imported items without qualifying
#import bevy_pbr::pbr_functions::pbr
// for backwards compatibility the old style is still supported:
// #import bevy_pbr::pbr_functions pbr
// ...
pbr()
- allows most imported items to end with
_
and numbers (naga_oil#30). still doesn’t allow struct members to end with_
or numbers but it’s progress. - the vast majority of existing shader code will work without changes, but will emit “deprecated” warnings for old-style imports. these can be suppressed with the
allow-deprecated
feature. - partly breaks overrides (as far as i’m aware nobody uses these yet) - now overrides will only be applied if the overriding module is added as an additional import in the arguments to
Composer::make_naga_module
orComposer::add_composable_module
. this is necessary to support determining whether imports are modules or items.
Bind group entries #
- Calls to
RenderDevice::create_bind_group({BindGroupDescriptor { label, layout, entries })
must be amended toRenderDevice::create_bind_group(label, layout, entries)
. - If
label
s have been specified as"bind_group_name".into()
, they need to change to just"bind_group_name"
.Some("bind_group_name")
andNone
will still work, butSome("bind_group_name")
can optionally be simplified to just"bind_group_name"
.
Detect cubemap for dds textures #
If you are matching on a TextureError
, you will need to add a new branch to handle TextureError::IncompleteCubemap
.
Add convenient methods for Image #
Replace calls to the Image::size()
method with size_f32()
. Replace calls to the Image::aspect_2d()
method with aspect_ratio()
.
Use “specular occlusion” term to consistently extinguish fresnel on Ambient and Environment Map lights #
- If Fresnel highlights from Ambient and Environment Map lights are no longer visible in your materials, make sure you’re using a higher, physically plausible value of
reflectance
(⪆ 0.35).
Fix fog color being inaccurate #
Colors in FogSettings
struct (color
and directional_light_color
) are now sent to the GPU in linear space. If you were using Color::rgb()
/Color::rgba()
and would like to retain the previous colors, you can quickly fix it by switching to Color::rgb_linear()
/Color::rgba_linear()
.
Image Sampler Improvements #
- When using the
Image
API, useImageSamplerDescriptor
instead ofwgpu::SamplerDescriptor
- If writing custom wgpu renderer features that work with
Image
, call&image_sampler.as_wgpu()
to convert to a wgpu descriptor.
StandardMaterial
Light Transmission #
SsaoPipelineKey::temporal_noise
has been renamed toSsaoPipelineKey::temporal_jitter
- The
TAA
shader def (controlled by the presence of theTemporalAntiAliasSettings
component in the camera) has been replaced with theTEMPORAL_JITTER
shader def (controlled by the presence of theTemporalJitter
component in the camera) MeshPipelineKey::TAA
has been replaced byMeshPipelineKey::TEMPORAL_JITTER
- The
TEMPORAL_NOISE
shader def has been consolidated withTEMPORAL_JITTER
Increase default normal bias to avoid common artifacts #
The default shadow_normal_bias
value for DirectionalLight
and SpotLight
has changed to accommodate artifacts introduced with the new shadow PCF changes. It is unlikely (especially given the new PCF shadow behaviors with these values), but you might need to manually tweak this value if your scene requires a lower bias and it relied on the previous default value.
Make DirectionalLight
Cascades
computation generic over CameraProjection
#
If you have a component MyCustomProjection
that implements CameraProjection
:
- You need to implement a new required associated method,
get_frustum_corners
, returning an array of the corners of a subset of the frustum with givenz_near
andz_far
, in local camera space. - You can now add the
build_directional_light_cascades::<MyCustomProjection>
system inSimulationLightSystems::UpdateDirectionalLightCascades
afterclear_directional_light_cascades
for your projection to work with directional lights.
Move skin code to a separate module #
Renamed skinning systems, resources and components:
- extract_skinned_meshes -> extract_skins
- prepare_skinned_meshes -> prepare_skins
- SkinnedMeshUniform -> SkinUniform
- SkinnedMeshJoints -> SkinIndex
Move scene spawner systems to SpawnScene schedule #
scene_spawner_system
was moved to a new SpawnScene
schedule which is run between Update
and PostUpdate
.
If you were ordering your own systems to run before scene_spawner_system
in Update
, that might no longer be necessary. If your system needs to run after scene_spawner_system
, it should be moved to the SpawnScene
or PostUpdate
schedule.
Remove Resource and add Debug to TaskPoolOptions #
If for some reason anyone is still using TaskPoolOptions
as a Resource, they would now have to use a wrapper type:
#[derive(Resource)]
pub struct MyTaskPoolOptions(pub TaskPoolOptions);
Global TaskPool API improvements #
Uses of ComputeTaskPool::init
, AsyncComputeTaskPool::init
and IoTaskPool::init
should be changed to ::get_or_init
.
Unify FixedTime
and Time
while fixing several problems #
- Change all
Res<Time>
instances that accessraw_delta()
,raw_elapsed()
and related methods toRes<Time<Real>>
anddelta()
,elapsed()
, etc. - Change access to
period
fromRes<FixedTime>
toRes<Time<Fixed>>
and usedelta()
. - The default timestep has been changed from 60 Hz to 64 Hz. If you wish to restore the old behaviour, use
app.insert_resource(Time::<Fixed>::from_hz(60.0))
. - Change
app.insert_resource(FixedTime::new(duration))
toapp.insert_resource(Time::<Fixed>::from_duration(duration))
- Change
app.insert_resource(FixedTime::new_from_secs(secs))
toapp.insert_resource(Time::<Fixed>::from_seconds(secs))
- Change
system.on_fixed_timer(duration)
tosystem.on_timer(duration)
. Timers in systems placed inFixedUpdate
schedule automatically use the fixed time clock. - Change
ResMut<Time>
calls topause()
,is_paused()
,set_relative_speed()
and related methods toResMut<Time<Virtual>>
calls. The API is the same, with the exception thatrelative_speed()
will return the actual last ste relative speed, whileeffective_relative_speed()
returns 0.0 if the time is paused and corresponds to the speed that was set when the update for the current frame started.
Change the default for the measure_func
field of ContentSize
to None. #
The default for ContentSize
now sets its measure_func
to None
, instead of a fixed size measure that returns Vec2::ZERO
. The helper function fixed_size
can be called with ContentSize::fixed_size(Vec2::ZERO)
to get the previous behaviour.
Change UiScale
to a tuple struct #
Replace initialization of UiScale
like UiScale { scale: 1.0 }
with UiScale(1.0)
Cleanup some bevy_text pipeline.rs #
- The
ResMut<TextPipeline>
argument tomeasure_text_system
doesn’t exist anymore. If you were calling this system manually, you should remove the argument. - The
{min,max}_width_content_size
fields ofTextMeasureInfo
are renamed tomin
andmax
respectively - Other changes to
TextMeasureInfo
may also break your code if you were manually building it. Please consider using the newTextMeasureInfo::from_text
to build one instead. TextPipeline::create_text_measure
has been removed in favor ofTextMeasureInfo::from_text
Make GridPlacement
's fields non-zero and add accessor functions. #
GridPlacement
’s constructor functions no longer accept values of 0
. Given any argument of 0
they will panic with a GridPlacementError
.
Remove Val
's try_*
arithmetic methods #
Val
’s try_*
arithmetic methods have been removed. To perform arithmetic on Val
s deconstruct them using pattern matching.
Rename Val
evaluate
to resolve
and implement viewport variant support #
- Renamed the following
Val
methods and added aviewport_size
parameter:evaluate
toresolve
try_add_with_size
totry_add_with_context
try_add_assign_with_size
totry_add_assign_with_context
try_sub_with_size
totry_sub_with_context
try_sub_assign_with_size
totry_sub_assign_with_context
TextLayoutInfo::size
should hold the drawn size of the text, and not a scaled value. #
The size
value of TextLayoutInfo
is stored in logical pixels and has been renamed to logical_size
. There is no longer any need to divide by the window’s scale factor to get the logical size.
Have a separate implicit viewport node per root node + make viewport node Display::Grid
#
- Bevy UI now lays out root nodes independently of each other in separate layout contexts. If you were relying on your root nodes being able to affect each other’s layouts, then you may need to wrap them in a single root node.
- The implicit viewport node (which contains each user-specified root node) is now
Display::Grid
withalign_items
andjustify_items
both set toStart
. You may need to addheight: Val::Percent(100.)
to your root nodes if you were previously relying on being implicitly set.
Rename num_font_atlases
to len
. #
The num_font_atlases
method of FontAtlasSet
has been renamed to len
.
Various accessibility API updates. #
Change direct accesses of AccessibilityRequested
to use AccessibilityRequested.::get()
/AccessibilityRequested::set()
// 0.11
use std::sync::atomic::Ordering;
// To access
accessibility_requested.load(Ordering::SeqCst)
// To update
accessibility_requested.store(true, Ordering::SeqCst);
// 0.12
// To access
accessibility_requested.get()
// To update
accessibility_requested.set(true);
Add some more docs for bevy_text. #
Usages of TextSettings.max_font_atlases
from bevy_text
must be changed to TextSettings.soft_max_font_atlases
.
Update UI alignment docs #
The JustifyContents
enum has been expanded to include JustifyContents::Stretch
.
Add option to toggle window control buttons #
Added an enabled_buttons
member to the Window
struct through which users can enable or disable specific window control buttons.
Improve bevy_winit
documentation #
UpdateMode::Reactive { max_wait: .. }
->UpdateMode::Reactive { wait: .. }
UpdateMode::ReactiveLowPower { max_wait: .. }
->UpdateMode::ReactiveLowPower { wait: .. }
Work around naga/wgpu WGSL instance_index -> GLSL gl_InstanceID bug on WebGL2 #
Shader code before:
struct Vertex {
@builtin(instance_index) instance_index: u32,
...
}
@vertex
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
// ...
var model = mesh[vertex_no_morph.instance_index].model;
}
After:
#import bevy_render::instance_index
struct Vertex {
@builtin(instance_index) instance_index: u32,
// ...
}
@vertex
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
// ...
let instance_index = bevy_render::instance_index::get_instance_index(vertex_no_morph.instance_index);
var model = mesh[instance_index].model;
}
Remove IntoIterator
impl for &mut EventReader
#
&mut EventReader
does not implement IntoIterator
anymore. replace for foo in &mut events
by for foo in events.iter()
Update default `ClearColor`` to better match Bevy's branding #
The default app background color has changed. To use the old default, add a ClearColor
resource.
App::new()
.insert_resource(ClearColor(Color::rgb(0.4, 0.4, 0.4)))
.add_plugins(DefaultPlugins)
View Transformations #
mesh_functions::mesh_position_world_to_clip
was moved and renamed to view_transformations::position_world_to_clip
. It now also takes a vec3
instead of a vec4
so you will need to use vec4.xyz
to get a vec3
.
// 0.11
#import bevy_pbr::mesh_functions::mesh_position_world_to_clip
fn mesh_position_local_to_clip(model: mat4x4<f32>, vertex_position: vec4<f32>) -> vec4<f32> {
let world_position = mesh_position_local_to_world(model, vertex_position);
return mesh_position_world_to_clip(world_position);
}
// 0.12
#import bevy_pbr::view_transformations::position_world_to_clip;
fn mesh_position_local_to_clip(model: mat4x4<f32>, vertex_position: vec4<f32>) -> vec4<f32> {
let world_position = mesh_position_local_to_world(model, vertex_position);
return position_world_to_clip(world_position.xyz);
}