Bevy 0.15
Posted on November 29, 2024 by Bevy Contributors
Thanks to 195 contributors, 1203 pull requests, community reviewers, and our generous donors, we're happy to announce the Bevy 0.15 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.15, check out our 0.14 to 0.15 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:
- Required Components: A rethink of how spawning entities works that significantly improves the Bevy user experience
- Entity Picking / Selection: A modular system for selecting entities across contexts
- Animation Improvements: generalized entity animation, animation masks, additive blending, and animation events
- Curves: a new
Curve
trait, cyclic splines, common easing functions, color gradient curves - Reflection Improvements: Function reflection, unique reflect, remote type reflection
- Bevy Remote Protocol (BRP): A new protocol that allows external clients (such as editors) to interact with running Bevy games
- Visibility Bitmask Ambient Occlusion (VBAO): An improved GTAO algorithm that improves ambient occlusion quality
- Chromatic Aberration: A new post processing effect that simulates lenses that fail to focus light to a single point
- Volumetric Fog Improvements: "Fog volumes" that define where volumetric fog is rendered (and what form it takes), along with Point Lights and Spotlight compatibility
- Order Independent Transparency: A new opt-in transparency algorithm that improves the stability / quality of transparent objects as their distance from the camera changes
- Improved Text Rendering: We've switched to Cosmic Text for our text rendering, which significantly improves our ability to render text, especially for non-Latin-based languages that require font shaping and bidirectional text
- Gamepads as Entities: Gamepads are now represented as entities, making them much easier to interact with
- UI Box Shadows: Bevy UI nodes can now render configurable box shadows
Bevy 0.15 was prepared using our new release candidate process to help ensure that you can upgrade right away with peace of mind. We worked closely with both plugin authors and ordinary users to catch critical bugs, polish new features, and refine the migration guide. For each release candidate, we prepared fixes, shipped a new release candidate on crates.io, let core ecosystem crates update, and listened closely for show-stopping problems. A huge thanks to everyone who helped out! These efforts are a vital step towards making Bevy something that teams large and small can trust to work reliably.
Required Components #
First: buckle up because Required Components is one of the most profound improvements to the Bevy API surface since Bevy was first released.
Since Bevy's creation, Bundle
has been our abstraction for spawning an entity of a given "type". A Bundle
is just a Rust type, where each field is a Component
:
#[derive(Bundle)]
struct PlayerBundle {
player: Player,
team: Team,
sprite: Sprite,
transform: Transform,
global_transform: GlobalTransform,
visibility: Visibility,
inherited_visibility: InheritedVisibility,
view_visibility: ViewVisibility,
}
Then whenever a new player needs to be spawned, developers would initialize and insert a PlayerBundle
on an entity:
commands.spawn(PlayerBundle {
player: Player {
name: "hello".into(),
..default()
},
team: Team::Blue,
..default()
});
This inserts all of the components in PlayerBundle
, including the ones not explicitly being set. The Bundle
concept is functional (it has gotten us this far), but it is also far from ideal:
- It is an entirely new set of APIs that developers need to learn. Someone that wants to spawn a
Player
entity needs to know thatPlayerBundle
exists. - Bundle APIs don't exist at runtime after insertion ... they are an additional spawn-only concept that developers need to think about. You don't write
PlayerBundle
behaviors. You writePlayer
behaviors. - The
Player
component needs the components inPlayerBundle
to function as aPlayer
. SpawningPlayer
on its own is possible, and it likely (depending on the implementation) wouldn't function as intended. - Bundles are always "flat" (by convention). The person defining the
Player
component needs to define all of the component dependencies.Sprite
needsTransform
andVisibility
,Transform
needsGlobalTransform
,Visibility
needsInheritedVisibility
andViewVisibility
. This lack of "dependency inheritance" makes defining bundles much harder and error prone than it needs to be. It requires consumers of APIs to be intimately aware of what amounts to implementation details. And when these details change, the developer of theBundle
needs to be aware and update theBundle
accordingly. Nested bundles are supported, but they are a pain for users to work with and we have disallowed them in upstream Bevy bundles for a while now. PlayerBundle
is effectively defined by the needs of thePlayer
component, but when spawning it is possible to never mention thePlayer
symbol. Ex:commands.spawn(PlayerBundle::default())
. This is odd given thatPlayer
is the "driving concept".- Bundles introduce significant "stutter" to the API. Notice the
player: Player
andteam: Team
in the example above. - Bundles introduce additional (arguably excessive) nesting and
..default()
usage.
Every one of these points has a sizable impact on what it feels like to use Bevy on a day-to-day basis. In Bevy 0.15 we've landed Required Components, which solves these problems by fundamentally rethinking how this all works.
Required Components are the first step in our Next Generation Scene / UI effort, which aims to make Bevy a best-in-class app / scene / UI development framework. Required Components stand on their own as a direct improvement to Bevy developers' lives, but they also help set the stage for making Bevy's upcoming next generation scene system (and the upcoming Bevy Editor) something truly special.
What are they?
Required Components enable developers to define which components a given component needs:
#[derive(Component, Default)]
#[require(Team, Sprite)]
struct Player {
name: String,
}
When the Player
component is inserted, its Required Components and the components required by those components are automatically inserted as well!
commands.spawn(Player::default());
The code above automatically inserts Team
and Sprite
. Sprite
requires Transform
and Visibility
, so those are automatically inserted as well. Likewise Transform
requires GlobalTransform
and Visibility
requires InheritedVisibility
and ViewVisibility
.
This code produces the same result as the PlayerBundle
code in the previous section:
commands.spawn((
Player {
name: "hello".into(),
..default()
},
Team::Blue,
))
Much better right? The Player
type is easier and less error prone to define, and spawning it takes less typing and is easier to read.
Efficiency
We've implemented Required Components in a way that makes them effectively "free":
- Required Components are only initialized and inserted if the caller did not insert them manually. No redundancy!
- Required Components are inserted alongside the normal components, meaning (for you ECS nerds out there) there are no additional archetype changes or table moves. From this perspective, the Required Components version of the
Player
example is identical to thePlayerBundle
approach, which manually defines all of the components up front. - Required Components are cached on the archetype graph, meaning computing what required components are necessary for a given type of insert only happens once.
Component Initialization
By default, Required Components will use the Default
impl for the component (and fail to compile if one does not exist):
#[derive(Component)]
#[require(Team)] // Team::Red is the default value
struct Player {
name: String,
}
#[derive(Default)]
enum Team {
#[default]
Red,
Blue,
}
This can be overridden by passing in a function that returns the component:
#[derive(Component)]
#[require(Team(blue_team))]
struct Player {
name: String,
}
fn blue_team() -> Team {
Team::Blue
}
To save space, you can also pass a closure to the require directly:
#[derive(Component)]
#[require(Team(|| Team::Blue))]
struct Player {
name: String,
}
Isn't this a bit like inheritance?
Required Components can be considered a form of inheritance. But it is notably not traditional object-oriented inheritance. Instead it is "inheritance by composition". A Button
widget can (and should) require Node
to make it a "UI node". In a way, a Button
"is a" Node
like it would be in traditional inheritance. But unlike traditional inheritance:
- It is expressed as a "has a" relationship, not an "is a" relationship.
Button
andNode
are still two entirely separate types (with their own data), which you query for separately in the ECS.- A
Button
can require more components in addition toNode
. You aren't limited to "straight line" standard object-oriented inheritance. Composition is still the dominating pattern. - You don't need to require components to add them. You can still tack on whatever additional components you want during spawn to add behaviors in the normal "composition style".
What is happening to Bundles?
The Bundle
trait will continue to exist, and it is still the fundamental building block for insert APIs (tuples of components still implement Bundle
). Developers are still free to define their own custom bundles using the Bundle
derive. Bundles play nicely with Required Components, so you can use them with each other.
That being said, as of Bevy 0.15 we have deprecated all built-in bundles like SpriteBundle
, NodeBundle
, PbrBundle
, etc. in favor of Required Components. In general, Required Components are now the preferred / idiomatic approach. We encourage Bevy plugin and app developers to port their bundles over to Required Components.
Porting Bevy to Required Components
As mentioned above, all built-in Bevy bundles have been deprecated in favor of Required Components. We've also made API changes to take advantage of this new paradigm. This does mean breakage in a few places, but the changes are so nice that we think people won't complain too much :)
In general, we are moving in the direction specified by our Next Generation Scene / UI document. Some general design guidelines:
- When spawning an entity, generally there should be a "driving concept" component. When implementing a new entity type / behavior, give it a concept name ... that is the name of your "driving component" (ex: the "player" concept is a
Player
component). That component should require any additional components necessary to perform its functionality. - People should think directly in terms of components and their fields when spawning. Prefer using component fields directly on the "concept component" as the "public API" for the feature.
- Prefer simple APIs / don't over-componentize. By default, if you need to attach new properties to a concept, just add them as fields to that concept's component. Only break out new components / concepts when you have a good reason, and that reason is motivated by user experience or performance (and weight user experience highly). If a given "concept" (ex: a
Sprite
) is broken up into 10 components, that is very hard for users to reason about and work with. - Instead of using Asset handles directly as components, define new components that hold the necessary handles. Raw asset handles as components were problematic for a variety of reasons (a big one is that you can't define context-specific Required Components for them), so we have removed the
Component
implementation forHandle<T>
to encourage (well ... force) people to adopt this pattern.
UI
Bevy UI has benefitted tremendously from Required Components. UI nodes require a variety of components to function, and now all of those requirements are consolidated on Node
. Defining a new UI node type is now as simple as adding #[require(Node)]
to your component.
#[derive(Component)]
#[require(Node)]
struct MyNode;
commands.spawn(MyNode);
The Style
component fields have been moved into Node
. Style
was never a comprehensive "style sheet", but rather just a collection of properties shared by all UI nodes. A "true" ECS style system would style properties across components (Node
, Button
, etc), and we do have plans to build a true style system. All "computed" node properties (such as the size of the node after it has been laid out) have been moved to the ComputedNode
component.
This change has made spawning UI nodes in Bevy much cleaner and clearer:
commands.spawn(Node {
width: Val::Px(100.),
..default()
});
Compare that to what it was before!
commands.spawn(NodeBundle {
style: Style {
width: Val::Px(100.),
..default()
},
..default()
})
UI components like Button
, ImageNode
(previously UiImage
), and Text
now require Node
. Notably, Text
has been reworked to be easier to use and more component driven (we'll cover this more in the next section):
commands.spawn(Text::new("Hello there!"));
MaterialNode<M: UiMaterial>
is now a proper component for "UI material shaders", and it also requires Node
:
commands.spawn(MaterialNode(my_material));
Text
Bevy's Text API has been reworked to be simpler and more component driven. There are still two primary text components: Text
(the UI text component) and Text2d
(the world-space 2D text component).
The first thing that has changed is that these primary components are literally just a String
newtype:
commands.spawn(Text("hello".to_string()))
commands.spawn(Text::new("hello"))
commands.spawn(Text2d("hello".to_string()))
commands.spawn(Text2d::new("hello"))
Spawn one of these components, and you have text! Both of these components now require the following components:
TextFont
: configures the font / sizeTextColor
: configures the colorTextLayout
: configures how the text is laid out.
Text
, which is the UI component, also requires Node
because it is a node. Similarly, Text2d
requires a Transform
because it is positioned in world space.
Both Text
and Text2d
are a standalone "block" of text. These top level text components also contribute a single "span" of text, which is added to the "block". If you need "rich text" with multiple colors / fonts / sizes, you can add TextSpan
entities as children of either Text
or Text2d
. TextSpans
use the same TextFont
/ TextLayout
components to configure text. Each TextSpan
will contribute its span to its parent text:
// The `Text` UI node will render "hello world!", where "hello" is red and "world!" is blue
commands.spawn(Text::default())
.with_child((
TextSpan::new("hello"),
TextColor::from(RED),
))
.with_child((
TextSpan::new(" world!"),
TextColor::from(BLUE),
));
This produces the exact same output, but uses the "default" span on the top-level Text
component:
commands.spawn((
Text::new("hello"),
TextColor::from(RED),
))
.with_child((
TextSpan::new(" world!"),
TextColor::from(BLUE),
));
This "entity driven" approach to text spans replaces the "internal span array" approach used in previous Bevy versions. This yields significant benefits. First, it lets you use the normal Bevy ECS tools, such as marker components and queries, to mark a text span and access it directly. This is much easier (and more resilient) than using indices in an array, which are hard to guess and unstable as the array contents change:
#[derive(Component)]
struct NameText;
commands.spawn(Text::new("Name: "))
.with_child((
TextSpan::new("Unknown"),
NameText,
));
fn set_name(mut names: Query<&mut TextSpan, With<NameText>>) {
names.single_mut().0 = "George".to_string();
}
Text spans as entities play nicer with Bevy Scenes (including the upcoming Next Generation Scene / UI system), and allow it to integrate nicely with existing tools like entity inspectors, animation systems, timers, etc.
Sprites
Sprites are largely unchanged. In addition to the Required Components port (Sprite
now requires Transform
and Visibility
), we've also done some component consolidation. The TextureAtlas
component is now an optional Sprite::texture_atlas
field. Likewise the ImageScaleMode
component is now a Sprite::image_mode
field. Spawning sprites is now super simple!
commands.spawn(Sprite {
image: assets.load("player.png"),
..default()
});
Transforms
Transform
now requires GlobalTransform
. If you want your entity to have "hierarchical transforms", require Transform
(and it will add GlobalTransform
). If you just want your entity to have a "flat" global transform, require GlobalTransform
.
Most Bevy components that are intended to exist in world space now require Transform
.
Visibility
The Visibility
component now requires InheritedVisibility
and ViewVisibility
, meaning that you can now just require Visibility
if you want your entity to be visible. Bevy's built-in "visible" components, such as Sprite
, require Visibility
.
Cameras
The Camera2d
and Camera3d
components now each require Camera
. Camera
requires the various camera components (Frustum
, Transform
, etc.). This means that you can spawn a 2D or 3D camera like this:
commands.spawn(Camera2d::default());
commands.spawn(Camera3d::default());
Camera2d
and Camera3d
also require the components that set the relevant default render graphs and enable the default render features relevant to the 2D and 3D contexts (respectively).
You can of course explicitly set the values of the other components:
commands.spawn((
Camera3d::default(),
Camera {
hdr: true,
..default()
},
Transform {
translation: Vec3::new(1.0, 2.0, 3.0),
..default()
},
));
Bevy has a number of components that enable "camera render features": MotionBlur
, TemporalAntiAliasing
, ScreenSpaceAmbientOcclusion
, and ScreenSpaceReflections
. Some of these camera features depend on other camera feature components to function. These dependencies are now expressed and enforced using Required Components. For example, MotionBlur
now requires DepthPrepass
and MotionVectorPrepass
. This makes enabling camera features much easier!
commands.spawn((
Camera3d::default(),
MotionBlur,
))
Meshes
The old mesh approach relied on adding Handle<Mesh>
and Handle<M: Material>
components directly (via PbrBundle
and MaterialMeshBundle
), neither of which were compatible with required components.
In Bevy 0.15 you use Mesh3d
and MeshMaterial3d<M: Material>
to render a mesh in 3D:
commands.spawn((
Mesh3d(mesh),
MeshMaterial3d(material),
));
Mesh3d
requires Transform
and Visibility
.
There are also 2D equivalents:
commands.spawn((
Mesh2d(mesh),
MeshMaterial2d(material),
));
Meshlets
Bevy's "virtual geometry" implementation (similar to Nanite), has also been ported. It uses the same pattern as Mesh3d
and Mesh2d
:
commands.spawn((
MeshletMesh3d(mesh),
MeshMaterial3d(material),
));
Lights
The light port involved no major changes to the component structure. All of the spatial light types (PointLight
, DirectionalLight
, SpotLight
) now require Transform
and Visibility
, and each light component requires the relevant light-specific configuration components (ex: PointLight
requires CubemapFrusta
and CubemapVisibleEntities
).
Spawning a light of a given type is now as simple as:
commands.spawn(PointLight {
intensity: 1000.0,
..default()
});
The LightProbe
component now also requires Transform
and Visibility
.
Volumetric Fog
The FogVolume
component now requires Transform
and Visibility
, meaning you can now add volumetric fog like this:
commands.spawn(FogVolume {
density_factor: 0.2,
..default()
});
Scenes
Scenes previously used raw Handle<Scene>
components, spawned via SceneBundle
. Bevy 0.15 introduces the SceneRoot
component, which wraps the scene handle and requires Transform
and Visibility
:
commands.spawn(SceneRoot(some_scene));
Likewise, there is now DynamicSceneRoot
, which is exactly like SceneRoot
, but it wraps Handle<DynamicScene>
instead of Handle<Scene>
.
Audio
Audio also used a raw Handle<AudioSource>
spawned via an AudioBundle
. We've added a new AudioPlayer
component, which will trigger audio playback when spawned:
command.spawn(AudioPlayer(assets.load("music.mp3")));
AudioPlayer
requires the PlaybackSettings
component.
Non-standard audio from arbitrary Decodable
trait impls can use the AudioSourcePlayer
component, which also requires PlaybackSettings
.
IDE Integration
Required Components play nicely with Rust Analyzer. You can "go to definition" / press F12
on required components to navigate to where they are defined in code.
Runtime Required Components
In some cases, developers without direct control over a component might want to add additional Required Components on top of the ones provided directly on the type via #[require(Thing)]
. This is supported!
// Make `Bird` require `Wings` with a `Default` constructor.
app.register_required_components::<Bird, Wings>();
// Make `Wings` require `FlapSpeed` with a custom constructor.
app.register_required_components_with::<Wings, FlapSpeed>(|| FlapSpeed::from_duration(1.0 / 80.0));
Note that only adding Required Components is allowed. Removing Required Components from a type you do not own is explicitly not supported, as that could invalidate assumptions made upstream.
In general, this is intended to be used in very specific, targeted contexts, such as a physics plugin adding additional metadata to a core type it does not control. Adding a new component requirement could change the performance characteristics of the app or break it in unexpected ways. When in doubt, don't do it!
Chromatic Aberration #
We've added chromatic aberration, which is a common postprocessing effect that simulates lenses that fail to focus all colors of light to a single point. It's often used for impact effects and/or horror games. Our implementation uses the technique from Inside (Gjøl & Svendsen 2016), which allows the developer to customize the particular color pattern to achieve different effects.
To use it, add the ChromaticAberration
component to your camera:
commands.spawn((Camera3d::default(), ChromaticAberration));
Visibility Bitmask Ambient Occlusion (VBAO) #
Bevy 0.15 introduces a new Screen Space Ambient Occlusion (SSAO) algorithm: Visibility Bitmask Ambient Occlusion (VBAO). VBAO builds on GTAO by adding a bitmask that allows multiple "sectors" of a considered half circle to be occluded, instead of just one angle. This improves the accuracy of the technique and is particularly important on thin geometry (like the chair legs in the scene below):
Drag this image to compare
VBAO produces a significant enough quality improvement that we have replaced our old GTAO algorithm entirely. Just add the existing ScreenSpaceAmbientOcclusion
component to your camera to enable it.
Volumetric Fog Support for Point Lights and Spotlights #
Volumetric fog was introduced in Bevy 0.14. Initially, only directional lights could interact with it. In Bevy 0.15, point lights and spot lights work with it too:
To add volumetric fog to your scene, add VolumetricFog to the camera, and add VolumetricLight to directional light, point light, or spot light that you wish to be volumetric.
// Add VolumetricFog to the camera.
commands
.spawn((
Camera3d::default(),
Camera {
hdr: true,
..default()
},
))
.insert(VolumetricFog {
// This value is explicitly set to 0 since we have no environment map light.
ambient_intensity: 0.0,
..default()
});
// Add VolumetricLight to point light.
commands.spawn((
PointLight {
shadows_enabled: true,
range: 150.0,
color: RED.into(),
intensity: 1000.0,
..default()
},
VolumetricLight,
Transform::from_xyz(-0.4, 1.9, 1.0),
));
// Add VolumetricLight to spot light.
commands.spawn((
SpotLight {
intensity: 5000.0, // lumens
color: Color::WHITE,
shadows_enabled: true,
inner_angle: 0.76,
outer_angle: 0.94,
..default()
},
VolumetricLight,
Transform::from_xyz(-1.8, 3.9, -2.7).looking_at(Vec3::ZERO, Vec3::Y),
));
Fog Volumes #
Bevy 0.15 adds the concept of "fog volumes". These are entities with the FogVolume
component, which defines a bounding box for fog, which can be scaled and positioned to define where the fog will be rendered.
A camera with the VolumetricFog
component will render any FogVolume
entities in its view. Fog volumes can also define a density texture, which is a 3D texture of voxels that specify the density of the fog at each point:
FogVolume
has a density_texture_offset
, which allows the 3D texture to be "scrolled". This allows effects such as clouds "passing through" the volume:
Order Independent Transparency #
Before this feature, Bevy only used alpha blending to render transparent meshes. We now have the option to use Order Independent Transparency when rendering transparent meshes. Instead of only sorting the mesh, this will sort every pixel that contributes to a transparent triangle. This is useful if you have a lot of transparent layers in your scene.
The implementation is currently pretty simple and uses a lot of GPU memory, but it should always render perfectly accurate transparency as long as you have configured enough layers.
This feature is still a work in progress and we will keep working on improving it.
This feature was contributed to Bevy by Foresight Spatial Labs. It is based on an internal implementation they use in their applications.
User-Friendly CPU Drawing #
There are many situations where you might want to just set the color of pixels from CPU code. Procedural assets, certain art styles, or simply because it is easier. No need to bother with shaders and materials, when you just want to change a few specific pixels!
In previous versions of Bevy, this was difficult and tedious. Bevy gives you access to the raw data bytes of an Image
, but you had to compute the byte offset corresponding to your desired pixel coordinate, make sure to encode your bytes with respect to the TextureFormat
, etc. Very low level!
In Bevy 0.15, there are now user-friendly APIs for reading and writing the colors of pixels in an Image
. The tricky low-level details are dealt with for you! You can even use bevy_color
's fancy color space APIs!
fn my_system(mut images: ResMut<Assets<Image>>, mut commands: Commands) {
// Create a new image.
let mut image = Image::new_fill(
// 64x64 size
Extent3d {
width: 64,
height: 64,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&Srgba::WHITE.to_u8_array(),
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::all(),
);
// This is new:
// Make the pixel at x: 23, y: 32 magenta
image.set_color_at(23, 32, Color::srgb(1.0, 0.0, 1.0))
.expect("Error writing color");
// Set the pixel at 10,10 to a color specified using the Oklch color space:
image.set_color_at(10, 10, Color::oklch(0.3, 0.2, 0.5))
.expect("Error writing color");
// read the bytes of the pixel we just wrote:
let bytes = image.pixel_bytes(UVec3::new(10, 10, 0)).unwrap();
// read the (approximate) color back (as sRGB):
let color = image.get_color_at(10, 10);
// We could add our new image to Bevy's assets
// and spawn a sprite to display it:
commands.spawn(Sprite {
image: images.add(image),
..default()
});
}
Note: The Color
-based methods are lossy. They have to convert to/from the Image
's TextureFormat
. If you read back the color you wrote, it will be slightly different.
Entity Picking / Selection #
Being able to click on objects to select them is a vital and seemingly simple task in any game. Since 2020, doing this in Bevy has largely meant pulling in @aevyrie
's beloved ecosystem crate, bevy_mod_picking
and its simple raycasting companion bevy_mod_raycast
.
Over the years, this crate has been refined and battle-tested, both by Foresight Spatial Labs (a CAD-creating, Bevy-using company where Aevyrie works) and the broader open source community of game developers that have used it for everything from first-person-shooters to point-and-click adventures. Bevy is thrilled to have had the chance to work with the team behind bevy_mod_picking
and have adopted the project wholesale into Bevy itself. Integrating a large project is a ton of work, and we're incredibly grateful to the contributors who have made bevy_picking
a stable, first-class feature of the engine.
The new bevy_picking
crate follows the existing modular architecture closely:
- Inputs are gathered from mouse, touch and pen devices. Each pointing device (humans are equipped with 10 by default) gets a screen-space
PointerLocation
. - Each modular backend performs the domain-specific work (like raycasting) of figuring out how these pointer locations map to
PointerHits
on objects that they're watching. - The hit information from each backend is combined and sorted to produce a coherent
HoverMap
, which lists which entities each pointer is hovering over. - High level events (both ordinary events and observers!) are emitted for each hovered entity, capturing complex behavior such as clicking, dragging or releasing various objects.
In Bevy 0.15, we're shipping three first-party picking backends for UI, sprites, and meshes. Each of these comes with its own caveats for now:
- UI: both the legacy
Interaction
and newPickingInteraction
components exist for now, with subtle behavioral differences. - Sprites: picking always uses the full rectangle, and alpha transparency is not taken into account.
- Mesh: this is a naive raycast against the full mesh. If you run into performance problems here, you should use simplified meshes and an acceleration data structure like a BVH to speed this up. As a result, this functionality is currently disabled by default. It can be enabled by adding the
MeshPickingPlugin
.
We expect both bevy_rapier
and avian
(the two most popular ecosystem physics crates for Bevy) to add their own accelerated collider picking backends to work with the newly upstreamed API. Unless you're debugging, building an editor or really care about the exact triangles of raw meshes, you should use one of those crates for efficient mesh picking.
Usage
There are two good ways to get started with the API:
First, you might want to quickly update the state of your objects (be they UI or game objects) based on what is being done to them, typically highlighting them or changing their color. For that, simply query for changes to the PickingInteraction
component, which will change based on the current picking state.
Second, you might want to respond dynamically to various pointer-powered events. For that, we recommend using observers. Here, we're spawning a simple text node and responding to pointer events:
// UI text that prints a message when clicked:
commands
.spawn(Text::new("Click Me!"))
.observe(on_click_print_hello);
// A cube that spins when dragged:
commands
.spawn((
Mesh3d(meshes.add(Cuboid::default())),
MeshMaterial3d(materials.add(Color::WHITE)),
))
.observe(on_drag_spin);
}
fn on_click_print_hello(click: Trigger<Pointer<Click>>) {
println!("{} was clicked!", click.entity());
}
fn on_drag_spin(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
let mut transform = transforms.get_mut(drag.entity()).unwrap();
transform.rotate_y(drag.delta.x * 0.02);
}
If you want to override how an entity interacts with picking, add the PickingBehavior
component to them and configure it to meet your needs.
Bubbling Observers #
Virtually every pointer interaction (like mouse click) is rare (humans are slow!), and often requires a complex response.
This pattern is particularly useful in UI, where unhandled interactions are often intended for the pane that contains the entity that's on top, but is also valuable for in-game interactions: clicking on a unit's sword should select the unit!
To support this, we've extended the Event
trait to include an associated Traversal
type and an associated AUTO_PROPAGATE
constant. This behavior is opt-in: when you derive the Event
type, these are set to ()
and false
respectively.
For the Pointer<E>
event type, we've chosen to implement this as:
impl <E> Event for Pointer<E>{
type Traversal = &Parent;
const AUTO_PROPAGATE: bool = true;
}
This means that, unless you call Trigger::propagate(false)
, pointer events will be bubbled up the hierarchy (accessing the Entity
stored in the Parent
component) until it reaches the entity root.
Any type that implements the Traversal
trait can be used as the associated type and can access arbitrary read-only query data from the world. While using the standard entity hierarchy is a sensible choice for many applications, bubbling can be used for arbitrary event propagation using your own proto-relations. Let us know what you cook up: user feedback is indispensable for building a better Bevy!
Virtual Geometry Improvements #
Virtual geometry (the meshlet
feature) got a ton of improvements in Bevy 0.15. It's still not production ready, and will remain as an experimental module, but performance has been greatly improved upon since the last release.
For all the interesting details, read the author's blog post.
For existing users of this feature:
- Your GPU must now support
WgpuFeatures::SHADER_INT64_ATOMIC_MIN_MAX
to use this feature. As forewarned in the previous release, older GPUs may no longer be compatible. - You must regenerate your MeshletMesh assets. MeshletMesh assets generated in Bevy 0.14 are not compatible with Bevy 0.15.
- Make sure you read both the migration guide and the updated rustdoc for full details on how to upgrade your project.
Animation Masks #
Animations now support masking out animation targets (joints). This is implemented at the level of animation blend graphs (AnimationGraph
) and can be used to play different animations on separate parts of the same model without interfering with one another. For example, you can play separate animations on a character's upper and lower body.
In this video, the fox's head and legs are playing two separate animations, thanks to animation masks:
Generalized Animation #
AnimationClip
can now be used to animate component fields with arbitrary curves.
animation_clip.add_curve_to_target(
animation_target_id,
AnimatableCurve::new(
animated_field!(TextFont::font_size),
// Oscillate the font size during the length of the animation.
FunctionCurve::new(
Interval::UNIT,
|t| 25.0 * f32::sin(TAU * t) + 50.0
)
)
);
This works for any named field and uses the new Curve
API, which supports arbitrary curve types. Animating Transform
fields will likely be the most common use case:
animation_clip.add_curve_to_target(
animation_target_id,
AnimatableCurve::new(
animated_field!(Transform::translation),
// Construct a `Curve<Vec3>`` using a built-in easing curve constructor.
EasingCurve::new(
vec3(-10., 2., 0.),
vec3(6., 2., 0.),
EaseFunction::CubicInOut,
)
)
);
Bevy's internal animation handling for things like GLTF animations uses the same API!
If you need more complicated logic than "animate a specific component field", you can implement AnimatableProperty
, which can be used in AnimatableCurve
in place of animated_field!
.
Animation Graph: Additive Blending #
Bevy's animation graphs (AnimationGraph
), which are used to combine simultaneously playing animations, now support additive blending.
Additive blending is a technique which allows separately authored animations to be applied on top of an arbitrary base animation. For instance, an animation in which a character swings a weapon may be applied additively on top of a walking or running animation.
Within an animation graph itself, this is accomplished by using Add
nodes. The situation above might be described with an animation graph that looks something like this (weights omitted):
┌─────┐
│Walk ┼─┐
└─────┘ │ ┌─────┐
┼─┼Blend┼─┐
┌─────┐ │ └─────┘ │ ┌─────┐ ┌─────┐
│Run ┼─┘ ┼─┤Add ┼───┼Root │
└─────┘ ┌─────┐ │ └─────┘ └─────┘
│Swing┼─┘
└─────┘
The Add
node functions by taking its first input (here, a blend of the 'Walk' and 'Run' clips) as-is and then applying the subsequent inputs additively on top of it. In code, the graph might be constructed as follows:
let mut animation_graph = AnimationGraph::new();
// Attach an `Add` node to the root.
let add_node = animation_graph.add_additive_blend(1.0, animation_graph.root);
// Add the `Blend` node and the additive clip as children; the `Blend` result
// will be used as the base because it is listed first.
let blend_node = animation_graph.add_blend(1.0, add_node);
animation_graph.add_clip(swing_clip_handle, 1.0, add_node);
// Finally, blend the 'Walk' and 'Run' clips to use as a base.
animation_graph.add_clip(walk_clip_handle, 0.5, blend_node);
animation_graph.add_clip(run_clip_handle, 0.5, blend_node);
Animation Events #
Triggering gameplay events at specific points in an animation is a common pattern for synchronizing the visual, audible, and mechanical parts of your game. In Bevy 0.15 we've added "animation event" support to AnimationClip
, which means that you can trigger a specific Event
at a given point in time during AnimationClip
playback:
#[derive(Event, Clone)]
struct PlaySound {
sound: Handle<AudioSource>,
}
// This will trigger the PlaySound event at the 1.5 second mark in `animation_clip`
animation_clip.add_event(1.5, PlaySound {
sound: assets.load("sound.mp3"),
});
app.add_observer(|trigger: Trigger<PlaySound>, mut commands: Commands| {
let sound = trigger.event().sound.clone();
commands.spawn(AudioPlayer::new(sound));
});
You can also trigger events for specific animation targets (such as bones):
animation_clip.add_event_to_target(AnimationTargetId::from_iter(["LeftLeg", "LeftFoot"], 0.5, TouchingGround);
This enables things like "triggering a dust effect each time a foot touches the ground in an animation":
Bevy Remote Protocol (BRP) #
The Bevy Remote Protocol allows the ECS of a running Bevy application to be interacted with remotely. This can be used, for example, to inspect and edit entities and their components at runtime. We anticipate that this will be used to create things like inspectors which monitor the content of the ECS from a separate process. We're planning on using BRP in the upcoming Bevy Editor to communicate with remote Bevy apps.
Currently, you can use BRP to:
- Get the serialized values of a set of components from an entity
- Perform a query for all entities matching a set of components and retrieve the matching values
- Create a new entity with a given set of component values
- For a given entity, insert or remove a set of components
- Despawn an entity
- Reparent one or more entities
- List the components registered in the ECS or present on an entity
Here is the minimal app setup required to use BRP over HTTP:
fn main() {
App::new()
.add_plugins((
DefaultPlugins,
// The "core" plugin, which handles remote requests provided by transports
RemotePlugin::default(),
// Provides remote request transport via HTTP
RemoteHttpPlugin::default(),
))
.run();
}
Here is a sample request:
{
"method": "bevy/get",
"id": 0,
"params": {
"entity": 4294967298,
"components": [
"bevy_transform::components::transform::Transform"
]
}
}
And here is a sample response:
{
"jsonrpc": "2.0",
"id": 0,
"result": {
"bevy_transform::components::transform::Transform": {
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
"translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
}
}
}
Gamepads as Entities #
Gamepads are now represented as entities, which makes them easier to work with! The Gamepad
component provides button and axis state, as well as metadata such as the vendor and product ID. The GamepadSettings
component provides configurable settings for a given Gamepad
, such as deadzones and sensitivity. The name of the gamepad is now stored in Bevy's standard Name
component.
In Bevy 0.14, you might write:
fn gamepad_system(
gamepads: Res<Gamepads>,
button_inputs: Res<ButtonInput<GamepadButton>>,
button_axes: Res<Axis<GamepadButton>>,
axes: Res<Axis<GamepadAxis>>,
) {
for gamepad in &gamepads {
if button_inputs.just_pressed(
GamepadButton::new(gamepad, GamepadButtonType::South)
) {
info!("just pressed South");
}
let right_trigger = button_axes
.get(GamepadButton::new(
gamepad,
GamepadButtonType::RightTrigger2,
))
.unwrap();
if right_trigger.abs() > 0.01 {
info!("RightTrigger2 value is {}", right_trigger);
}
let left_stick_x = axes
.get(GamepadAxis::new(gamepad, GamepadAxisType::LeftStickX))
.unwrap();
if left_stick_x.abs() > 0.01 {
info!("LeftStickX value is {}", left_stick_x);
}
}
}
In 0.15, we can write this much more simply as:
fn gamepad_system(gamepads: Query<&Gamepad>) {
for gamepad in &gamepads {
if gamepad.just_pressed(GamepadButton::South) {
println!("just pressed South");
}
let right_trigger = gamepad.get(GamepadButton::RightTrigger2).unwrap();
if right_trigger.abs() > 0.01 {
info!("RightTrigger2 value is {}", right_trigger);
}
let left_stick_x = gamepad.get(GamepadAxis::LeftStickX).unwrap();
if left_stick_x.abs() > 0.01 {
info!("LeftStickX value is {}", left_stick_x);
}
}
}
Much better!
Box Shadows #
Bevy UI now supports box shadows! Box shadows can be used to achieve particular artistic effects, such as creating a sense of depth to cue to users that an element is interactable.
By adding the new BoxShadow
component to any Node
entity, you can draw a pretty shadow behind your widgets.
#[derive(Component)]
pub struct BoxShadow {
/// The shadow's color
pub color: Color,
/// Horizontal offset
pub x_offset: Val,
/// Vertical offset
pub y_offset: Val,
/// How much the shadow should spread outward.
///
/// Negative values will make the shadow shrink inwards.
/// Percentage values are based on the width of the UI node.
pub spread_radius: Val,
/// Blurriness of the shadow
pub blur_radius: Val,
}
We have plans for future improvements: enable using shadows to create an inset / sunken look, and adding shadow support for images and text. If those possibilities excite you, get involved! We love helping new contributors land the features they care about.
Cosmic Text #
Historically, Bevy has used the ab_glyph
library to render text. This handled simple latin text rendering reasonably well. But Bevy aims to be a generic app framework usable with any language, and there were a number of areas where ab_glyph
wasn't meeting our needs.
The Rust text space has evolved significantly since we selected ab_glyph
. Fortunately there are a number of good options now. We chose cosmic-text
because of its robust feature set and its use in production applications (Iced, Cosmic Desktop, Zed, Lapce, etc). Notably, cosmic-text
gives us support for:
- Font Shaping: The ability to take a string of character codes and perform layout and transformation rules. This can involve moving, modifying, and combining characters (such as ligatures). This is extremely important for non-Latin-based languages.
- System Font Loading: The ability to scan for the available fonts installed on a system and load them.
- Bidirectional Text: Not all languages go from left to right! Cosmic Text gives us support for bidirectional text.
- Text Editing: Cosmic Text has its own internal text editing model, which we can take advantage of.
In Bevy 0.15 we ported our text rendering to cosmic-text
. This was largely an internal change (unlike the other "high level" text API changes this release, such as the port to Required Components).
That being said, you will definitely notice our improved ability to render text! Here is Bevy rendering Arabic text, right-to-left, using the Noto Sans Arabic font:
Note that we haven't yet wired up cosmic-text
's "system font loading" features, but we're working on it!
UI Scrolling #
Bevy 0.15 introduces scrolling support for UI containers.
A UI Node
with the overflow
property set to Overflow::scroll()
will offset its contents by the positive offset_x
and offset_y
values of the ScrollPosition
component on the node.
Scrolling is done by modifying ScrollPosition
directly; there is currently no built-in scroll input handler. A new scroll
example demonstrates handling simple mouse wheel scrolling. Axes of a node without OverflowAxis::Scroll
will ignore changes to ScrollPosition
.
Retained Rendering World #
For awhile now, Bevy has had a "parallel pipelined renderer". To enable this, we added a Render World, in addition to the Main World (a World
holds ECS data like Entities, Components, and Resources). The Main World is the source of truth for app logic. While the Render World is rendering the current frame, the Main World can be simulating the next frame. There is a brief "extract step", where we synchronize the two and copy relevant data from the Main World to the Render World.
In previous versions of Bevy, we employed an "immediate mode" approach to Main World -> Render World synchronization: we fully cleared the Render World entities every frame. This accomplished a couple of things:
- It allowed us to ensure entity IDs "lined up", allowing us to reuse entities in both places.
- It prevented us from needing to solve the "desync problem". By clearing every frame and re-syncing, we ensure the two Worlds are always perfectly in sync.
There was also precedent for the "immediate mode" pipelined rendering approach: Bungie's Destiny renderer uses it to great effect!
However we learned pretty quickly that clearing every frame had major downsides:
- The clearing process itself had overhead.
- "Table" ECS storage could be expensive to rebuild every frame relative to alternatives, due to "archetype moves". As a result, we employed many workarounds such as moving storage outside of the ECS.
- Full resyncs every frame meant re-doing work that didn't need redoing. ECS gives us a nice global view of how our data is changing. We should take advantage of that!
In Bevy 0.15 we switched to a "retained Render World". We no longer clear each frame. We no longer rely on a shared entity ID space. Instead:
- Each world has its own entities
- For entities that are related, we store that relationship as components (ex: Render World entities have a
MainEntity
component and Main World entities have aRenderEntity
component). If a Main World entity withSyncToRenderWorld
is spawned, we spawn an equivalent in the Render World. If a Main World entity is despawned, we despawn the relevant entity in the Render World.
Ensuring synchronization is perfect is not an easy problem. Plugging all of the holes took a lot of time this cycle and we will likely continue to evolve our synchronization strategy in the future. But we think "retained" is fundamentally better for Bevy, and we're excited to have this foundation laid!
Curves #
The new Curve<T>
trait provides a shared interface for curves, describing how values of type T
change as we vary a f32
parameter t
over some domain.
What's changing, and the domain that it's changing over are both incredibly flexible. You might choose to set the generic parameter T
to anything from position, to damage, to colors (like we did to create a powerful abstraction for color gradients).
The progress parameter t
often represents time (like in animation), but it can also represent things like a fraction/percentage of progress between a starting and ending value or distance (such as curves that are mapped into 2D or 3D space),
Constructing Curves
Each curve made be defined in a variety of ways. For example, a curve may be:
- defined by a function
- interpolated from samples
- constructed using splines
- produced by an easing function
Take a look at the constructors on the Curve<T>
trait for more details.
Modifying curves
Procedurally modifying curves is a powerful tool for both creating curves with the desired behavior and dynamically altering them.
Bevy 0.15 provides a number of flexible adaptors for taking an existing curve and modifying its output and/or parametrization.
For example:
let timed_angles = [
(0.0, 0.0),
(1.0, -FRAC_PI_2),
(2.0, 0.0),
(3.0, FRAC_PI_2),
(4.0, 0.0)
];
// A curve interpolating our list of (time, angle)-pairs. At each time, it
// produces the angle, so it is a `Curve<f32>` parametrized over `[0, 4]`.
let angle_curve = UnevenSampleAutoCurve::new(timed_angles).unwrap();
// Interpret these angles as angles of rotation for a `Curve<Rot2>`.
let rotation_curve = angle_curve.map(Rot2::radians);
// Change the parameterizing interval so that the whole loop happens in
// only 1 second instead of 4.
let fast_rotation_curve = rotation_curve.reparametrize_linear(Interval::UNIT).unwrap();
A number of other adaptors are available. For instance:
- a curve may be reversed, repeated, or ping-ponged
- two curves may be chained together to form a longer curve
- two curves may be zipped together to form a curve valued in tuples
Sampling from curves
Sampling is the process of asking "what is the value of this curve at some particular value of t
". To do so, just call Curve::sample
!
Much like how vector graphics can be rasterized into pixels, curves can be rasterized into regular, discretized intervals. By resampling into an approximation derived from sample interpolation on the original curve, we can make curves of diverse origin uniform at the level of data.
While this may seem exotic, this technique is critical for serializing curves or approximating properties via numerical methods.
// A curve defined by a function, which may be challenging to store as data.
let exponential_curve = FunctionCurve::new(
interval(0.0, 10.0).unwrap(),
|t| f32::exp(2.0 * t)
);
// A curve approximating the original by resampling on 100 segments.
// Internally, this just holds the samples and the parameter interval.
let raster_curve = exponential_curve.resample_auto(100).unwrap();
Common Easing Functions #
"Easing functions" can be used to easily construct curves that interpolate between two values. There are many "common" easing functions that each have a different "character" to them. These are often used in "tweening" scenarios to give life to the interpolation.
Bevy 0.15 adds a new Ease
trait, which defines how to interpolate a value of a given type. The Ease
types include:
- vector types (
f32
,Vec2
,Vec3
, ...); - direction types (
Dir2
,Dir3
,Dir3A
); - rotation types (
Rot2
,Quat
).
We've also added an EaseFunction
enum, which defines many common easing functions. The new EasingCurve
type uses these as inputs to define a final Curve
from the given easing parameters.
For example, we can use an easing function to interpolate between two rotations:
// Ease between no rotation and a rotation of angle PI/2 about the y-axis.
let rotation_curve = EasingCurve::new(
Quat::IDENTITY,
Quat::from_rotation_y(FRAC_PI_2),
EaseFunction::ElasticInOut,
)
.reparametrize_linear(interval(0.0, 4.0).unwrap())
.unwrap();
Cyclic Splines #
Most cubic spline constructions now support creating a closed loop instead of just a path, if desired. This can be convenient for constructing things like periodic paths for NPCs or other game entities.
The only difference is that to_curve_cyclic
must be called in place of to_curve
. The supported spline constructions are:
- Hermite splines (
CubicHermite
), - Cardinal splines (
CubicCardinalSpline
), - B-splines (
CubicBSpline
), - Linear splines (
LinearSpline
).
PartialReflect
#
Bevy boasts a powerful reflection system that allows you to introspect and build types at runtime. It works by passing around data as Reflect
trait objects like Box<dyn Reflect>
. This has the effect of erasing the compile-time type information, allowing data to be stored and moved around without having to know the exact type behind the trait object.
Because of this type erasure, bevy_reflect
can also get away with some interesting tricks. For instance, there are many cases where a type needs to be built up field-by-field, such as during deserialization. This works fine when you know the type at compile-time, but it becomes very challenging to do at runtime. To solve this, bevy_reflect
has a concept of dynamic types.
Dynamic types exist as a way to dynamically construct and store reflected data in a way that appears like a concrete type. Behind the scenes, bevy_reflect
uses these types to build up a representation of the target type. And it can do so since we hide the actual type behind the dyn Reflect
trait object.
Unfortunately, this comes with a very common issue: it becomes very easy to accidentally believe a dyn Reflect
is a concrete type when it's actually a dynamic type representing that concrete type.
To address this problem, Bevy 0.15 has reworked the Reflect
trait based on the Unique Reflect RFC. This splits it into two separate traits: Reflect
and PartialReflect
.
PartialReflect
is much like the Reflect
trait of previous versions. It allows access to fundamental reflection capabilities and allows for type-erasure behind a dyn PartialReflect
trait object. It allows for both concrete types and dynamic types to be used interchangeably.
Reflect
, on the other hand, has become a much stricter trait. It's a subset of PartialReflect
that guarantees the underlying type beneath the trait object is exactly the concrete type it says it is.
This split allows reflection-based APIs and user code to be more explicit about the dynamic-ness of the trait objects they're working with. It moves the knowledge of whether a type is dynamic or not to compile-time, preventing many common pitfalls of working with dynamic types, including knowing when they need to be handled separately.
Reflect Remote Types #
The bevy_reflect
crate relies on types implementing Reflect
in order to make them reflectable. Fields of structs and enums that don't implement Reflect
must be specifically ignored with #[reflect(ignore)]
. And due to Rust's orphan rule, this is often the case for types not owned by the current crate.
Following serde
's example, Bevy 0.15 introduces a way to reflect remote types using a new #[reflect_remote(...)]
attribute macro. This allows users to define a model for reflection to base its behavior on, while still operating with the actual type.
// Pretend this type is defined in a crate called `external_crate`
#[derive(Default)]
struct Name {
pub value: String,
}
// We can define our model, including other derives and reflection attributes
#[reflect_remote(external_crate::Name)]
#[derive(Default)]
#[reflect(Default)]
struct NameWrapper {
pub value: String,
}
// Now we can use `Name` as a field in a reflected type without having to ignore it
#[derive(Reflect)]
struct Player {
#[reflect(remote = NameWrapper)]
name: external_crate::Name,
}
Under the hood, this works by transforming our model into a transparent wrapper around the actual type:
#[repr(transparent)]
struct NameWrapper(pub external_crate::Name);
The macro then uses the model to generate all the reflection trait implementations, driven by a new ReflectRemote
trait for swapping between our wrapper and the remote type. Compile-time assertions are also generated to help ensure the model and the actual type stay in sync.
While this feature has many aspects complete, including generic support, enum support, and nesting, there are still some limitations we hope to address in future releases, including support for reflecting a remote type with private fields.
The Reflectable
Trait #
bevy_reflect
is powered by many different traits working together to provide the full reflection API. These include traits like Reflect
, but also other traits like TypePath
, Typed
, and GetTypeRegistration
.
This can make adding the right bounds on generic parameters a bit confusing, and it's easy to forget to include one of these traits.
To make this simpler, 0.15 introduces the Reflectable
trait. All the traits listed above are supertraits of Reflectable
, allowing it to be used in place of all of them where necessary.
Function Reflection #
Rust's options for working with functions in a dynamic context are limited. We're forced to either coerce the function to a function pointer (e.g. fn(i32, i32) -> i32
) or turn it into a trait object (e.g. Box<dyn Fn(i32, i32) -> i32>
).
In both cases, only functions with the same signature (both inputs and outputs) can be stored as an object of the same type. For truly dynamic contexts, such as working with scripting languages or fetching functions by name, this can be a significant limitation.
Bevy's bevy_reflect
crate already removes the need for compile-time knowledge of types through reflection. In Bevy 0.15, functions can be reflected as well!
This feature is opt-in and requires the reflect_functions
feature to be enabled on bevy
(or the functions
feature on bevy_reflect
if using that crate directly).
It works by converting regular functions which arguments and return type derive Reflect
into a DynamicFunction
type using a new IntoFunction
trait.
fn add(a: i32, b: i32) -> i32 {
a + b
}
let function = add.into_function();
With a DynamicFunction
, we can then generate our list of arguments into an ArgList
and call the function:
let args = ArgList::new()
.push_owned(25_i32)
.push_owned(75_i32);
let result = function.call(args);
Calling a function returns a FunctionResult
which contains our Return
data or a FunctionError
if something went wrong.
match result {
Ok(Return::Owned(value)) => {
let value = value.try_take::<i32>().unwrap();
println!("Got: {}", value);
}
Err(err) => println!("Error: {:?}", err),
_ => unreachable!("our function always returns an owned value"),
}
Closure Reflection
This feature doesn't just work for regular functions—it works on closures too!
For closures that capture their environment immutably, we can continue using DynamicFunction
and IntoFunction
. For closures that capture their environment mutably, there's DynamicFunctionMut
and IntoFunctionMut
.
let mut total = 0;
let increment = || total += 1;
let mut function = increment.into_function_mut();
function.call(ArgList::new()).unwrap();
function.call(ArgList::new()).unwrap();
function.call(ArgList::new()).unwrap();
// Drop the function to release the mutable borrow of `total`.
// Alternatively, our last call could have used `call_once` instead.
drop(function);
assert_eq!(total, 3);
FunctionInfo
Reflected functions hold onto their type metadata via FunctionInfo
which is automatically generated by the TypedFunction
trait. This allows them to return information about the function including its name, arguments, and return type.
let info = String::len.get_function_info();
assert_eq!(info.name().unwrap(), "alloc::string::String::len");
assert_eq!(info.arg_count(), 1);
assert!(info.args()[0].is::<&String>());
assert!(info.return_info().is::<usize>());
One thing to note is that closures, anonymous functions, and function pointers are not automatically given names. For these cases, names can be provided manually.
The same is true for all arguments including self
arguments: names are not automatically generated and must be supplied manually if desired.
Using FunctionInfo
, a DynamicFunction
will print out its signature when debug-printed.
dbg!(String::len.into_function());
// Outputs:
// DynamicFunction(fn alloc::string::String::len(_: &alloc::string::String) -> usize)
Manual Construction
For cases where IntoFunction
won't work, such as for functions with too many arguments or for functions with more complex lifetimes, DynamicFunction
can also be constructed manually.
// Note: This function would work with `IntoFunction`,
// but for demonstration purposes, we'll construct it manually.
let add_to = DynamicFunction::new(
|mut args| {
let a = args.take::<i32>()?;
let b = args.take_mut::<i32>()?;
*b += a;
Ok(Return::unit())
},
FunctionInfo::named("add_to")
.with_arg::<i32>("a")
.with_arg::<&mut i32>("b")
.with_return::<()>(),
);
The Function Registry
To make it easier to work with reflected functions, a dedicated FunctionRegistry
has been added. This works similarly to the TypeRegistry
where functions can be registered and retrieved by name.
let mut registry = FunctionRegistry::default();
registry
// Named functions can be registered directly
.register(add)?
// Unnamed functions (e.g. closures) must be registered with a name
.register_with_name("add_3", |a: i32, b: i32, c: i32| a + b + c)?;
let add = registry.get("my_crate::math::add").unwrap();
let add_3 = registry.get("add_3").unwrap();
For better integration with the rest of Bevy, a new AppFunctionRegistry
resource has been added along with registration methods on App
.
The Function
Trait
A new reflection trait—appropriately called Function
—has been added to correspond to functions.
Due to limitations in Rust, we're unable to implement this trait for all functions, but it does make it possible to pass around a DynamicFunction
as a PartialReflect
trait object.
#[derive(Reflect)]
#[reflect(from_reflect = false)]
struct EventHandler {
callback: DynamicFunction<'static>,
}
let event_handler: Box<dyn Struct> = Box::new(EventHandler {
callback: (|| println!("Event fired!")).into_function(),
});
let field = event_handler.field("callback").unwrap();
if let ReflectRef::Function(callback) = field.reflect_ref() {
callback.reflect_call(ArgList::new()).unwrap();
}
Limitations
While this feature is quite powerful already, there are still a number of limitations.
Firstly, IntoFunction
/IntoFunctionMut
only work for functions with up to 16 arguments, and only support returning borrowed data where the lifetime is tied to the first argument (normally self
in methods).
Secondly, the Function
trait can't be implemented for all functions due to how the function reflection traits are defined.
Thirdly, all arguments and return types must have derived Reflect
. This can be confusing for certain types such as &str
since only &'static str
implements Reflect
and its borrowed version would be &&'static str
.
Lastly, while generic functions are supported, they must first be manually monomorphized. This means that if you have a generic function like fn foo<T>()
, you have to create the DynamicFunction
like foo::<i32>.into_function()
.
Most of these limitations are due to Rust itself. The lack of variadics and issues with coherence are among the two biggest difficulties to work around. Despite this, we will be looking into ways of improving the ergonomics and capabilities of this feature in future releases.
We already have a PR up to add support for overloaded functions: functions with a variable number of arguments and argument types.
TypeInfo
Improvements #
With bevy_reflect
, compile-time type information can be retrieved from a reflected type as TypeInfo
.
Bevy 0.15 adds many improvements and convenience methods for working with TypeInfo
.
Generic Parameter Info
The first addition is the ability to get information about a type's generic parameters. This not includes the parameter's type, but also its name and—if it's a const parameter—its default value.
#[derive(Reflect)]
struct MyStruct<T>(T);
let generics = MyStruct::<f32>::type_info().generics();
let t = generics.get(0).unwrap();
assert_eq!(t.name(), "T");
assert!(t.ty().is::<f32>());
assert!(!t.is_const());
Nested TypeInfo
Pretty much every type in Rust is made up of other types. Structs, maps, lists—they all contain other types.
In previous versions of Bevy, TypeInfo
granted you limited access to type information of these nested types. It mostly just provided the type's TypeId
and TypePath
.
However, in Bevy 0.15, you can now directly access the TypeInfo
of these nested types.
#[derive(Reflect)]
struct Row {
id: usize
}
let struct_info: StructInfo = Row::type_info().as_struct();
let field: NamedField = struct_info.field("id").unwrap();
// `NamedField` now exposes a way to fetch the `TypeInfo` of the field's type
let field_info: TypeInfo = field.type_info().unwrap();
assert!(field_info.is::<usize>());
TypeInfo
Convenience Casts
In most cases, TypeInfo
needs to first be pattern matched to the correct variant in order to gain full access to the type's compile-time information. This can be mildly annoying when you already know the variant ahead of time. This often occurs when writing tests, but also shows up when trying to get the type's ReflectRef
data along with its TypeInfo
. It tends to looks something like:
// We have to pattern match on `ReflectRef`...
let ReflectRef::List(list) = reflected_value.reflect_ref() else {
panic!("expected a list");
};
// ...and still need to pattern match on `TypeInfo`
let TypeInfo::List(list_info) = reflected_value.get_represented_type_info().unwrap() else {
panic!("expected a list info");
};
In such cases, the variant is already verified via the ReflectRef
but the TypeInfo
must still be pattern matched regardless.
In Bevy 0.15, convenience methods have been added to TypeInfo
, ReflectRef
, ReflectMut
, and ReflectOwned
to conveniently cast to the expected variant or return an error upon failure.
// We can simply verify the kind of our reflected value once...
let ReflectRef::List(list) = reflected_value.reflect_ref() else {
panic!("expected a list");
};
// ...and just assert the `TypeInfo`
let list_info = reflected_value.get_represented_type_info().unwrap().as_list().unwrap();
If the .as_list()
cast fails in the snippet above, it will return an error detailing what kind we expected (i.e. List
) and what we actually got (e.g. Array
, Struct
, etc.).
And this works in the opposite direction as well:
let TypeInfo::List(list_info) = reflected_value.get_represented_type_info().unwrap() else {
panic!("expected a list info");
};
let list = reflected_value.reflect_ref().as_list().unwrap();
The Type
Type #
Rust's TypeId
is a unique identifier for a type, making it a perfect candidate for use as a key in mappings and for checking whether two types are the same at runtime. And since it's essentially just two u64
values, it's extremely cheap to copy, compare, and hash.
One of the downsides to using TypeId
, though, is that it doesn't contain any other information about the type, including its name. This can make debugging somewhat frustrating as you can't easily tell which type a TypeId
corresponds to.
Since bevy_reflect
makes heavy use of TypeId
, 0.15 introduces a new type to help alleviate the debugging issue while still maintaining the benefits of TypeId
: Type
.
Type
is a simple wrapper around TypeId
that also stores the TypePathTable
. Like TypeId
it's Copy
, Eq
, and Hash
, delegating to the underlying TypeId
for the latter two. But unlike TypeId
, its Debug
implementation will print the type path of the type it represents. This debuggability comes at the cost of an extra 32 bytes, but may often be well worth it, especially if that data would have been stored elsewhere anyway.
It can be constructed from any type that implements TypePath
:
let ty = Type::of::<String>();
let mut map = HashMap::<Type, i32>::new();
map.insert(ty, 25);
let debug = format!("{:?}", map);
assert_eq!(debug, "{alloc::string::String: 25}");
Reflection support for Sets #
Inside of bevy_reflect
, every reflected Rust object ends up being mapped to one of a handful of ReflectKind
variants.
Before Bevy 0.15, sets (like HashSet
) were treated as opaque "values": there was no way to view or modify their contents via reflection. With these changes, we can now properly represent sets of all kinds, which is particularly handy for runtime debugging tools like bevy-inspector-egui
!
Change Detection Source Location Tracking #
Keeping track of when and where values are changed can be tricky in any complex program, and Bevy applications are no exception. Thankfully, our unified ECS-backed data model makes it easy for us to add debugging tools that work right out of the box, with no user configuration required.
When you turn on the track_change_detection
feature flag, Bevy will record the exact line of code that mutated your component or resource side-by-side with the value. While this is obviously too expensive for ordinary use, it's a godsend for debugging tricky issues, as the value can be logged or read directly via the debugger of your choice.
As shown in the change_detection
example, simply turn on the feature and call my_component.changed_by()
on any Ref
, Mut
, Res
or ResMut
smart pointer to get a helpful string pointing you straight to the last line of code that mutated your data!
Optimized Iteration of Mixed Sparse Set and Table Components #
In Bevy, components can be stored using one of two different mechanisms, according to the StorageType
set when implementing the Component
trait.
Table storage is the traditional archetypal ECS storage, where component data is densely packed into tables of raw data with other entities who share the same set of components. By contrast, sparse set storage keeps the component information out of the table, separating entities by archetype (the set of components they have) without fragmenting otherwise shared tables.
As a result of the map-like storage strategy used by sparse set components, they have faster insertion and removal speed, at the cost of slower random-access iteration. This is a reasonable tradeoff, but historically, one that Bevy developers were unlikely to use.
That's because a long-standing bug caused iteration to use the slower, fallback sparse-style iteration if even one of the components in the query or its filters were sparse sets, regardless of whether or not this was necessary. The fix has resulted in query iteration speeds that are between 1.8 and 3.5 times faster (when using parallel iteration) for these scenarios!
Iterating over the data in sparse set components is still relatively slow, but they should finally be a good default choice for any repeatedly inserted or dataless components.
Expose winit's MonitorHandle
#
The new Monitor
component simplifies the process of working with multi-monitor setups by providing easy access to monitor properties such as resolution, refresh rate, position, and scaling factor. This feature is especially useful for developers who need to spawn windows on specific displays, gather monitor details, or adjust their application based on available hardware. This is especially useful for creative setups like multi-projector installations or LED video walls, where precise control over display environments is critical.
Monitor
can be queried for and used for things like spawning or resizing Windows:
fn spawn_windows(
mut commands: Commands,
monitors: Query<(Entity, &Monitor)>,
) {
for (entity, monitor) in monitors_added.iter() {
commands.spawn(Window {
mode: WindowMode::Fullscreen(MonitorSelection::Entity(entity)),
position: WindowPosition::Centered(MonitorSelection::Entity(entity)),
..default()
});
}
}
Custom Cursors #
Previously Bevy's native window cursors supported only a fixed set of built-in OS cursors. Bevy now also supports arbitrary images as "custom cursors". Custom cursors still use native facilities of the OS, which allows them to stay perfectly responsive even when the frame rate of the application drops.
Insert the CursorIcon
component with a CustomCursor
to set a Window
entity's cursor:
commands
.entity(window)
.insert(CursorIcon::Custom(CustomCursor::Image {
handle: asset_server.load("cursor_icon.png"),
hotspot: (5, 5),
}));
Uniform Mesh Sampling #
The surfaces of meshes can now be randomly sampled. This can be used for things like placing scenery or particle effects.
This consists of:
- The
Mesh::triangles
method, which allows the extraction of aMesh
's list of triangles (Triangle3d
). - The
UniformMeshSampler
type, which allows the creation of aDistribution
that uniformly samples points in space (Vec3
) from a collection of triangles.
The functionality comes from putting these together:
let mut rng = StdRng::seed_from_u64(8765309);
// Get an iterator over triangles in the mesh. This can fail if the mesh has
// the wrong format or if its vertex/index data is malformed.
let triangles = my_mesh.triangles().unwrap();
// Construct the distribution. This can fail in some cases - most notably if
// the mesh surface has zero area.
let distribution = UniformMeshSampler::try_new(triangles).unwrap();
// Get 1000 points uniformly sampled from the surface of the mesh.
let samples: Vec<Vec3> = distribution.sample_iter(&mut rng).take(1000).collect();
EventMutator
#
When working with complex event-driven logic, you may find that you want to conditionally modify events without changing their type or re-emitting them. While this has always been possible, it was quite onerous:
// We need to manually track which events this system has read
// using a system-local `EventCursor`, previously called `ManualEventReader`.
fn mutate_events(mut events: ResMut<Events<MyEvent>>, mut local_cursor: Local<EventCursor<MyEvent>>){
for event in local_cursor.read_mut(&mut *events){
event.some_mutation();
}
}
Now, you can simply use the new EventMutator
system param, which keeps track of this bookkeeping for you.
fn mutate_events(mut event_mutator: EventMutator<MyEvent>>){
for event in event_mutator.read(){
event.some_mutation();
}
}
Isometry Types #
Vectors and quaternions are commonly used in 3D to describe relative and absolute positions and orientations of objects. However, when performing more complicated transformations, such as going from a global frame of reference to an object's local space and back, or composing multiple translations and rotations together, they can get rather unwieldy and difficult to reason about.
The new Isometry2d
and Isometry3d
types introduced in Bevy 0.15 are a simple yet powerful tool for efficiently describing these kinds of transformations. An isometry represents a rotation followed by a translation, similar to a Transform
with a scale of 1.
// Create an isometry from a translation and rotation.
let iso1 = Isometry3d::new(Vec3::new(2.0, 1.0, 3.0), Quat::from_rotation_z(FRAC_PI_2));
// Transform a point using the isometry.
let point = Vec3::new(4.0, 4.0, 4.0);
let result = iso1.transform_point(point); // or iso1 * point
assert_relative_eq!(result, Vec3::new(-2.0, 5.0, 7.0));
// Create another isometry.
let iso2 = Isometry3d::from_rotation(Quat::from_rotation_z(FRAC_PI_2));
// Compute the relative translation and rotation.
let relative_iso = iso1.inverse_mul(iso2); // or iso1.inverse() * iso2
Isometries are most useful in mathematical contexts where scaling is not desired, such as when describing relative positions of objects for intersection tests and other geometric queries. However, they are now also used in some APIs, including gizmo methods:
// Specify rectangle position and orientation with an isometry.
gizmos.rect_2d(Isometry2d::new(translation, Rot2::degrees(45.0)), Vec2::splat(250.0), CYAN);
// Many methods take an `impl Into<Isometry3d>`, so it is enough to only provide
// translation or rotation if a full isometry isn't needed.
gizmos.sphere(translation, 1.0, PURPLE);
Transform
and GlobalTransform
can also be converted to an Isometry3d
using the to_isometry
method, providing a convenient way to use these APIs when you already have access to entity transforms.
Note that unlike Transform
, these isometry types are not components. They are purely convenience types for math.
Lifecycle Hook & Observer Trigger for Replaced Values #
Bevy 0.14 introduced Component Lifecycle Hooks and Observers, and included several built-in observer triggers for each way that components could be added to or removed from entities: OnAdd
, OnInsert
and OnRemove
. However, there was a hole in this API. While OnRemove
is a counterpart to OnAdd
, OnInsert
had no such counterpart, meaning certain operations had no corresponding lifecycle hook or observer trigger:
use bevy::{
ecs::component::{ComponentHooks, StorageType},
prelude::{Commands, Component, Deref, DerefMut, Entity, Query, Resource},
utils::HashMap,
};
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
struct SomeId(u32);
#[derive(Resource, Deref, DerefMut)]
struct EntityLookupById(HashMap<SomeId, Entity>);
impl Component for SomeId {
const STORAGE_TYPE: StorageType = StorageType::Table;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks
.on_insert(|mut world, entity, _| {
let this = *world.entity(entity).get::<Self>().unwrap();
world
.resource_mut::<EntityLookupById>()
.insert(this, entity);
})
.on_remove(|mut world, entity, _| {
let this = *world.entity(entity).get::<Self>().unwrap();
world.resource_mut::<EntityLookupById>().remove(&this);
});
}
}
fn some_system(mut commands: Commands, query: Query<(Entity, &SomeId)>) {
let mut iter = query.iter();
let Some((a_entity, _)) = iter.next() else {
return;
};
let Some((_, &b_id)) = iter.next() else {
return;
};
commands.entity(a_entity).insert(b_id);
}
In this example, the system inserts a new component value onto an entity that already has one, which overwrites the previous component value. This causes the on_insert
lifecycle hook to run for the new value, but the on_remove
hook doesn't run for the previous value. As a result, the hashmap entry for the previous ID value is still present, even though it has been replaced.
Bevy 0.15 introduces a new component lifecycle hook and observer trigger for this scenario: on_replace
/OnReplace
. This hook runs just before the on_remove
hook in all cases, and additionally runs in the aforementioned scenario where a component value is entirely replaced. The hook runs just before the replacement occurs, letting you access the soon-to-be-dropped value to perform bookkeeping and cleanup.
The above example would be fixed by simply replacing the on_remove
hook with the new on_replace
hook:
21 .resource_mut::<EntityLookupById>()
22 .insert(this, entity);
23 })
-24 .on_remove(|mut world, entity, _| {
+24 .on_replace(|mut world, entity, _| {
25 let this = *world.entity(entity).get::<Self>().unwrap();
26 world.resource_mut::<EntityLookupById>().remove(&this);
27 });
Note that it does not run if a component value is merely mutated - in those cases you want to use change detection instead.
Pack multiple vertex and index arrays together into growable buffers #
Bevy 0.15 changes the way meshes are stored on the GPU to greatly improve CPU performance. Instead of using separate vertex and index buffers for every mesh as is done in Bevy 0.14, now they are coalesced respectively into 'slabs' of configurable size. This cuts down on how frequently we need to change bind groups, winning us up to 2x speedups!
The MeshAllocatorSettings
resource allows tuning slab sizes, growth rate, and cut-offs to best fit your application's needs. The defaults should already be a significant win for most scenes.
WebGL2 does not support packing vertex buffers together, so only index buffers get combined on this platform.
Some measurements on the Bistro scene:
Overall frame time improves from 8.74 ms to 5.53 ms (1.58x speedup) Render system time improves from 6.57 ms to 3.54 ms (1.86x speedup) Opaque pass time improves from 4.64 ms to 2.33 ms (1.99x speedup)
Rewrite screenshots #
Screenshots can now be taken with a new observer based API that allows targeting any RenderTarget
that can be used with a Camera
, not just windows.
// Capture the primary window
commands
.spawn(Screenshot::primary_window())
.observe(save_to_disk(path));
// Or a `Handle<Image>`
commands
.spawn(Screenshot::image(render_target))
.observe(save_to_disk(path));
The observer triggers with a ScreenshotCaptured
event containing an Image that can be used for saving to disk, post-processing, or generating thumbnails. This flexible approach makes it easier to capture content from any part of your rendering pipeline, whether it’s a window, an off-screen render target, or a texture in a custom render pass.
SystemParamBuilder
#
Bevy 0.14 introduced the SystemBuilder
type to allow systems to be created with dynamic queries. In Bevy 0.15, this has been extended to many more types of system parameters!
The SystemBuilder
type has been replaced with a SystemParamBuilder<P>
trait to make it easier to compose builders. Aggregates of parameters, including tuples, ParamSet
, Vec<T>
, and custom parameters using #[derive(SystemParam)]
, can now be used in dynamic systems. For example, a ParamSet<Vec<Query<FilteredEntityMut>>>
can be used to pass a variable number of dynamic queries that may conflict.
New FilteredResources
and FilteredResourcesMut
types can access a set of resources configured at runtime, similar to how the existing FilteredEntityRef
and FilteredEntityMut
access a set of components on one entity.
Finally, a new DynSystemParam
type allows systems to use parameters of dynamic type and then downcast them. This is especially useful for implementing part of a system with trait objects, where each trait implementation can use a different system parameter type.
Taken together, these can be used to build a system that runs a script defined at runtime, where the script needs a variable number of query and resource parameters. Or, they can be used to build systems out of parts assembled at runtime!
fn buildable_system(
query_a: Query<&A>,
query_b: Query<&B>,
queries_with_locals: Vec<(Query<FilteredEntityMut>, Local<usize>)>,
mut dynamic_params: ParamSet<Vec<DynSystemParam>>,
resources: FilteredResourcesMut,
) {
// Parameters in a `ParamSet<Vec>` are accessed by index.
let mut dyn_param_0: DynSystemParam = dynamic_params.get_mut(0);
// Parameters in a `DynSystemParam` are accessed by downcasting to the original type.
let param: Local<&str> = dyn_param_0.downcast_mut::<Local<&str>>().unwrap();
// `FilteredResources` and `FilteredResourcesMut` have methods to get resources by type or by ID.
let res: Ref<R> = resources.get::<R>().unwrap();
}
let param_builder = (
// Parameters that don't need configuration can be built using `ParamBuilder` or its factory methods.
ParamBuilder,
ParamBuilder::query(),
// A `Vec` of parameters can be built using a `Vec` of builders.
vec![
// A tuple of parameters can be built using a tuple of builders.
(
// Queries are built with a callback that supplies a `QueryBuilder` to configure the query.
QueryParamBuilder::new(|builder| { builder.data::<&A>(); }),
// Locals are built by passing the initial value for the local.
LocalBuilder(123),
),
],
// A `ParamSet` can be built for either a tuple or a `Vec`.
ParamSetBuilder(vec![
// A `DynSystemParam` is built using a builder for any type, and can be downcast to that type.
DynParamBuilder::new(LocalBuilder("hello")),
DynParamBuilder::new(ParamBuilder::resource::<R>()),
// The type may be any system parameter, even a tuple or a `Vec`!
DynParamBuilder::new((ParamBuilder::query::<&A>(), ParamBuilder::query::<&B>())),
]),
// `FilteredResources` and `FilteredResourcesMut` are built with a callback
// that supplies a builder to configure the resource access.
FilteredResourcesMutParamBuilder::new(|builder| { builder.add_read::<R>(); }),
);
let system = param_builder
.build_state(&mut world)
.build_system(buildable_system);
// The built system is just like any other system, and can be added to a schedule.
schedule.add_systems(system);
State Scoped Events #
State scoped events will be automatically cleared when exiting a state (similar to StateScoped entities). This is useful when you want to guarantee clean state transitions.
Normally, you would configure your event via:
fn setup(app: &mut App) {
app.add_event::<MyGameEvent>();
}
If you want the events to be cleared when you exit a specific state, change this to:
fn setup(app: &mut App) {
app.add_state_scoped_event::<MyGameEvent>(GameState::Play);
}
EntityRefExcept
and EntityMutExcept
#
EntityMut
and EntityRef
are powerful tools for interacting with all components of a given entity at once in arbitrary ways. These types implement QueryData
, so you can add them to any Query
you'd like!
However, because they can access any component information, Rust's prohibition against mutable aliasing prevent you from simultaneously accessing other component information, even if you pinky promise not to read any data that's being written to.
// This system is forbidden!
//
// Inside the body of the function, we could choose to mutate the `AnimationPlayer` itself
// while reading its value!
fn animate_anything(query: Query<(&AnimationPlayer, EntityMut)> ){}
To let you work around this limitation, we've introduced a matching pair of tools: EntityMutExcept
and EntityRefExcept
, which work just like the EntityMut
and EntityRef
but don't provide access to a bundle of components that you declare off-limits.
/// Look mom, no mutable aliasing!
fn animate_anything(query: Query<(&AnimationPlayer, EntityMutExcept<AnimationPlayer>)> ){}
Cached One-shot Systems #
Bevy 0.15 introduces a convenient new "cached" API for running one-shot systems:
// Old, uncached API:
let foo_id = commands.register_system(foo);
commands.run_system(foo_id);
// New, cached API:
commands.run_system_cached(foo);
This allows you to call register_system_cached
without needing to worry about producing duplicate systems.
// Uncached API:
let id1 = world.register_system(quux);
let id2 = world.register_system(quux);
assert!(id1 != id2);
// Cached API:
let id1 = world.register_system_cached(quux);
let id2 = world.register_system_cached(quux);
assert!(id1 == id2);
Comparison to run_system_once
run_system_once
sets up a system, runs it once, and tears it down. This means system parameters like Local
and EventReader
that rely on persistent state between runs will be lost. Any system parameters like Query
that rely on cached computations to improve performance will have to rebuild their cache each time, which can be costly. As a consequence, run_system_once
is only recommended for diagnostic use (e.g. unit tests), and run_system
or run_system_cached
should be preferred for "real" code.
Limitations
With the cached API, different systems cannot be cached under the same CachedSystemId<S>
. There can be no more than one distinct system of type S
. This is true when size_of::<S>() == 0
, which is almost always true in practice. To enforce correctness, the new API will give you a compile-time error if you try to use a non-zero-sized function (like a function pointer or a capturing closure).
Fallible System Parameters #
In Bevy 0.14 and prior, the following code would panic:
#[derive(Resource)]
struct MyResource;
fn my_system(my_resource: Res<MyResource>) {}
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins)
app.add_systems(my_system);
// Panic here: `my_system` cannot fetch `MyResource`, because it was never added.
app.run();
}
but in Bevy 0.15, my_system
simply won't be executed and a warning will be logged.
This works for all system-based features:
- Systems and Observers will be skipped.
- Run Conditions will be skipped and return
false
.
Compound systems, like system_a.pipe(system_b)
, are currently skipped if any required data is missing.
Pre-existing parameters which now benefit from this feature are: Res
and ResMut
as well as their siblings NonSend
and NonSendMut
. Parameters that build on top of other parameters: tuples, DynSystemParam
and ParamSet
are considered present if and only if all of their system parameters are present.
Additionally, few new system params were introduced to simplify existing code:
Single<D, F>
- Works likeQuery<D, F>::single
, fails if query contains 0 or more than 1 match,Option<Single<D, F>>
- Works likeQuery<D, F>::single
, fails if query contains more than 1 match,Populated<D, F>
- Works like aQuery<D, F>
, fails if query contains no matches.
Warnings
Fallible system params come with a primitive warning mechanic. Currently, systems can behave in one of two ways:
- (default) warn exactly once,
- never warn.
The default can be changed as following:
// For systems
app.add_systems(my_system.never_param_warn());
// For observers
app.add_observer(my_observer.never_param_warn());
// For run conditions
app.add_systems(my_system.run_if(my_condition.never_param_warn()));
Let us know what other warning strategies you'd like!
Passing Data Into Systems By Reference #
System piping is a powerful (if relatively niche) tool to pass data directly from one system to another. While this is useful for error handling, it's a general purpose tool for composing fragments of logic by gluing together matching inputs and outputs.
This machinery has since been repurposed for use with one-shot systems, allowing you to call World::run_system_with_input
to evaluate systems with whatever input you supply, and get the return value back out. Great for writing tests!
However, this set of tools has always had a frustrating and confusing limitation: any data passed into a system must have a static lifetime. This seems absurd; the data is passed directly from one owner to the next and the systems are run as if they were a single unit.
With the liberal application of some type magic pixie dust, this limitation has been lifted!
let mut world = World::new();
let mut value = 2;
// This always worked:
fn square(In(input): In<usize>) -> usize {
input * input
}
value = world.run_system_with_input(value, square);
// Now possible:
fn square_ref(InRef(input): InRef<usize>) -> usize {
*input * *input
}
value = world.run_system_with_input(&value, square_ref);
// Mutably:
fn square_mut(InMut(input): InMut<usize>) {
*input *= *input;
}
world.run_system_with_input(&mut value, square_mut);
We're excited to see what you do with this newfound power.
List Components in QueryEntityError::QueryDoesNotMatch #
When accessing an entity through a query fails due to mismatched components, the error now includes the names of the components the entity has:
QueryDoesNotMatch(0v1 with components Sprite, Transform, GlobalTransform, Visibility, InheritedVisibility, ViewVisibility, SyncToRenderWorld)
no_std
Progress #
Bevy relies heavily on Rust's standard library, making it challenging to use on embedded, niche platforms, and even certain consoles. But what if that wasn't the case?
We've undertaken a new initiative to challenge the reliance on the standard library, with the eventual goal of providing a no_std
compatible subset of Bevy which could be used on a much wider range of platforms.
The first very simple step is to enable a new set of lints:
For those unfamiliar with no_std
Rust, the standard library, std
, gets a lot of its functionality from two smaller crates, core
and alloc
. The core
crate is available on every Rust target with very few exceptions, providing the fundamental infrastructure that the Rust language relies on, such as iterators, Result
, and many more. Complementing that the alloc
crate provides access to allocation-related functionality, such as Vec
, Box
, and String
.
Rust's support for platforms follows a three tiered policy, where tier 1 is guaranteed to work and will always provide the std
crate, and tiers 2 and 3 may have the std
crate, but often do not. The reason for this is some platforms simply don't support the features the std
crate requires, such as a filesystem, networking, or threading.
But why should Bevy care about these platforms? When a new platform is added to Rust, it is often lacking tier 1 support. Even modern consoles such as the Nintendo Switch, PlayStation 5, or Xbox Series don't have tier 1 support due to non-disclosure agreements and platform specifics. Adding no_std
support to Bevy will make it easier for commercial teams developing for these platforms to get started and stay up to date.
Beyond the commercially-relevant modern consoles, there is a vibrant community of embedded and retro enthusiasts developing for platforms that may never support the standard library. Crates such as agb
and psx
provide support for developing games on the GameBoy Advance and PlayStation One respectively. With no_std
support in Bevy, users may be able to leverage the wider Rust ecosystem to run their software on these platforms.
We're still a while away from true no_std
support in Bevy, but the first few changes have already been accepted, with many more lined up for the next release in 0.16.
If this work sounds interesting, check out the no_std
tracking issue on GitHub, where you can find a list of pull requests, and even prototypes of Bevy running in no_std
environments.
GltfMaterialName
Component #
The glTF 3D model file format allows a single mesh to be associated with multiple materials. For example, a teapot may consist of a single mesh, yet each part may have a different material. When a single mesh is assigned multiple materials, it is divided into several primitive nodes, with each primitive assigned a unique material.
{
"meshes": [
{
"name": "Cube",
"primitives": [
{
"attributes": { "POSITION": 0, "NORMAL": 1, "TEXCOORD_0": 2 },
"indices": 3,
"material": 0
},
{
"attributes": { "POSITION": 4, "NORMAL": 5, "TEXCOORD_0": 6 },
"indices": 7,
"material": 1
},
{
"attributes": { "POSITION": 8, "NORMAL": 9, "TEXCOORD_0": 10 },
"indices": 11,
"material": 2
},
{
"attributes": { "POSITION": 12, "NORMAL": 13, "TEXCOORD_0": 14 },
"indices": 15,
"material": 3
}
]
}
]
}
In Bevy 0.14 and before, these primitives are named using the format "Mesh.Index", which complicates querying. A new component GltfMaterialName is now added to each primitive node that has a material, letting you quickly look up the primitive by using the this component with the material name.
fn find_top_material_and_mesh(
mut materials: ResMut<Assets<StandardMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
time: Res<Time>,
mesh_materials: Query<(
&MeshMaterial3d<StandardMaterial>,
&Mesh3d,
&GltfMaterialName,
)>,
) {
for (mat_handle, mesh_handle, name) in &mesh_materials {
// locate the material and associated submesh by name
if name.0 == "Top" {
if let Some(material) = materials.get_mut(mat_handle) {
// ...
}
if let Some(mesh) = meshes.get_mut(mesh_handle) {
// ...
}
}
}
}
GPU Readback #
The new Readback
component simplifies the tricky process of getting data back from the GPU to the CPU using an observer-based API.
commands.spawn(Readback::buffer(buffer.clone())).observe(
|trigger: Trigger<ReadbackComplete>| {
let data = trigger.event().to_shader_type();
// ...
},
);
Normally, manually retrieving data from the GPU involves a lot of boilerplate and careful management of GPU resources. You have to deal with synchronization, ensure the GPU has finished processing, and handle copying data between memory spaces—which isn’t straightforward!
The new Readback
component streamlines this process. When spawned into the main world, Readback
will queue a Handle<Image>
or Handle<ShaderStorageBuffer>
to be asynchronously read and copied back from the GPU to CPU in a future frame where it will trigger a ReadbackComplete
event containing the raw bytes of the resource.
This is especially useful for debugging, saving GPU-generated data, or performing CPU-side computations with results from the GPU. It’s perfect for scenarios where you need to analyze simulation data, capture rendered frames, or process large datasets on the GPU and retrieve the results for further use on the CPU.
Android: Configurable GameActivity
and NativeActivity
#
Bevy now uses GameActivity
as the default Activity
for Android projects, replacing NativeActivity
. NativeActivity
is still available, but has been placed behind a feature flag.
This change updates Bevy to a more modern Android stack, and includes an SDK minimum version bump to PlayStore's current version requirement. We've also switched to a cargo-ndk
based build, which gives us more control by default. Gradle projects for both GameActivity
and NativeActivity
are provided.
GameActivity
brings with it improvements to game interaction (SurfaceView
rendering, improved touch and input handling), more frequent updates, and access to other parts of the JetPack ecosystem. It is better placed to integrate with Rust code without excessive JNI wrangling. You can read more about GameActivity
here.
Reflection Serialization Improvements #
Serialization with registry context
bevy_reflect
provides a way to easily serialize and deserialize nearly any type that implement Reflect
. It does so by relying purely on the reflection APIs and the TypeRegistry
, without having to know the type at compile-time.
However, sometimes serialization/deserialization for a type requires more explicit control. In such cases, a custom Serialize
/Deserialize
implementation can be provided by registering the ReflectSerialize
/ReflectDeserialize
type data for the type in the TypeRegistry
.
This approach generally works well enough for most cases. However, sometimes you want to handle the case for your type alone and continue using reflection for the rest of the fields. For example, you might want to serialize your type as a map that includes a few extra entries, but you still want to use the reflection serializer for each value.
Unfortunately, not only does this not nest well within serializers, but it also means you need to manually capture a reference to the TypeRegistry
so that you can pass it down to the nested reflection serializers. What this basically means is that you can't use custom logic along with reflection-based serialization.
Thankfully, Bevy 0.15 introduces the SerializeWithRegistry
and DeserializeWithRegistry
traits, which work much like Serialize
and Deserialize
but with an additional TypeRegistry
parameter. This allows you to perform your custom logic while still being able to continue using reflection for the rest.
impl SerializeWithRegistry for MyType {
fn serialize<S>(&self, serializer: S, registry: &TypeRegistry) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let state = serializer.serialize_map(None)?;
// ...custom logic...
state.serialize_entry(
"data",
// Continue using reflection-based serialization
&ReflectSerializer::new(
self.data,
registry,
),
)?;
state.end()
}
}
With your custom serialization and deserialization logic in place, you can then register the ReflectSerializeWithRegistry
and ReflectDeserializeWithRegistry
type data for your type to have the reflection serializer/deserializer make use of your custom logic for all instances of your type.
Reflect de/serializer processors
Alongside SerializeWithRegistry
and DeserializeWithRegistry
, a new tool has been added for users who use the reflect machinery for de/serialization. When using the ReflectSerializer
or ReflectDeserializer
, you can now use with_processor
and pass in a de/serializer processor. This processor allows you to override the de/serialization logic for specific values and specific types, while also capturing any context you might need inside the processor itself.
The motivating example for this is being able to deserialize Handle<T>
s properly inside an asset loader when reflect-deserializing. Let's imagine that we have an asset that looks like this:
#[derive(Debug, Clone, Reflect)]
struct AnimationGraph {
nodes: Vec<Box<dyn AnimationNode>>,
}
trait AnimationNode: Send + Sync + Reflect { /* .. */ }
#[derive(Debug, Clone, Reflect)]
struct ClipNode {
clip: Handle<AnimationClip>
}
impl AnimationNode for ClipNode { /* .. */ }
#[derive(Debug, Clone, Reflect)]
struct AdjustSpeedNode {
speed_multiplier: f32,
}
impl AnimationNode for AdjustSpeedNode { /* .. */ }
(
animation_graph: (
nodes: [
{
"my_app::animation::node::ClipNode": (
clip: "animations/run.anim.ron",
)
},
{
"my_app::animation::node::AdjustSpeedNode": (
speed_multiplier: 1.5,
)
}
]
)
)
When we write an AssetLoader
for this AnimationGraph
, we have access to a &mut LoadContext
which we can use to start new asset load operations, and get a Handle
to that asset. We can also use the existing ReflectDeserializer
to deserialize Box<dyn AnimationNode>
s. However, when the deserializer encounters a Handle<AnimationClip>
, this will be deserialized as Handle::default
and no asset load will be kicked off, making the handle useless.
With a ReflectDeserializerProcessor
, we can pass in a processor which captures the &mut LoadContext
and, if it encounters a Handle<T>
, it will kick off an asset load for T
, and assigns the result of that load to the field it's deserializing.
struct HandleProcessor<'a> {
load_context: &'a mut LoadContext,
}
impl ReflectDeserializerProcessor for HandleProcessor<'_> {
fn try_deserialize<'de, D>(
&mut self,
registration: &TypeRegistration,
_registry: &TypeRegistry,
deserializer: D,
) -> Result<Result<Box<dyn PartialReflect>, D>, D::Error>
where
D: Deserializer<'de>,
{
let Some(reflect_handle) = registration.data::<ReflectHandle>() else {
// we don't want to deserialize this - give the deserializer back
// and do default deserialization logic
return Ok(Err(deserializer));
};
let asset_type_id = reflect_handle.asset_type_id();
let asset_path = deserializer.deserialize_str(AssetPathVisitor)?;
let handle: Handle<LoadedUntypedAsset> = self.load_context
.loader()
.with_dynamic_type(asset_type_id)
.load(asset_path);
Ok(Box::new(handle))
}
}
Combined with ReflectSerializerProcessor
, this can be used to round-trip Handle
s to/from string asset paths.
The processors take priority over all other serde logic, including De/SerializeWithRegistry
, so it can be used to override any reflect serialization logic.
Contextual Serialization Errors
Sometimes when working with the reflection serializer and deserializer, it can be difficult to track down the source of an error. Since we can't tell whether a type can be serialized or not until runtime, an un-serializable type might slip into a type that is supposed to be serializable.
In Bevy 0.15, a new default debug
feature has been added to the bevy_reflect
crate, which allows the serializers and deserializers to retain contextual information in order to provide the type's "stack" when an error occurs.
These messages can be used to more easily track down the source of the error:
type `bevy_utils::Instant` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `bevy_time::time::Time<bevy_time::real::Real>` -> `bevy_time::real::Real` -> `bevy_utils::Instant`)
Simplified Multi-Entity Access #
When using some of the more advanced features of Bevy's ECS, like hooks or exclusive systems, it's common to want to fetch entities straight out of a World
:
#[derive(Component)]
#[component(on_add = on_foo_added)]
struct Foo;
fn on_foo_added(world: DeferredWorld, entity: Entity, _: ComponentId) {
let has_foo = world.entity(entity);
println!("{:?} has a Foo", has_foo.id());
}
In previous versions of Bevy, you could grab multiple entities from a World
using a variety of different functions:
World::many_entities<N>(&self, [Entity; N]) -> [EntityRef; N]
World::many_entities_mut<N>(&mut self, [Entity; N]) -> [EntityMut; N]
World::get_many_entities<N>(&self, [Entity; N]) -> Result<[EntityRef; N], Entity>
World::get_many_entities_dynamic(&self, &[Entity]) -> Result<Vec<EntityRef>, Entity>
World::get_many_entities_mut<N>(&mut self, [Entity; N]) -> Result<[EntityMut; N], QueryEntityError>
World::get_many_entities_dynamic_mut(&self, &[Entity]) -> Result<Vec<EntityMut>, QueryEntityError>
World::get_many_entities_from_set_mut(&mut self, &EntityHashSet) -> Result<Vec<EntityMut>, QueryEntityError>
As you can see, that's a lot of functions with very long names! But the gist of them is that we want to support the ability to give a bunch of entity IDs, and receive a bunch of entity references. Surely there's a better way!
In 0.15
, all of those functions have been deprecated and now all you need is the panicking World::entity
/World::entity_mut
or the non-panicking World::get_entity
/World::get_entity_mut
:
let e1: Entity = world.spawn_empty().id();
let e2: Entity = world.spawn_empty().id();
// Note: use World::get_entity or World::get_entity_mut instead to receive a Result
// You can still pass a single ID as normal:
let eref = world.entity(e1);
let emut = world.entity_mut(e1);
// But you can also pass in an array of IDs (any amount N supported!):
let [eref1, eref2]: [EntityRef; 2] = world.entity([e1, e2]);
let [emut1, emut2]: [EntityMut; 2] = world.entity_mut([e1, e2]);
// Or a slice of IDs:
let ids = vec![e1, e2];
let eref_vec: Vec<EntityRef> = world.entity(&ids);
let emut_vec: Vec<EntityMut> = world.entity_mut(&ids);
// Or even a set of IDs:
let ids = EntityHashSet::from_iter([e1, e2]);
let eref_map: EntityHashMap<EntityRef> = world.entity(&ids);
let emut_map: EntityHashMap<EntityMut> = world.entity_mut(&ids);
It might feel like magic, but it's all standard Rust code! The Entity
id parameter that the World::entity
family of functions accept was changed to instead accept anything that implements a newly introduced trait: WorldEntityFetch
. Check out the trait and World::entity
to learn more about how it was accomplished.
Hierarchy Traversal Tools #
We've spruced up the HierarchyQueryExt
extension trait, making it easier to traverse entity hierarchies defined by the Parent
and Children
components.
The full set of methods is now:
parent
(new)children
(new)root_ancestor
(new)iter_leaves
(new)iter_siblings
(new)iter_descendants
iter_descendants_depth_first
(new)iter_ancestors
All of these operations were previously possible, but we hope that this API makes working with hierarchies more pleasant, especially for UI and animation.
Shader Storage Buffer Asset #
A new asset ShaderStorageBuffer
has been added to simplify working with storage buffers in custom materials and compute shaders. Storage buffers are large, GPU-accessible memory buffers designed for storing data that can be read from or written to by shaders. Unlike smaller, more restricted uniform buffers, storage buffers allow you to store large amounts of data, making them perfect for general purpose tasks where large datasets need to be processed. Examples include managing complex data in physics simulations (like particle systems), holding the transformation data for thousands of objects in a scene, or storing procedural geometry information for dynamic terrain generation. Storage buffers are particularly useful when different stages of the rendering pipeline (such as compute shaders and rendering passes) need to share and update large amounts of data efficiently.
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
struct CustomMaterial {
#[storage(0, read_only)]
colors: Handle<ShaderStorageBuffer>,
}
fn setup(
mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
mut materials: ResMut<Assets<CustomMaterial>>,
) {
// Example data for the storage buffer
let color_data: Vec<[f32; 4]> = vec![
[1.0, 0.0, 0.0, 1.0],
[0.0, 1.0, 0.0, 1.0],
[0.0, 0.0, 1.0, 1.0],
[1.0, 1.0, 0.0, 1.0],
[0.0, 1.0, 1.0, 1.0],
];
let colors = buffers.add(ShaderStorageBuffer::from(color_data));
// Create the custom material with the storage buffer
let custom_material = CustomMaterial { colors };
materials.add(custom_material);
}
By declaring Handle<ShaderStorageBuffer>
on the material using AsBindGroup
, this buffer can now be accessed in the shader:
@group(2) @binding(0) var<storage, read> colors: array<vec4<f32>, 5>;
Accumulated Mouse Inputs #
"How much has the player moved their mouse this frame" is a natural question for games when the player is trying to aim or scroll a map. Unfortunately, the operating system, and thus winit
, only provides us with a stream of events, in the form of individual MouseMotion
events.
To get the summarized information (and the equivalent MouseScroll
) information that most game systems care about, you had to sum them yourself.
pub fn accumulate_mouse_motion_system(
mut mouse_motion_event: EventReader<MouseMotion>,
mut accumulated_mouse_motion: ResMut<AccumulatedMouseMotion>,
) {
let mut delta = Vec2::ZERO;
for event in mouse_motion_event.read() {
delta += event.delta;
}
accumulated_mouse_motion.delta = delta;
}
Bevy now does this for you, exposed in the new AccumulatedMouseMotion
and AccumulatedMouseScroll
resources.
Stable Interpolation and Smooth Following #
When animating cameras or programming unit AI (not that kind of AI!), moving something continuously towards a target is an essential basic operation. Simply lerping to the target seems easy enough, but as Freya Holmer explains, making sure that this interpolation is timestep independent is both vital and surprisingly tricky.
We've done the math for you; you just need to use the StableInterpolate
trait's interpolate_stable
and smooth_nudge
methods and tune the decay_rate
parameter to really optimize your game feel. Fear not: it even works on quaternions! Stable, smooth camera controllers have never been easier.
What's Next? #
The features above may be great, but what else does Bevy have in flight? Peering deep into the mists of time (predictions are extra hard when your team is almost all volunteers!), we can see some exciting work taking shape:
- Bevy Scene Notation: Required components mark the first step on Cart's master plan for BSN. Over the next few months, he's going to be heads-down developing a Bevy-specific file format (complete with matching macro and IDE support), the
Construct
trait (to easily include asset data in scenes), patches (to layer modifications to scenes) and experimenting with approaches to reactivity for UI. - Better font support: While
cosmic_text
is a huge leap forward for text shaping and rendering, our approach to handling fonts and type-faces is still quite crude. Bidirectional text, working with system fonts, a convenient Markdown-style "bold this section of the text" API, font fallback and more are planned. - Picking-Powered UI Interaction:
bevy_picking
introduces a much more powerful and expressive way to handle pointer interactions, but we're not leveraging its full power withinbevy_ui
itself. While picking events are great, a single source of truth for "what's the user doing with this button" is vital for responsive widget styling. bevy_lint
: Try as we might, it is possible to misuse Bevy's API! As part of a broaderbevy_cli
project, the Bevy community has developed a Bevy-specific linter to catch common mistakes or hazards and are looking for early adopters to try it out!- Focus abstraction: Keeping track of which UI element is focused is vital to allow users of screen readers, gamepads and keyboards to comfortably navigate the UI. We're planning to build on our success with
bevy_picking
and develop a complementary focus-tracking solution, along with a few simple backends to opt-in to keyboard or gamepad-based UI navigation. - Immutable components: Component hooks and observers are really powerful for responding to changes and upholding invariants, but they're easily bypassed by simply mutating the component. The mad science crew has been experimenting with a way to opt-out of direct mutation, opening the door to more robust hierarchies, complex observer-powered reactions and a first-party component indexing solution.
- Actually Retained Rendering: While the render world is technically retained in Bevy 0.15, most of our existing code still spawns and despawns entities every frame to reduce the risk of introducing bugs during the migration. We're looking forward to gradually changing this and profiling the performance impact!
no_std
Bevy: To better support weird platforms (like the Playdate!) and make life easier for devs experimenting with Bevy on modern consoles, we've been working towards ensuring that (much of) Bevy can compile and run without Rust's standard library.
Support Bevy #
Bevy will always be free and open-source, but it isn't free to make! Because Bevy is free, we rely on the generosity of the Bevy community to fund our efforts. If you are a happy user of Bevy or you believe in our mission, please consider donating to the Bevy Foundation... every bit helps!
Contributors #
A huge thanks to the 195 contributors that made this release (and associated docs) possible! In random order:
- @Elabajaba
- @Kees-van-Beilen
- @rosefromthedead
- @Waridley
- @Torstein Grindvik
- @james-j-obrien
- @NiklasEi
- @hxYuki
- @softmoth
- @MarkusTheOrt
- @RobWalt
- @Natalie Baker
- @Davier
- @daxpedda
- @devnev
- @rparrett
- @Jondolf
- @Aztro-dev
- @NoahShomette
- @kettle11
- @Malax
- @Architector4
- @maniwani
- @dubrowgn
- @mamekoro
- @Leinnan
- @DasLixou
- @IceSentry
- @MrGVSV
- @nvdaz
- @SkiFire13
- @BD103
- @cbournhonesque-sc
- @wackbyte
- @atornity
- @Dig-Doug
- @kirusfg
- @hank
- @Alice Cecile
- @BorisBoutillier
- @tripokey
- @ekropotin
- @Eduardo Canellas de Oliveira
- @alice-i-cecile
- @kidrigger
- @tjamaan
- @solis-lumine-vorago
- @NathanSWard
- @mockersf
- @ebola
- @bardt
- @GitGhillie
- @mintlu8
- @SpecificProtagonist
- @nelsontkq
- @TheTacBanana
- @JMS55
- @AxiomaticSemantics
- @ameknite
- @kristoff3r
- @nelson
- @james7132
- @fantasyRqg
- @mnmaita
- @thebluefish
- @aevyrie
- @Ato2207
- @valentinegb
- @killercup
- @pablo-lua
- @janhohenheim
- @rafalh
- @Testare
- @doonv
- @wgxer
- @afonsolage
- @hecksmosis
- @HeyZoos
- @SIGSTACKFAULT
- @irate-devil
- @Vrixyz
- @tygyh
- @bogdiw
- @notverymoe
- @richardhozak
- @HugoPeters1024
- @Trashtalk217
- @nfagerlund
- @matiqo15
- @nxsaken
- @lkolbly
- @jakobhellermann
- @DGriffin91
- @AlexOkafor
- @shanecelis
- @hankjordan
- @lee-orr
- @bonsairobo
- @NthTensor
- @Bluefinger
- @thepackett
- @st0rmbtw
- @rqg
- @maueroats
- @scottmcm
- @Olle-Lukowski
- @wainwrightmark
- @xNapha
- @extrawurst
- @thmsgntz
- @brianreavis
- @garychia
- @orph3usLyre
- @TrialDragon
- @ickshonpe
- @Braymatter
- @johanhelsing
- @capt-glorypants
- @andriyDev
- @Ixentus
- @CorneliusCornbread
- @LeshaInc
- @DavJCosby
- @Pixelstormer
- @nothendev
- @tguichaoua
- @Shatur
- @torsteingrindvik
- @stepancheg
- @ArthurBrussee
- @RyanSpaker
- @dmlary
- @Gadzev
- @johnbchron
- @coreh
- @IQuick143
- @davidasberg
- @viridia
- @andristarr
- @Kanabenki
- @tychedelia
- @JoJoJet
- @kayhhh
- @ickk
- @josfeenstra
- @cart
- @Nathan-Fenner
- @antoniacobaeus
- @UkoeHB
- @Adamkob12
- @asuratos
- @matthew-gries
- @KirmesBude
- @13ros27
- @benfrankel
- @taizu-jin
- @eltociear
- @ibotha
- @Friz64
- @SludgePhD
- @rodolphito
- @tbillington
- @ManevilleF
- @GuillaumeGomez
- @bushrat011899
- @MinerSebas
- @akimakinai
- @laund
- @soqb
- @hymm
- @cBournhonesque
- @VitalyAnkh
- @komadori
- @fuchsnj
- @simbleau
- @robtfm
- @asafigan
- @Nilirad
- @MiniaczQ
- @superdump
- @jeliag
- @esensar
- @porkbrain
- @anarelion
- @seabassjh
- @TimJentzsch
- @laundmo
- @NiseVoid
- @nicopap
- @TheBlckbird
- @re0312
- @SET001
- @pcwalton
- @ItsDoot
- @Aceeri
Full Changelog #
A-Rendering + A-Windowing #
- Allow prepare_windows to run off main thread.
- Allow prepare_windows to run off main thread on all platforms
- don't run
create_surfaces
system if not needed - fix create_surfaces system ordering
A-Animation + A-Reflection #
A-Assets #
- Don't
.unwrap()
inAssetPath::try_parse
- feat:
Debug
implemented forAssetMode
- Remove rogue : from embedded_asset! docs
- use
tree
syntax to explain bevy_rock file structure - Make AssetLoader/Saver Error type bounds compatible with anyhow::Error
- Fix untyped labeled asset loading
- Add
load_untyped
to LoadContext - fix example custom_asset_reader on wasm
ReadAssetBytesError::Io
exposes failing path- Added Method to Allow Pipelined Asset Loading
- Add missing asset load error logs for load_folder and load_untyped
- Fix wasm builds with file_watcher enabled
- Do not panic when failing to create assets folder (#10613)
- Use handles for queued scenes in SceneSpawner
- Fix file_watcher feature hanging indefinitely
- derive asset for enums
- Ensure consistency between Un/Typed
AssetId
andHandle
- Fix Asset Loading Bug
- remove double-hasing of typeid for handle
- AssetMetaMode
- Fix GLTF scene dependencies and make full scene renders predictable
- Print precise and correct watch warnings (and only when necessary)
- Allow removing and reloading assets with live handles
- Add GltfLoaderSettings
- Refactor
process_handle_drop_internal()
in bevy_asset - fix base64 padding when loading a gltf file
- assets should be kept on CPU by default
- Don't auto create assets folder
- Use
impl Into<A>
forAssets::add
- Add
reserve_handle
toAssets
. - Better error message on incorrect asset label
- GLTF extension support
- Fix embedded watcher to work with external crates
- Added AssetLoadFailedEvent, UntypedAssetLoadFailedEvent
- auto create imported asset folder if needed
- Fix minor typo
- Include asset path in get_meta_path panic message
- Fix documentation for
AssetReader::is_directory
function - AssetSaver and AssetTransformer split
- AssetPath source parse fix
- Allow TextureAtlasBuilder in AssetLoader
- Add a getter for asset watching status on
AssetServer
- Make SavedAsset::get_labeled accept &str as label
- Added Support for Extension-less Assets
- Fix embedded asset path manipulation
- Fix AssetTransformer breaking LabeledAssets
- Put asset_events behind a run condition
- Use Asset Path Extension for
AssetLoader
Disambiguation
A-Core + A-App #
A-Accessibility #
A-Transform #
A-ECS + A-Hierarchy #
A-Text #
- Rename
TextAlignment
toJustifyText
. - Subtract 1 from text positions to account for glyph texture padding.
A-Assets + A-UI #
A-Utils + A-Time #
A-Rendering + A-Assets #
- Fix shader import hot reloading on windows
- Unload render assets from RAM
- mipmap levels can be 0 and they should be interpreted as 1
A-Physics #
A-ECS + A-Editor + A-App + A-Diagnostics #
A-Reflection + A-Scenes #
A-Audio + A-Windowing #
A-Build-System + A-Meta #
A-ECS + A-Time #
- Wait until
FixedUpdate
can see events before dropping them - Add First/Pre/Post/Last schedules to the Fixed timestep
- Add run conditions for executing a system after a delay
- Add paused run condition
A-Meta #
- Add "update screenshots" to release checklist
- Remove references to specific projects from the readme
- Fix broken link between files
- [doc] Fix typo in CONTRIBUTING.md
- Remove unused namespace declarations
- Add docs link to root
Cargo.toml
- Migrate third party plugins guidelines to the book
- Run markdownlint
- Improve
config_fast_builds.toml
- Use
-Z threads=0
option inconfig_fast_builds.toml
- CONTRIBUTING.md: Mention splitting complex PRs
A-Time #
- docs: use
read
instead of deprecatediter
- Rename
Time::<Fixed>::overstep_percentage()
andTime::<Fixed>::overstep_percentage_f64()
- Rename
Timer::{percent,percent_left}
toTimer::{fraction,fraction_remaining}
- Document how to configure FixedUpdate
- Add discard_overstep function to Time
A-Assets + A-Reflection #
A-Diagnostics + A-Utils #
A-Windowing + A-App #
A-ECS + A-Scenes #
A-Hierarchy #
- bevy_hierarchy: add some docs
- Make bevy_app and reflect opt-out for bevy_hierarchy.
- Add
bevy_hierarchy
Crate and plugin documentation - Rename "AddChild" to "PushChild"
- Inline trivial methods in bevy_hierarchy
A-ECS + A-App #
A-Transform + A-Math #
A-UI + A-Text #
A-Input #
- Make ButtonSettings.is_pressed/released public
- Rename
Input
toButtonInput
- Add method to check if all inputs are pressed
- Add window entity to TouchInput events
- Extend
Touches
with clear and reset methods - Add logical key data to KeyboardInput
- Derive Ord for GamepadButtonType.
- Add delta to CursorMoved event
A-Rendering + A-Diagnostics #
A-Rendering #
- Fix bevy_pbr shader function name
- Implement Clone for VisibilityBundle and SpatialBundle
- Reexport
wgpu::Maintain
- Use a consistent scale factor and resolution in stress tests
- Ignore inactive cameras
- Add shader_material_2d example
- More inactive camera checks
- Fix post processing example to only run effect on camera with settings component
- Make sure added image assets are checked in camera_system
- Ensure ExtendedMaterial works with reflection (to enable bevy_egui_inspector integration)
- Explicit color conversion methods
- Re-export wgpu BufferAsyncError
- Improve shader_material example
- Non uniform transmission samples
- Explain how
AmbientLight
is inserted and configured - Add wgpu_pass method to TrackedRenderPass
- Add a
depth_bias
toMaterial2d
- Use as_image_copy where possible
- impl From
for ClearColorConfig - Ensure instance_index push constant is always used in prepass.wgsl
- Bind group layout entries
- prepass vertex shader always outputs world position
- Swap material and mesh bind groups
- try_insert Aabbs
- Fix prepass binding issues causing crashes when not all prepass bindings are used
- Fix binding group in custom_material_2d.wgsl
- Normalize only nonzero normals for mikktspace normal maps
- light renderlayers
- Explain how RegularPolygon mesh is generated
- Fix Mesh2d normals on webgl
- Update to wgpu 0.18
- Fix typo in docs for
ViewVisibility
- Add docs to bevy_sprite a little
- Fix BindingType import warning
- Update texture_atlas example with different padding and sampling
- Update AABB when Sprite component changes in calculate_bounds_2d()
- OrthographicProjection.scaling_mode is not just for resize
- Derive
Debug
forBloomCompositeMode
- Document None conditions on compute_aabb
- Replace calculation with function call
- Register Camera types.
- Add example for pixel-perfect grid snapping in 2D
- Misc cleanup
- Keep track of when a texture is first cleared
- Fix Mesh::ATTRIBUTE_UV_0 documentation
- Do not load prepass normals for transmissive materials
- Export tonemapping_pipeline_key (2d), alpha_mode_pipeline_key
- Simplify examples/3d/orthographic
- Implement lightmaps.
- Bump the vertex attribute index for prepass joints.
- Fix: Gizmos crash due to the persistence policy being set to
Unload
. Change it toKeep
- Usability methods for RenderTargets and image handles
- Explain Camera physical size is in pixel
- update Outdated comment
- Revert "Implement minimal reflection probes. (#10057)"
- Explain OrthographicProjection.scale
- Mul
for ScalingMode - Rustdoc examples for OrthographicProjection
- Option to enable deterministic rendering
- Fix ssao only sampling mip 0
- Revert "Implement minimal reflection probes. (#10057)"
- Sprite slicing and tiling
- Approximate indirect specular occlusion
- Texture Atlas rework
- Exposure settings (adopted)
- Remove Vec from GpuArrayBuffer
- Make
DynamicUniformBuffer::push
accept an&T
instead ofT
- Restore brightness in the remaining three examples after exposure PR
- Customizable camera main texture usage
- Cleanup deterministic example
- Implement minimal reflection probes (fixed macOS, iOS, and Android).
- optimize batch_and_prepare_render_phase
- add
storage_texture
option to as_bind_group macro- Revert rendering-related associated type name changes
- Meshlet prep
- Reuse sampler when creating cached bind groups
- Add Animated Material example
- Update to wgpu 0.19 and raw-window-handle 0.6
- Fix bug where Sprite::rect was ignored
- Added documentation explaining the difference between lumens and luxes
- Fix infinite asset preparation due to undrained AssetEvent events
- Workaround for ICE in the DXC shader compiler in debug builds with an
EnvironmentMapLight
- Refactor tonemapping example's image viewer update into two systems
- Add
Mesh
transformation- Fix specular envmap in deferred
- Add
Meshable
trait and implement meshing for 2D primitives- Optimize extract_clusters and prepare_clusters systems
- RenderAssetPersistencePolicy → RenderAssetUsages
- RenderGraph Labelization
- Gate diffuse and specular transmission behind shader defs
- Add helpers for translate, rotate, and scale operations - Mesh
- CameraProjection::compute_frustum
- Added formats to
MeshVertexAttribute
constant's docstrings- Async pipeline compilation
- sort by pipeline then mesh for non transparent passes for massively better batching
- Added remove_indices to Mesh
- Implement irradiance volumes.
- Mesh insert indices
- Don't try to create a uniform buffer for light probes if there are no views.
- Properly check for result when getting pipeline in Msaa
- wait for render app when main world is dropped
- Deprecate shapes in
bevy_render::mesh::shape
- Cache the QueryState used to drop swapchain TextureViews
- Multithreaded render command encoding
- Fix
Quad
deprecation message mentioning a type that doesn't exist- Stop extracting mesh entities to the render world.
- Stop copying the light probe array to the stack in the shader.
- Add
Mesh::merge
- Call a TextureAtlasLayout a layout and not an atlas
- fix shadow batching
- Change light defaults & fix light examples
- New Exposure and Lighting Defaults (and calibrate examples)
- Change MeshUniform::new() to be public.
- Rename Core Render Graph Labels
- Support optional clear color in ColorAttachment.
- irradiance: use textureSampleLevel for WebGPU support
- Add configuration for async pipeline creation on RenderPlugin
- Derive Reflect for Exposure
- Add
MeshPipelineKey::LIGHTMAPPED
as applicable during the shadow map pass.- Irradiance volume example tweaks
- Disable irradiance volumes on WebGL and WebGPU.
- Remove
naga_oil
dependency frombevy_pbr
A-Scenes #
- Re-export
ron
inbevy_scene
- Fix load scene example to use proper serialization format for rotation field
- Mention DynamicSceneBuilder in doc comment
- Mention DynamicSceneBuilder in scene example
- Implement Std traits for
SceneInstanceReady
- Change SceneSpawner::spawn_dynamic_sync to return InstanceID
- Fix scene example
- Send
SceneInstanceReady
only once per scene
A-Utils #
- bevy_utils: Export
generate_composite_uuid
utility function - Save an instruction in
EntityHasher
- Add SystemTime to bevy_utils
- Re-export smallvec crate from bevy_utils
- Enable cloning EntityHashMap and PreHashMap
- impl
Borrow
andAsRef
forCowArc
- Hash stability guarantees
- Deprecating hashbrown reexports
- Update ahash to 0.8.7
A-UI #
- ui material: fix right border width
- Add PartialEq to Anchor
- UI Material: each material should have its own buffer
- UI Materials: ignore entities with a
BackgroundColor
component - Fix panic when using image in UiMaterial
- Make clipped areas of UI nodes non-interactive
- Fix typo in resolve_outlines_system
- Clip outlines by the node's own clipping rect, not the parent's.
- Give UI nodes with
Display::None
an empty clipping rect - Create serialize feature for bevy_ui
- Made the remaining types from bevy_ui to reflect the Default trait if…
- Camera-driven UI
- fix occasional crash moving ui root nodes
- Fix panic on Text UI without Cameras
- Allow user to choose default ui camera
- Rustdoc links in bevy_ui
- Avoid unconditionally unwrapping the Result - UI Stack System
A-Assets + A-Diagnostics #
A-Audio + A-Reflection #
A-Audio #
- Add
VolumeLevel::ZERO
- Deduplicate systems in bevy_audio
- Non-Intrusive refactor of
play_queued_audio_system()
- docs: AnimationPlayer::play doesn't have transition_duration arg
- Remove the ability to ignore global volume
- Optional override for global spatial scale
A-Tasks #
- Make FakeTask public on singlethreaded context
- Re-export
futures_lite
inbevy_tasks
- bump bevy_tasks futures-lite to 2.0.1
- Fix wrong transmuted type in
TaskPool::scope_with_executor_inner
- Use
std::thread::sleep
instead of spin-waiting in the async_compute example
A-ECS #
- Use
EntityHashMap
forEntityMapper
- Allow registering boxed systems
- Remove unnecessary if statement in scheduler
- Optimize
Entity::eq
- Add 'World::run_system_with_input' function + allow
World::run_system
to get system output - Update
Event
send methods to returnEventId
- Some docs for IntoSystemSet
- Link to
In
inpipe
documentation - Optimise
Entity
with repr align & manualPartialOrd
/Ord
- Allow #[derive(Bundle)] on tuple structs (take 3)
- Add an
Entry
api toEntityWorldMut
. - Make impl block for RemovedSystem generic
- Append commands
- Rustdoc example for Ref
- Link to
Main
schedule docs from other schedules - Warn that Added/Changed filters do not see deferred changes
- Fix non-functional nondeterministic_system_order example
- Copy over docs for
Condition
trait from PR #10718 - Implement
Drop
forCommandQueue
- Split WorldQuery into WorldQueryData and WorldQueryFilter
- Make IntoSystemConfigs::into_configs public API (visible in docs)
- Override QueryIter::fold to port Query::for_each perf gains to select Iterator combinators
- Deprecate QueryState::for_each_unchecked
- Clarifying Commands' purpose
- Make ComponentId typed in Components
- Reduced
TableRow
as
Casting - Add
EntityCommands.retain
andEntityWorldMut.retain
- Remove unnecessary ResMut in examples
- Add a couple assertions for system types
- Remove reference to default schedule
- Improve
EntityWorldMut.remove
,retain
anddespawn
docs by linking to more detail - Reorder fields in SystemSchedule
- Rename
WorldQueryData
&WorldQueryFilter
toQueryData
&QueryFilter
- Fix soundness of
UnsafeWorldCell
usage example - Actually check alignment in BlobVec test aligned_zst
- Rename
Q
type parameter toD
when referring toWorldQueryData
- Allow the editing of startup schedules
- Auto insert sync points
- Simplify lifetimes in
QueryState
methods - Add is_resource_changed_by_id + is_resource_added_by_id
- Rename some lifetimes (ResMut etc) for clarity
- Add non-existent entity behavior to Has doc
- Fix typo in docs for Has
- Add insert_state to App.
- Explain Changed, Added are not archetype filters
- Add missing colon in
States
documentation - Explain EventWriter limits concurrency
- Better doc for SystemName
- impl ExclusiveSystemParam for WorldId
- impl ExclusiveSystemParam for PhantomData
- Remove little warn on bevy_ecs
- Rename
ArchetypeEntity::entity
intoArchetypeEntity::id
- Fixed Typo in the description of EntityMut
- Implement Deref and DerefMut for In
- impl ExclusiveSystemParam for SystemName
- Print a warning for un-applied commands being dropped from a CommandQueue
- Implement TypePath for EntityHash
- Fix integer overflow in BlobVec::push for ZST
- Fix integer overflow in BlobVec::reserve_exact
- StateTransitionEvent
- Restore support for running
fn
EntityCommands
on entities that might be despawned - Remove apply_deferred example
- Minimize small allocations by dropping the tick Vecs from Resources
- Change Entity::generation from u32 to NonZeroU32 for niche optimization
- fix B0003 example and update logs
- Unified identifer for entities & relations
- Simplify conditions
- Add example using
State
in docs - Skip rehashing TypeIds
- Make
TypeId::hash
more robust in case of upstream rustc changes - Fix doc of [
Schedules
] to mention exclusion of current schedule. - Dynamic queries and builder API
- Remove duplicate
#[automatically_derived]
in ECS macro - Get Change Tick methods for Resources
- Optional state
- Double the capacity when BlobVec is full
- document which lifetime is needed for systemparam derive
- refactor: Simplify lifetimes for
Commands
and related types - Implement
Debug
forCommandQueue
- Fix typo in comment
- Rename Schedule::name to Schedule::label
- Exclusive systems can now be used for one-shot systems
- added ability to get
Res<T>
fromWorld
withWorld::get_resource_ref
- bevy_ecs: Add doc example for par_iter_mut (#11311)
- Add an example demonstrating how to send and receive events in the same system
- Add a doctest example for EntityMapper
- Rephrase comment about Local
for clarity. (Adopted) - Use batch spawn in benchmarks
- Fix bug where events are not being dropped
- Make Archetypes.archetype_component_count private
- Deprecated Various Component Methods from
Query
andQueryState
System::type_id
Consistency- Typo in [
ScheduleLabel
] derive macro- Mention Resource where missing from component/resource related type docs
- Expose query accesses
- Add a method for detecting changes within a certain scope
- Fix double indirection when applying command queues
- Immediately poll the executor once before spawning it as a task
- Fix small docs misformat in
BundleInfo::new
FilteredEntityRef
conversionsA-Rendering + A-Animation #
A-ECS + A-Meta #
A-Rendering + A-UI #
- Provide GlobalsUniform in UiMaterial shaders
- Include UI node size in the vertex inputs for UiMaterial.
- UI Texture 9 slice
- Optional ImageScaleMode
A-Math #
- Define a basic set of Primitives
- Add and impl Primitives
- Add winding order for
Triangle2d
- Use minor and major radii for
Torus
primitive shape - Remove
From
implementations from the direction types - Impl
TryFrom
vector for directions and addInvalidDirectionError
- Add
Direction2d::from_xy
andDirection3d::from_xyz
- Implement
Neg
forDirection2d
andDirection3d
- Add constants for
Direction2d
andDirection3d
- Add
approx
feature tobevy_math
- Add
libm
feature tobevy_math
- Add
new_and_length
method toDirection2d
andDirection3d
- Update
glam
,encase
andhexasphere
- Implement bounding volume types
- Remove
Default
impl forCubicCurve
- Implement bounding volumes for primitive shapes
- Improve
Rectangle
andCuboid
consistency - Change
Ellipse
representation and improve helpers - Add
Aabb2d::new
andAabb3d::new
constructors - Add geometric primitives to
bevy_math::prelude
- Direction: Rename
from_normalized
tonew_unchecked
- Implement bounding volume intersections
- Add
new
constructors forCircle
andSphere
- Derive PartialEq, Serialize, Deserialize and Reflect on primitives
- Document RegularPolygon
- Add RayTest2d and RayTest3d
- Add more constructors and math helpers for primitive shapes
- Add
Capsule2d
primitive - Add volume cast intersection tests
- Add Clone to intersection test types
- Implement
approx
traits for direction types - Support rotating
Direction3d
byQuat
- Rename RayTest to RayCast
- Add example for bounding volumes and intersection tests
- Dedicated primitive example
- Un-hardcode positions and colors in
2d_shapes
example
A-Build-System #
- check for all-features with cargo-deny
- Bump actions/github-script from 6 to 7
- Add doc_markdown clippy linting config to cargo workspace
- Enable
clippy::undocumented_unsafe_blocks
warning across the workspace - Remove trailing whitespace
- Move remaining clippy lint definitions to Cargo.toml
- Add
clippy::manual_let_else
at warn level to lints - Remove unused import
- Rename functions and variables to follow code style
- Remove unused variable
- add libxkbcommon-x11-0 to the default linux dependencies
- fix patches for example showcase after winit update
- finish cleaning up dependency bans job
- Bump actions/upload-artifact from 2 to 4
- Publish dev-docs with Github Pages artifacts (2nd attempt)
- example showcase patches: use default instead of game mode for desktop
- Bump toml_edit in build-template-pages tool
- Miri is failing on latest nightly: pin nightly to last known working version
- Bump dev-docs pages actions
- Unpin nightly for miri
- documentation in CI: remove lock file
- Bump actions/cache from 3 to 4
- simplify animated_material example
- example showcase: fix window resized patch
- run examples on macOS to validate PRs
- Inverse
missing_docs
logic - Bump peter-evans/create-pull-request from 5 to 6
A-Gizmos #
- Fix float precision issue in the gizmo shader
- Gizmo Arrows
- Move Circle Gizmos to Their Own File
- move gizmo arcs to their own file
- Warn when bevy_sprite and bevy_pbr are not enabled with bevy_gizmos
- Multiple Configurations for Gizmos
- Fix gizmos app new panic
- Use Direction3d for gizmos.circle normal
- Implement Arc3D for Gizmos
- Insert Gizmos config instead of Init
- Drawing Primitives with Gizmos
- fix(primitives): fix polygon gizmo rendering bug
- Fix global wireframe behavior not being applied on new meshes
- Overwrite gizmo group in
insert_gizmo_group
A-Rendering + A-Math #
- Split
Ray
intoRay2d
andRay3d
and simplify plane construction - Introduce AspectRatio struct
- Implement meshing for
Capsule2d
- Implement
Meshable
for some 3D primitives
A-Core #
- Derive
Debug
forFramecount
- Don't unconditionally enable bevy_render or bevy_assets if mutli-threaded feature is enabled
A-Windowing #
- Some explanations for Window component
- don't run update before window creation in winit
- add new event
WindowOccluded
from winit - Add comment about scale factor in
WindowMode
- Refactor function
update_accessibility_nodes
- Change
Window
scale factor to f32 (adopted) - Reexport winit::platform::android::activity::* in bevy_winit
- Update winit dependency to 0.29
- Remove CanvasParentResizePlugin
- Use
WindowBuilder::with_append()
to append canvas - Fix perf degradation on web builds
- mobile and webgpu: trigger redraw request when needed and improve window creation
- Remove unnecessary unsafe impls for WinitWindows on Wasm
- Fix Reactive and ReactiveLowPower update modes
- Change
WinitPlugin
defaults to limit game update rate when window is not visible (for real this time) - Cleanup bevy winit
- Add
name
tobevy::window::Window
- Avoid unwraps in winit fullscreen handling code
A-UI + A-Transform + A-Text #
A-Animation #
- Fix animations resetting after repeat count
- Add Debug, PartialEq and Eq derives to bevy_animation.
- support all types of animation interpolation from gltf
- Clean up code to find the current keyframe
- Skip alloc when updating animation path cache
- Replace the
cubic_spline_interpolation
macro with a generic function - Animatable trait for interpolation and blending
A-ECS + A-Pointers #
A-ECS + A-Utils #
A-Reflection #
- Fix issue with
Option
serialization - fix
insert_reflect
panic caused byclone_value
- Remove pointless trait implementation exports in
bevy_reflect
- Fix nested generics in Reflect derive
- Fix debug printing for dynamic types
- reflect: maximally relax
TypePath
bounds - Use
static_assertions
to check for trait impls - Add
ReflectFromWorld
and replace theFromWorld
requirement onReflectComponent
andReflectBundle
withFromReflect
- Fix reflected serialization/deserialization on
Name
component - Add Reflection for Wrapping/Saturating types
- Remove TypeUuid
- Fix warnings in bevy_reflect
- bevy_reflect: Type parameter bounds
- bevy_reflect: Split
#[reflect(where)]
- reflection: replace
impl_reflect_struct
withimpl_reflect
- Add the ability to manually create ParsedPaths (+ cleanup)
- bevy_reflect: Reflect
&'static str
- Improve DynamicStruct::insert
- Missing registrations
- Add
ReflectKind
- doc(bevy_reflect): add note about trait bounds on
impl_type_path
- bevy_reflect_derive: Clean up attribute logic
A-ECS + A-Tasks #
A-Pointers #
- Remove a ptr-to-int cast in
CommandQueue::apply
- Fix memory leak in dynamic ECS example
- bevy_ptr: fix
unsafe_op_in_unsafe_fn
lint
A-ECS + A-Reflection #
A-Reflection + A-Gizmos #
No area label #
- Fix intra-doc link warnings
- Fix minor issues with custom_asset example
- Prepend
root_path
to meta path in HttpWasmAssetReader - support required features in wasm examples showcase
- examples showcase: use patches instead of sed for wasm hacks
- Add [lints] table, fix adding
#![allow(clippy::type_complexity)]
everywhere - Bumps async crates requirements to latest major version
- delete methods deprecated in 0.12
- Ran
cargo fmt
onbenches
crate - Remove unnecessary path prefixes
- Fix typos in safety comment
- Substitute
get(0)
withfirst()
- Remove identity
map
calls - Renamed Accessibility plugin to AccessKitPlugin in bevy_winit
- Reorder impl to be the same as the trait
- Replace deprecated elements
- Remove unnecessary parentheses
- Replace deprecated elements
- Simplify equality assertions
- Add Solus package requrements to linux_dependencies.md
- Update base64 requirement from 0.13.0 to 0.21.5
- Update sysinfo version to 0.30.0
- Remove unnecessary parens
- Reorder impl to be the same as the trait
- Fix ci xvfb
- Replace or document ignored doctests
- Add static assertions to bevy_utils for compile-time checks
- Fix missed explicit conversions in examples
- Remove unused event-listener dependency
- Fixed typo in generate_custom_mesh.rs example
- Extract examples
CameraController
into a module - Use EntityHashMap whenever possible
- Fix link to plugin guidelines
- [doc] Fix typo and formatting in CONTRIBUTING.md
- add a required feature for shader_material_glsl
- Update ruzstd requirement from 0.4.0 to 0.5.0
- Tweak gamepad viewer example style
- Add
.toml
extension to.cargo/config_fast_builds
- Add README to benches
- Fix panic in examples using argh on the web
- Fix cyclic dep
- Enable the
unsafe_op_in_unsafe_fn
lint - Update erased-serde requirement from 0.3 to 0.4
- Fix example send_and_receive_events
- Update cursor.rs
- Use the
Continuous
update mode in stress tests when unfocused - Don't auto insert on the extract schedule
- Update tracing-tracy requirement from 0.10.4 to 0.11.0 and tracy-client requirement from 0.16.4 to 0.17.0
- Use TypeIdMap whenever possible
- Fix a few typos in error docs
- bevy_render: use the non-send marker from bevy_core
- Ignore screenshots generated by
screenshot
example - Docs reflect that
RemovalDetection
also yields despawned entities - bevy_dynamic_plugin: fix
unsafe_op_in_unsafe_fn
lint - Replace
crossbeam::scope
reference withthread::scope
in docs - Use question mark operator when possible
- Fix a few Clippy lints
- WebGPU: fix web-sys version
- Remove map_flatten from linting rules
- Fix duplicate
encase_derive_impl
dependency
A-App #
- add regression test for #10385/#10389
- Fix typos plugin.rs
- Expressively define plugins using functions
- Mark
DynamicPluginLoadError
internal error types as source
A-Diagnostics #
- Fix Line for tracy version
- Some doc to bevy_diagnostic
- Print to stderr from panic handler in LogPlugin
- Add ability to panic to logs example
- Make sure tracy deps conform to compatibility table
- Describe purpose of bevy_diagnostic
- Add support for updating the tracing subscriber in LogPlugin
- Replace
DiagnosticId
byDiagnosticPath
- fix link to tracy
- Fix sysinfo CPU brand output
A-Rendering + A-ECS #
A-Assets + A-Math #