- 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.
elapsedwas removed. Useseek_time.set_elapsedwas removed. Useseek_to.stop_repeatingwas 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 #
anyhowis no longer exported bybevy_asset; Add it to your own project (if required).AssetLoaderandAssetSaverhave 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::Erroris a drop-in replacement.AssetLoaderErrorhas been removed; Define a new error type, or use an alternative (e.g.,anyhow::Error)- All the first-party
AssetLoaders andAssetSavers 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 LoadedUntypedAssets field handle.
reflect: TypePath part 2 #
- Rely on
TypePathinstead ofstd::any::type_namefor all stability guarantees and for use in all reflection contexts, this is used through with one of the following APIs:TypePath::type_pathif you have a concrete type and not a value.DynamicTypePath::reflect_type_pathif you have andyn Reflectvalue without a concrete type.TypeInfo::type_pathfor use through the registry or if you want to work with the represented type of aDynamicFoo.
- Remove
type_namefrom manualReflectimplementations. - Use
type_pathandtype_path_tablein place oftype_nameonTypeInfo-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_accessincludes any mutable accesses, then the caller must ensure thatfetchis called no more than once for eachentity/table_rowin each archetype. IfSelfimplementsReadOnlyWorldQuery, 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_scopecan be directly replaced with the following:map.world_scope(&mut world)->world.world_scope(&mut map) - Calls to legacy
EntityMapmethods such asEntityMap::getmust 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::iterandEventReader::iter_with_idwill have to be changed toEventReader::readandEventReader::read_with_idrespectively. - Existing usages of
ManualEventReader::iterandManualEventReader::iter_with_idwill have to be changed toManualEventReader::readandManualEventReader::read_with_idrespectively.
Replace IntoSystemSetConfig with IntoSystemSetConfigs #
- Use
App::configure_setsinstead ofApp::configure_set - Use
Schedule::configure_setsinstead 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_tickComponentSparseSet::get_changed_ticks→get_changed_tickColumn::get_added_ticks→get_added_tickColumn::get_changed_ticks→get_changed_tickColumn::get_added_ticks_unchecked→get_added_tick_uncheckedColumn::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
BoxedScheduleLabelandBox<dyn ScheduleLabel>withInternedScheduleLabelorInterned<dyn ScheduleLabel>.Replace
BoxedSystemSetandBox<dyn SystemSet>withInternedSystemSetorInterned<dyn SystemSet>.Replace
AppLabelIdwithInternedAppLabelorInterned<dyn AppLabel>.Types manually implementing
ScheduleLabel,AppLabelorSystemSetneed to implement:dyn_hashdirectly instead of implementingDynHashas_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::getandSchedules::get_mutby 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 #
TypeRegistryas re-exported by the wrapperbevycrate is nowTypeRegistryArcTypeRegistryInternalas re-exported by the wrapperbevycrate is nowTypeRegistry
Provide getters for fields of ReflectFromPtr #
ReflectFromPtr::as_reflect_ptris nowReflectFromPtr::as_reflectReflectFromPtr::as_reflect_ptr_mutis nowReflectFromPtr::as_reflect_mut
bevy_reflect: Fix ignored/skipped field order #
- Fields marked
#[reflect(skip_serializing)]now must implementDefaultor 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::newhas 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_fieldhas 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 Meshes 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
PrepareAssetse.g.prepare_assets<Mesh> - Queueing entities to
RenderPhases continues to be done inQueuee.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
PrepareBindGroupse.g.prepare_mesh_bind_group - Any batching or instancing can now be done in
Preparewhere 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::pbrno longer calls topbr_functions::alpha_discard. if you were using thepbrfunction in a custom shader with alpha mask mode you now also need to call alpha_discard manually- rename imports of
bevy_pbr::mesh_vertex_outputtobevy_pbr::forward_io - rename instances of
MeshVertexOutputtoVertexOutput
in custom material prepass shaders:
- rename instances of
VertexOutput::clip_positiontoVertexOutput::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-deprecatedfeature. - 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_moduleorComposer::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
labels have been specified as"bind_group_name".into(), they need to change to just"bind_group_name".Some("bind_group_name")andNonewill 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
ImageAPI, useImageSamplerDescriptorinstead 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_noisehas been renamed toSsaoPipelineKey::temporal_jitter- The
TAAshader def (controlled by the presence of theTemporalAntiAliasSettingscomponent in the camera) has been replaced with theTEMPORAL_JITTERshader def (controlled by the presence of theTemporalJittercomponent in the camera) MeshPipelineKey::TAAhas been replaced byMeshPipelineKey::TEMPORAL_JITTER- The
TEMPORAL_NOISEshader 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_nearandz_far, in local camera space. - You can now add the
build_directional_light_cascades::<MyCustomProjection>system inSimulationLightSystems::UpdateDirectionalLightCascadesafterclear_directional_light_cascadesfor 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
periodfromRes<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 inFixedUpdateschedule 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_systemdoesn’t exist anymore. If you were calling this system manually, you should remove the argument. - The
{min,max}_width_content_sizefields ofTextMeasureInfoare renamed tominandmaxrespectively - Other changes to
TextMeasureInfomay also break your code if you were manually building it. Please consider using the newTextMeasureInfo::from_textto build one instead. TextPipeline::create_text_measurehas 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 Vals deconstruct them using pattern matching.
Rename Val evaluate to resolve and implement viewport variant support #
- Renamed the following
Valmethods and added aviewport_sizeparameter:evaluatetoresolvetry_add_with_sizetotry_add_with_contexttry_add_assign_with_sizetotry_add_assign_with_contexttry_sub_with_sizetotry_sub_with_contexttry_sub_assign_with_sizetotry_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::Gridwithalign_itemsandjustify_itemsboth 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);
}