Thanks to 166 contributors, 522 pull requests, community reviewers, and our generous sponsors, we’re happy to announce the Bevy 0.11 release on crates.io!
For those who don’t know, Bevy is a refreshingly simple data-driven game engine built in Rust. You can check out our Quick Start Guide to try it today. It’s free and open source forever! You can grab the full source code on GitHub. Check out Bevy Assets for a collection of community-developed plugins, games, and learning resources.
To update an existing Bevy App or Plugin to Bevy 0.11, check out our 0.10 to 0.11 Migration Guide.
Since our last release a few months ago we’ve added a ton of new features, bug fixes, and quality of life tweaks, but here are some of the highlights:
- Screen Space Ambient Occlusion (SSAO): Increase scene render quality by simulating occlusion of “indirect” diffuse light
- Temporal Anti-Aliasing (TAA): A popular anti-aliasing technique that blends the current frame with past frames using motion vectors to smooth out artifacts
- Morph Targets: Animate vertex positions on meshes between predefined states. Great for things like character customization!
- Robust Constrast Adaptive Sharpening (RCAS): Intelligently sharpens renders, which pairs nicely with TAA
- WebGPU Support: Bevy can now render on the web faster and with more features using the modern WebGPU web API
- Improved Shader Imports: Bevy shaders now support granular imports and other new features
- Parallax Mapping: Materials now support an optional depth map, giving flat surfaces a feel of depth through parallaxing the material’s textures
- Schedule-First ECS APIs: A simpler and more ergonomic ECS system scheduling API
- Immediate Mode Gizmo Rendering: Easily and efficiently render 2D and 3D shapes for debugging and editor scenarios
- ECS Audio APIs: A more intuitive and idiomatic way to play back audio
- UI Borders: UI nodes can now have configurable borders!
- Grid UI Layout: Bevy UI now supports CSS-style grid layout
- UI Performance Improvements: The UI batching algorithm was changed, yielding significant performance wins
Screen Space Ambient Occlusion
#
authors: @JMS55, @danchia, @superdump
Drag this image to compare
SSAO Only
Bevy now supports Screen Space Ambient Occlusion (SSAO). While Bevy already supported shadows from direct lights
(DirectionalLight
, PointLight
, SpotLight
) via shadow mapping, Bevy now supports shadows from indirect diffuse lighting such as AmbientLight
or EnvironmentMapLight
.
These shadows give scenes a more “grounded” feel, by estimating how much surrounding geometry blocks incoming light via the screen-space depth and normal prepasses. You can try it out in the new SSAO example.
Note that using SSAO with the newly added Temporal Anti-Aliasing leads to a large increase in quality and noise reduction.
Platform support is currently limited – Only Vulkan, DirectX12, and Metal are currently supported. WebGPU support will come at a later date. WebGL likely won’t be supported because it doesn’t have compute shaders.
Special thanks to Intel for their open source XeGTAO project, which was a huge help in developing this feature.
Temporal Anti-Aliasing
#
authors: @JMS55, @DGriffin91
Alongside MSAA and FXAA, Bevy now supports Temporal Anti-aliasing (TAA) as an anti-aliasing option.
TAA works by blending the newly rendered frame with past frames in order to smooth out aliasing artifacts in the image. TAA has become increasingly popular in the industry because of its ability to cover up so many rendering artifacts: it smooths out shadows (both global illumination and “casted” shadows), mesh edges, textures, and reduces specular aliasing of light on reflective surfaces. However because the “smoothing” effect is so apparent, some people prefer other methods.
Here’s a quick rundown of the following advantages and disadvantages of each anti-aliasing method that Bevy supports:
- Multi-Sample Antialiasing (MSAA)
- Does a good job at smoothing the edges of meshes (anti geometric aliasing). Does not help with specular aliasing. Performance cost scales with triangle count, and performs very poorly on scenes with many triangles
- Fast Approximate Antialiasing (FXAA)
- Does a decent job of dealing with both geometric and specular aliasing. Very little performance cost in all scenes. Somewhat blurry and low quality results
- Temporal Antialiasing (TAA)
- Does a very good job at dealing with both geometric and specular aliasing. Does a good job at dealing with temporal aliasing, where high-frequency details flicker over time or as you move the camera around or as things animate. Performance cost is moderate, and scales only with screen resolution. Chance of “ghosting” where meshes or lighting effects may leave trails behind them that fade over time. Although TAA helps with reducing temporal aliasing, it may also introduce additional temporal aliasing, especially on thin geometry or texture detail rendered at a distance. Requires 2 view’s worth of additional GPU memory, as well as enabling the motion vector and depth prepasses. Requires accurate motion vector and depth prepasses, which complicates custom materials
TAA implementations are a series of tradeoffs and rely on heuristics that are easy to get wrong. In Bevy 0.11, TAA is marked as an experimental feature for the following reasons:
- TAA does not currently work with the following Bevy features: skinning, morph targets, and parallax mapping
- TAA currently tends to soften the image a bit, which can be worked around via post-process sharpening
- Our TAA heuristics are not currently user-configurable (and these heuristics are likely to change and evolve)
We will continue to improve quality, compatibility, and performance in future releases. Please report any bugs you encounter!
You can compare all of our anti-aliasing methods in Bevy’s improved anti-aliasing example.
Robust Contrast Adaptive Sharpening
#
authors: @Elabajaba
Effects like TAA and FXAA can cause the final render to become blurry. Sharpening post processing effects can help counteract that. In Bevy 0.11 we’ve added a port of AMD’s Robust Constrast Adaptive Sharpening (RCAS).
Drag this image to compare
Notice that the texture on the leather part of the helmet is much crisper!
Morph Targets
#
authors: @nicopap, @cart
Bevy, since the 0.7 release, supports 3D animations.
But it only supported skeletal animations. Leaving on the sidewalk a common
animation type called morph targets (aka blendshapes, aka keyshapes, and a slew
of other name). This is the grandparent of all 3D character animation!
Crash Bandicoot‘s run cycle used morph targets.
Character model by Samuel Rosario (© all rights reserved), used with permission. Modified by nicopap, using the Snow character texture by Demeter Dzadik for Blender Studios (🅯 CC-BY).
Nowadays, an animation artist will typically use a skeleton rig for wide
moves and morph targets to clean up the detailed movements.
When it comes to game assets, however, the complex skeleton rigs used by
artists for faces and hands are too heavy. Usually, the poses are
“baked” into morph poses, and facial expression transitions are handled
in the engine through morph targets.
Morph targets is a very simple animation method. Take a model, have a base
vertex position, move the vertices around to create several poses:
Store those poses as a difference between the default base mesh and the variant
pose, then, at runtime, mix each pose. Now that we have the difference with
the base mesh, we can get the variant pose by simply adding to the base
vertices positions.
That’s it, the morph target shader looks like this:
fn morph_vertex(vertex: Vertex) {
for (var i: u32 = 0u; i < pose_count(); i++) {
let weight = weight_for_pose(i);
vertex.position += weight * get_difference(vertex.index, position_offset, i);
vertex.normal += weight * get_difference(vertex.index, normal_offset, i);
}
}
In Bevy, we store the weights per pose in the MorphWeights
component.
fn set_weights_system(mut morph_weights: Query<&mut MorphWeights>) {
for mut entity_weights in &mut morph_weights {
let weights = entity_weights.weights_mut();
weights[0] = 0.5;
weights[1] = 0.25;
}
}
Now assuming that we have two morph targets, (1) the frown pose, (2)
the smirk pose:
[0.0, 0.0]
default pose
[1.0, 0.0]
frown only
[0.0, 1.0]
smirk only
[0.5, 0.0]
half frown
[1.0, 1.0]
both at max
[0.5, 0.25]
bit of both
While conceptually simple, it requires communicating to the GPU a tremendous
amount of data. Thousand of vertices, each 288 bits, several model variations,
sometimes a hundred.
We store the vertex data as pixels in a 3D texture. This allows morph targets to not only
run on WebGPU, but also on the WebGL2 wgpu backend.
This could be improved in a number of ways, but it is sufficient for an
initial implementation.
Parallax Mapping
#
author: @nicopap
Bevy now supports parallax mapping and depth maps. Parallax mapping puts normal
maps to shame when it comes to giving “illusion of depth” to a material. The top half of this video uses parallax mapping plus a normal map, whereas the bottom half only uses a normal map:
earth view, elevation & night view by NASA (public domain)
Notice how it is not merely the shading of pixels that changes, but their
actual position on screen. The mountaintops hide mountain ridges behind
themselves. High mountains move faster than coastal areas.
Parallax mapping moves pixels according to the perspective and depth on the
surface of the geometry. Adding true 3D depth to flat surfaces.
All of that, without adding a single vertex to the geometry. The whole globe
has exactly 648 vertices. Unlike a more primitive shader, such as displacement
mapping, parallax mapping only requires an additional grayscale image, called
the depth_map
.
Games often use parallax mapping for cobblestones or brick walls, so
let’s make a brick wall in Bevy! First, we spawn a mesh:
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Box::new(30.0, 10.0, 1.0).into()),
material: materials.add(StandardMaterial {
base_color: Color::WHITE,
..default()
}),
..default()
});
Of course, it’s just a flat white box, we didn’t add any texture.
So let’s add a normal map:
normal_map_texture: Some(assets.load("normal_map.png")),
This is much better. The shading changes according to the light direction too!
However, the specular highlights on the corner are overbearing, almost noisy.
Let’s see how a depth map can help:
depth_map: Some(assets.load("depth_map.png")),
We eliminated the noise! There is also that sweet 3D feel reminiscent of
90’s games pre-rendered cinematic sequences.
So what’s going on, why does parallax mapping eliminate the ugly specular
lights on the wall?
This is because parallax mapping insets the ridges between bricks, so that they
are occluded by the bricks themselves.
Since normal maps do not “move” the shaded areas, merely shade them
differently, we get those awkward specular highlights. With parallax mapping,
they are gone.
Drag this image to compare
Parallax mapping in Bevy is still very limited. The most painful aspect is that
it is not a standard glTF feature, meaning that the depth texture needs to be
programmatically added to materials if they came from a GLTF file.
Additionally, parallax mapping is incompatible with the temporal antialiasing
shader, doesn’t work well on curved surfaces, and doesn’t affect object’s
silhouettes.
However, those are not fundamental limitations of parallax mapping, and may be
fixed in the future.
Skyboxes
#
authors: @JMS55, @superdump
Bevy now has built-in support for displaying an HDRI environment as your scene background.
Simply attach the new Skybox
component to your Camera
. It pairs well with the existing EnvironmentMapLight
, which will use the environment map to light the scene.
We also plan to add support for built-in procedural skyboxes sometime in the future!
WebGPU Support
#
authors: @mockersf, many others throughout Bevy’s development
Bevy now supports WebGPU rendering on the web (in addition to WebGL 2). WebGPU support is still rolling out, but if you have a supported web browser you can explore our new live WebGPU examples page.
What is WebGPU?
#
WebGPU is an exciting new web standard for doing modern GPU graphics and compute. It takes inspiration from Vulkan, Direct3D 12, and Metal. In fact, it is generally implemented on top of these APIs under the hood. WebGPU gives us access to more GPU features than WebGL2 (such as compute shaders) and also has the potential to be much faster. It means that more of Bevy’s native renderer features are now also available on the web. It also uses the new WGSL shader language. We’re very happy with how WGSL has evolved over time and Bevy uses it internally for our shaders. We also added usability features like imports! But with Bevy you still have the option to use GLSL if you prefer.
How it Works
#
Bevy is built on top of the wgpu library, which is a modern low-level GPU API that can target pretty much every popular API: Vulkan, Direct3D 12, Metal, OpenGL, WebGL2, and WebGPU. The best backend API is selected for a given platform. It is a “native” rendering API, but it generally follows the WebGPU terminology and API design. Unlike WebGPU, it can provide direct access to the native APIs, which means Bevy enjoys a “best of all worlds” situation.
WebGPU Examples
#
Click one of the images below to check out our live WebGPU examples (if your browser supports it):
Improved Shader Imports
#
authors: @robtfm
Bevy’s rendering engine has a lot of great options and features. For example, the PBR StandardMaterial
pipeline supports desktop/webgpu and webgl, 6 optional mesh attributes, 4 optional textures, and a plethora of optional features like fog, skinning, and alpha blending modes, with more coming in every release.
Many feature combos need specialized shader variants, and with over 3000 lines of shader code split over 50 files in total, the text-substitution-based shader processor was beginning to creak at the seams.
This release we’ve switched to using naga_oil, which gives us a module-based shader framework. It compiles each file individually to naga’s IR and then combines them into a final shader on demand. This doesn’t have much visible impact yet, but it does give a few immediate benefits:
The future possibilities are more exciting. Using naga IR opens the door to a bunch of nice features that we hope to bring in future releases:
- Automatic bind slot allocation will let plugins extend the core view bindgroup, which means self-contained plugins for features like lighting and shadow methods, common material properties, etc become viable. This will allow us to modularise the core pipelines to make growing the codebase – while keeping support for multiple targets – more sustainable
- “Virtual” shader functions will allow user modifications to core functions (like lighting), and potentially lead to a template-style material system, where users can provide “hooks” that will be called at the right point in the pipeline
- Language interop: mix and match glsl and wgsl, so bevy’s pbr pipeline features could be accessed from your glsl material shader, or utils written for glsl could be used in wgsl code. We’re hopeful that this can extend to spirv (and rust-gpu) as well
- More cool stuff we haven’t thought of yet. Being able to inspect and modify shaders at runtime is very powerful and makes a lot of things possible!
UI Node Borders
#
authors: @ickshonpe
UI nodes now draws borders, whose color can be configured with the new BorderColor
component:
commands.spawn(ButtonBundle {
style: Style {
border: UiRect::all(Val::Px(5.0)),
..default()
},
border_color: BorderColor(Color::rgb(0.9, 0.9, 0.9)),
..default()
})
Each side of the border is configurable:
Grid UI Layout
#
authors: @nicoburns
In Bevy UI we wired up the new grid
feature in the layout library we use (Taffy). This enables CSS-style grid layouts:
This can be configured on the Style
component:
Style {
/// Use grid layout for this node
display: Display::Grid,
/// Make the grid have a 1:1 aspect ratio
/// This means the width will adjust to match the height
aspect_ratio: Some(1.0),
// Add 24px of padding around the grid
padding: UiRect::all(Val::Px(24.0)),
/// Set the grid to have 4 columns all with sizes minmax(0, 1fr)
/// This creates 4 exactly evenly sized columns
grid_template_columns: RepeatedGridTrack::flex(4, 1.0),
/// Set the grid to have 4 rows all with sizes minmax(0, 1fr)
/// This creates 4 exactly evenly sized rows
grid_template_rows: RepeatedGridTrack::flex(4, 1.0),
/// Set a 12px gap/gutter between rows and columns
row_gap: Val::Px(12.0),
column_gap: Val::Px(12.0),
..default()
},
Schedule-First ECS APIs
#
authors: @cart
In Bevy 0.10 we introduced ECS Schedule V3, which vastly improved the capabilities of Bevy ECS system scheduling: scheduler API ergonomics, system chaining, the ability to run exclusive systems and apply deferred system operations at any point in a schedule, a single unified schedule, configurable System Sets, run conditions, and a better State system.
However it pretty quickly became clear that the new system still had some areas to improve:
- Base Sets were hard to understand and error prone: What is a Base Set? When do I use them? Why do they exist? Why is my ordering implicitly invalid due to incompatible Base Set ordering? Why do some schedules have a default Base Set while others don’t? Base Sets were confusing!
- There were too many ways to schedule a System: We’ve accumulated too many scheduling APIs. As of Bevy 0.10, we had SIX different ways to add a system to the “startup” schedule. Thats too many ways!
- Too much implicit configuration: There were both default Schedules and default Base Sets. In some cases systems had default schedules or default base sets, but in other cases they didn’t! A system’s schedule and configuration should be explicit and clear.
- Adding Systems to Schedules wasn’t ergonomic: Things like
add_system(foo.in_schedule(CoreSchedule::Startup))
were not fun to type or read. We created special-case helpers, such asadd_startup_system(foo)
, but this required more internal code, user-defined schedules didn’t benefit from the special casing, and it completely hid theCoreSchedule::Startup
symbol!.
Unraveling the Complexity
#
If your eyes started to glaze over as you tried to wrap your head around this, or phrases like “implicitly added to the CoreSet::Update
Base Set” filled you with dread … don’t worry. After a lot of careful thought we’ve unraveled the complexity and built something clear and simple.
In Bevy 0.11 the “scheduling mental model” is much simpler thanks to Schedule-First ECS APIs:
app
.add_systems(Startup, (a, b))
.add_systems(Update, (c, d, e))
.add_systems(FixedUpdate, (f, g))
.add_systems(PostUpdate, h)
.add_systems(OnEnter(AppState::Menu), enter_menu)
.add_systems(OnExit(AppState::Menu), exit_menu)
- There is exactly one way to schedule systems
- Call
add_systems
, state the schedule name, and specify one or more systems
- Call
- Base Sets have been entirely removed in favor of Schedules, which have friendly / short names
- Ex: The
CoreSet::Update
Base Set has becomeUpdate
- Ex: The
- There is no implicit or implied configuration
- Default Schedules and default Base Sets don’t exist
- The syntax is easy on the eyes and ergonomic
- Schedules are first so they “line up” when formatted
To compare, expand this to see what it used to be!
app
// Startup system variant 1.
// Has an implied default StartupSet::Startup base set
// Has an implied CoreSchedule::Startup schedule
.add_startup_systems((a, b))
// Startup system variant 2.
// Has an implied default StartupSet::Startup base set
// Has an implied CoreSchedule::Startup schedule
.add_systems((a, b).on_startup())
// Startup system variant 3.
// Has an implied default StartupSet::Startup base set
.add_systems((a, b).in_schedule(CoreSchedule::Startup))
// Update system variant 1.
// `CoreSet::Update` base set and `CoreSchedule::Main` are implied
.add_system(c)
// Update system variant 2 (note the add_system vs add_systems difference)
// `CoreSet::Update` base set and `CoreSchedule::Main` are implied
.add_systems((d, e))
// No implied default base set because CoreSchedule::FixedUpdate doesn't have one
.add_systems((f, g).in_schedule(CoreSchedule::FixedUpdate))
// `CoreSchedule::Main` is implied, in_base_set overrides the default CoreSet::Update set
.add_system(h.in_base_set(CoreSet::PostUpdate))
// This has no implied default base set
.add_systems(enter_menu.in_schedule(OnEnter(AppState::Menu)))
// This has no implied default base set
.add_systems(exit_menu.in_schedule(OnExit(AppState::Menu)))
Note that normal “system sets” still exist! You can still use sets to organize and order your systems:
app.add_systems(Update, (
(walk, jump).in_set(Movement),
collide.after(Movement),
))
The configure_set
API has also been adjusted for parity:
// Bevy 0.10
app.configure_set(Foo.after(Bar).in_schedule(PostUpdate))
// Bevy 0.11
app.configure_set(PostUpdate, Foo.after(Bar))
Nested System Tuples and Chaining
#
authors: @cart
It is now possible to infinitely nest tuples of systems in a .add_systems
call!
app.add_systems(Update, (
(a, (b, c, d, e), f),
(g, h),
i
))
At first glance, this might not seem very useful. But in combination with per-tuple configuration, it allows you to easily and cleanly express schedules:
app.add_systems(Update, (
(attack, defend).in_set(Combat).before(check_health)
check_health,
(handle_death, respawn).after(check_health)
))
.chain()
has also been adapted to support arbitrary nesting! The ordering in the example above could be rephrased like this:
app.add_systems(Update,
(
(attack, defend).in_set(Combat)
check_health,
(handle_death, respawn)
).chain()
)
This will run attack
and defend
first (in parallel), then check_health
, then handle_death
and respawn
(in parallel).
This allows for powerful and expressive “graph-like” ordering expressions:
app.add_systems(Update,
(
(a, (b, c, d).chain()),
(e, f),
).chain()
)
This will run a
in parallel with b->c->d
, then after those have finished running it will run e
and f
in parallel.
Gizmos
#
authors: @devil-ira, @jannik4, @lassade, @The5-1, @Toqozz, @nicopap
It is often helpful to be able to draw simple shapes and lines in 2D and 3D for things like editor controls, and debug views. Game development is a very “spatial” thing and being able to quickly draw shapes is the visual equivalent of “print line debugging”. It helps answer questions like “is this ray casting in the right direction?” and “is this collider big enough?”
In Bevy 0.11 we’ve added an “immediate mode” Gizmos
drawing API that makes these things easy and efficient. In 2D and 3D you can draw lines, rects, circles, arcs, spheres, cubes, line strips, and more!
2D Gizmos
3D Gizmos
From any system you can spawn shapes into existence (for both 2D and 3D):
fn system(mut gizmos: Gizmos) {
// 2D
gizmos.line_2d(Vec2::new(0., 0.), Vec2::new(0., 10.), Color::RED);
gizmos.circle_2d(Vec2::new(0., 0.), 40., Color::BLUE);
// 3D
gizmos.circle(Vec3::ZERO, Vec3::Y, 3., Color::BLACK);
gizmos.ray(Vec3::new(0., 0., 0.), Vec3::new(5., 5., 5.), Color::BLUE);
gizmos.sphere(Vec3::ZERO, Quat::IDENTITY, 3.2, Color::BLACK)
}
Because the API is “immediate mode”, gizmos will only be drawn on frames where they are “queued up”, which means you don’t need to worry about cleaning up gizmo state!
Gizmos are drawn in batches, which means they are very cheap. You can have hundreds of thousands of them!
ECS Audio APIs
#
authors: @inodentry
Bevy’s audio playback APIs have been reworked to integrate more cleanly with Bevy’s ECS.
In previous versions of Bevy you would play back audio like this:
#[derive(Resource)]
struct MyMusic {
sink: Handle<AudioSink>,
}
fn play_music(
mut commands: Commands,
asset_server: Res<AssetServer>,
audio: Res<Audio>,
audio_sinks: Res<Assets<AudioSink>>
) {
let weak_handle = audio.play(asset_server.load("my_music.ogg"));
let strong_handle = audio_sinks.get_handle(weak_handle);
commands.insert_resource(MyMusic {
sink: strong_handle,
});
}
That is a lot of boilerplate just to play a sound! Then to adjust playback you would access the AudioSink
like this:
fn pause_music(my_music: Res<MyMusic>, audio_sinks: Res<Assets<AudioSink>>) {
if let Some(sink) = audio_sinks.get(&my_music.sink) {
sink.pause();
}
}
Treating audio playback as a resource created a number of problems and notably didn’t play well with things like Bevy Scenes. In Bevy 0.11, audio playback is represented as an Entity
with AudioBundle
components:
#[derive(Component)]
struct MyMusic;
fn play_music(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
AudioBundle {
source: asset_server.load("my_music.ogg"),
..default()
},
MyMusic,
));
}
Much simpler! To adjust playback you can query for the AudioSink
component:
fn pause_music(query_music: Query<&AudioSink, With<MyMusic>>) {
if let Ok(sink) = query.get_single() {
sink.pause();
}
}
Global Audio Volume
#
authors: @mrchantey
Bevy now has a global volume level which can be configured via the [GlobalVolume
] resource:
app.insert_resource(GlobalVolume::new(0.2));
Resource Support in Scenes
#
authors: @Carbonhell, @Davier
Bevy’s scene format is a very useful tool for serializing and deserializing game state to and from scene files.
Previously, the captured state was limited to only entities and their components.
With Bevy 0.11, scenes now support serializing resources as well.
This adds a new resources
field to the scene format:
(
resources: {
"my_game::stats::TotalScore": (
score: 9001,
),
},
entities: {
// Entity scene data...
},
)
Scene Filtering
#
authors: @MrGVSV
When serializing data to a scene, all components and resources are serialized by default.
In previous versions, you had to use the given TypeRegistry
to act as a filter, leaving out the types you don’t want included.
In 0.11, there’s now a dedicated SceneFilter
type to make filtering easier, cleaner, and more intuitive.
This can be used with DynamicSceneBuilder
to have fine-grained control over what actually gets serialized.
We can allow
a subset of types:
let mut builder = DynamicSceneBuilder::from_world(&world);
let scene = builder
.allow::<ComponentA>()
.allow::<ComponentB>()
.extract_entity(entity)
.build();
Or deny
them:
let mut builder = DynamicSceneBuilder::from_world(&world);
let scene = builder
.deny::<ComponentA>()
.deny::<ComponentB>()
.extract_entity(entity)
.build();
Default Font
#
authors: @mockersf
Bevy now supports a configurable default font and embeds a tiny default font (a minimal version of Fira Mono). This is useful if you use a common font throughout your project. And it makes it easier to prototype new changes with a “placeholder font” without worrying about setting it on each node.
UI Texture Atlas Support
#
authors: @mwbryant
Previously UI ImageBundle
Nodes could only use handles to full images without an ergonomic way to use TextureAtlases
in UI. In this release we add support for an AtlasImageBundle
UI Node which brings the existing TextureAtlas
support into UI.
This was achieved by merging the existing mechanisms that allows text rendering to select which glyph to use and the mechanisms that allow for TextureAtlasSprite
.
Gamepad Rumble API
#
authors: @johanhelsing, @nicopap
You can now use the EventWriter
system parameter to
trigger controllers force-feedback motors.
gilrs
, the crate Bevy uses for gamepad support, allows controlling
force-feedback motors. Sadly, there were no easy way of accessing the
force-feedback API in Bevy without tedious bookkeeping.
Now Bevy has the GamepadRumbleRequest
event to do just that.
fn rumble_system(
gamepads: Res<Gamepads>,
mut rumble_requests: EventWriter<GamepadRumbleRequest>,
) {
for gamepad in gamepads.iter() {
rumble_requests.send(GamepadRumbleRequest::Add {
gamepad,
duration: Duration::from_secs(5),
intensity: GamepadRumbleIntensity::MAX,
});
}
}
The GamepadRumbleRequest::Add
event triggers a force-feedback motor,
controlling how long the vibration should last, the motor to activate,
and the vibration strength. GamepadRumbleRequest::Stop
immediately stops all motors.
New Default Tonemapping Method
#
authors: @JMS55
In Bevy 0.10 we made tonemapping configurable with a ton of new tonemapping options. In Bevy 0.11 we’ve switched the default tonemapping method from “Reinhard luminance” tonemapping to “TonyMcMapface”:
Drag this image to compare
TonyMcMapface (created by Tomasz Stachowiak) is a much more neutral display transform that tries to stay as close to the input “light” as possible. This helps retain artistic choices in the scene. Notably, brights desaturate across the entire spectrum (unlike Reinhard luminance). It also works much better with bloom when compared to Reinhard luminance.
EntityRef Queries
#
authors: @james7132
EntityRef
now implements WorldQuery
, which makes it easier to query for arbitrary components in your ECS systems:
fn system(query: Query<EntityRef>) {
for entity in &query {
if let Some(mesh) = entity.get::<Handle<Mesh>>() {
let transform = entity.get::<Transform>().unwrap();
}
}
}
Note that EntityRef
queries access every entity and every component in the entire World
by default. This means that they will conflict with any “mutable” query:
/// These queries will conflict, making this system invalid
fn system(query: Query<EntityRef>, mut enemies: Query<&mut Enemy>) { }
To resolve conflicts (or reduce the number of entities accessed), you can add filters:
/// These queries will not conflict
fn system(
players: Query<EntityRef, With<Player>>,
mut enemies: Query<&mut Enemy, Without<Player>>
) {
// only iterates players
for entity in &players {
if let Some(mesh) = entity.get::<Handle<Mesh>>() {
let transform = entity.get::<Transform>().unwrap();
}
}
}
Note that it will generally still be more ergonomic (and more efficient) to query for the components you want directly:
fn system(players: Query<(&Transform, &Handle<Mesh>), With<Player>>) {
for (transform, mesh) in &players {
}
}
Screenshot API
#
authors: @TheRawMeatball
Bevy now has a simple screenshot API that can save a screenshot of a given window to the disk:
fn take_screenshot(
mut screenshot_manager: ResMut<ScreenshotManager>,
input: Res<Input<KeyCode>>,
primary_window: Query<Entity, With<PrimaryWindow>>,
) {
if input.just_pressed(KeyCode::Space) {
screenshot_manager
.save_screenshot_to_disk(primary_window.single(), "screenshot.png")
.unwrap();
}
}
RenderTarget::TextureView
#
authors: @mrchantey
The Camera
RenderTarget
can now be set to a wgpu TextureView
. This allows 3rd party Bevy Plugins to manage a Camera
‘s texture. One particularly interesting use case that this enables is XR/VR support. A few community members have already proven this out!
Improved Text Wrapping
#
authors: @ickshonpe
Previous versions of Bevy didn’t properly wrap text because it calculated the actual text prior to calculating layout. Bevy 0.11 adds a “text measurement step” that calculates the text size prior to layout, then computes the actual text after layout.
There is also a new NoWrap
variant on the BreakLineOn
setting, which can disable text wrapping entirely when that is desirable.
Faster UI Render Batching
#
authors: @ickshonpe
We got a huge UI performance win for some cases by avoiding breaking up UI batches when the texture changes but the next node is untextured.
Here is a profile of our “many buttons” stress test. Red is before the optimization and Yellow is after:
Better Reflect Proxies
#
authors: @MrGVSV
Bevy’s reflection API has a handful of structs which are collectively known as “dynamic” types.
These include DynamicStruct
, DynamicTuple
, and more, and they are used to dynamically construct types
of any shape or form at runtime.
These types are also used to create are commonly referred to as “proxies”, which are dynamic types
that are used to represent an actual concrete type.
These proxies are what powers the Reflect::clone_value
method, which generates these proxies under the hood
in order to construct a runtime clone of the data.
Unfortunately, this results in a few subtle footguns that could catch users by surprise,
such as the hashes of proxies differing from the hashes of the concrete type they represent,
proxies not being considered equivalent to their concrete counterparts, and more.
While this release does not necessarily fix these issues, it does establish a solid foundation for fixing them in the future.
The way it does this is by changing how a proxy is defined.
Before 0.11, a proxy was only defined by cloning the concrete type’s Reflect::type_name
string
and returning it as its own Reflect::type_name
.
Now in 0.11, a proxy is defined by copying a reference to the static TypeInfo
of the concrete type.
This will allow us to access more of the concrete type’s type information dynamically, without requiring the TypeRegistry
.
In a future release, we will make use of this to store hashing and comparison strategies in the TypeInfo
directly
in order to mitigate the proxy issues mentioned above.
FromReflect
Ergonomics
#
authors: @MrGVSV
Bevy’s reflection API commonly passes around data using type-erased dyn Reflect
trait objects.
This can usually be downcast back to its concrete type using
;
however, this doesn’t work if the underlying data has been converted to a “dynamic” representation
(e.g. DynamicStruct
for struct types, DynamicList
for list types, etc.).
let data: Vec<i32> = vec![1, 2, 3];
let reflect: &dyn Reflect = &data;
let cloned: Box<dyn Reflect> = reflect.clone_value();
// `reflect` really is a `Vec`
assert!(reflect.is::<Vec<i32>>());
assert!(reflect.represents::<Vec<i32>>());
// `cloned` is a `DynamicList`, but represents a `Vec`
assert!(cloned.is::<DynamicList>());
assert!(cloned.represents::<Vec<i32>>());
// `cloned` is equivalent to the original `reflect`, despite not being a `Vec`
assert!(cloned.reflect_partial_eq(reflect).unwrap_or_default());
To account for this, the FromReflect
trait can be used to convert any dyn Reflect
trait object
back into its concrete type— whether it is actually that type or a dynamic representation of it.
And it can even be called dynamically using the ReflectFromReflect
type data.
Before 0.11, users had to be manually derive FromReflect
for every type that needed it,
as well as manually register the ReflectFromReflect
type data.
This made it cumbersome to use and also meant that it was often forgotten about,
resulting in reflection conversions difficulties for users downstream.
Now in 0.11, FromReflect
is automatically derived and ReflectFromReflect
is automatically registered for all types that derive Reflect
.
This means most types will be FromReflect
-capable by default,
thus reducing boilerplate and empowering logic centered around FromReflect
.
Users can still opt out of this behavior by adding the #[reflect(from_reflect = false)]
attribute to their type.
#[derive(Reflect)]
struct Foo;
#[derive(Reflect)]
#[reflect(from_reflect = false)]
struct Bar;
fn test<T: FromReflect>(value: T) {}
test(Foo); // <-- OK!
test(Bar); // <-- ERROR! `Bar` does not implement trait `FromReflect`
Deref Derive Attribute
#
authors: @MrGVSV
Bevy code tends to make heavy use of the newtype pattern,
which is why we have dedicated derives for Deref
and DerefMut
.
This previously only worked for structs with a single field:
#[derive(Resource, Deref, DerefMut)]
struct Score(i32);
For 0.11, we’ve improved these derives by adding the #[deref]
attribute, which allows them to be used on structs with multiple fields.
This makes working with generic newtypes much easier:
#[derive(Component, Deref, DerefMut)]
struct Health<T: Character> {
#[deref] // <- use the `health` field as the `Deref` and `DerefMut` target
health: u16,
_character_type: PhantomData<T>,
}
Simpler RenderGraph Construction
#
authors: @IceSentry, @cart
Adding Node
s to the RenderGraph
requires a lot of boilerplate. In this release, we tried to reduce this
-span>