https://bevyengine.org/news/bevy-0-7/ bevy logo bevy icon News Learn News Community Assets Merch Donate heart icon Get Started Bevy 0.7 Posted on April 15, 2022 by Carter Anderson ( [github_gre] @cart [twitter_gr] @cart_cart [youtube_gr] cartdev ) [mushroom] 'Stylized mushrooms' scene by QumoDone rendered in Bevy. This scene is licensed under Creative Commons Attribution. Thanks to 123 contributors, 349 pull requests, and our generous sponsors, I'm happy to announce the Bevy 0.7 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 Quick Start Guide to get started. Bevy is also 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.7, check out our 0.6 to 0.7 Migration Guide. As always, there are a ton of new features, bug fixes, and quality of life tweaks in this release, but here are some of the highlights: * Skeletal animation and mesh skinning * GLTF animation importing * Unlimited* point lights in a scene * Improved clustered forward rendering: dynamic/adaptive clustering and faster, more accurate cluster assignment * Compressed texture support (KTX2 / DDS / .basis): load more textures in a scene, faster * Compute shader / pipeline specialization: Bevy's flexible shader system was ported to compute shaders, enabling hot reloading, shader defs, and shader imports * Render to texture: cameras can now be configured to render to a texture instead of a window * Flexible mesh vertex layouts in shaders * ECS improvements: Order systems using their names, Query::many_mut, use conflicting parameters in systems via ParamSets, WorldQuery derives * Documentation improvements: better examples, more doc tests and more coverage * More audio control: pause, volume, speed, and looping * Power usage options to enable only updating Bevy Apps when input occurs Skeletal Animation # authors: @james7132, @mockersf, @lassade, @Looooong Bevy finally supports 3D skeletal animation! Scene Credits: Tanabata evening - Kyoto inspired city scene by Mathias Tossens is licensed under Creative Commons Attribution. Character model and animation are royalty free assets from Mixamo. Skeletal animations can now be played, paused, scrubbed, looped, reversed, and speed controlled using the new AnimationPlayer component and AnimationClip asset: #[derive(Component)] struct Animations { dance: Handle, } fn start_dancing(mut query: Query<(&Animations, &mut AnimationPlayer)>) { for (animations, mut animation_player) in query.iter_mut() { animation_player.play(animations.dance.clone()); } } AnimationPlayer can also be used to animate arbitrary Transform components, not just skeletons! This critical feature has been a long time coming, but we wanted to build it in a way that meshed nicely with the new Bevy renderer and didn't just "hack things in". This builds on our new Flexible Mesh Vertex Layouts, Shader Imports, and Material systems, which ensures that this logic is flexible and reusable, even with non-standard meshes and custom render pipelines. And we're just getting started! Multi-track animation blending and higher level animation state management should arrive in the very near future. Now is a great time to start contributing animation features to Bevy. We've smashed through most of the foundational technical hurdles and what remains is largely high level api design choices. We already have a couple of draft RFCs open in these areas: Animation Composition and Animation Primitives. Feel free to join the conversation! GLTF Animation Importing # authors: @mockersf Bevy's GLTF importer was extended to import GLTF animations into the new AnimationPlayer system. This supports both "skeletal animation" and arbitrary transform animations: struct FoxAnimations { walk: Handle, } fn setup(mut commands: Commands) { commands.spawn_scene(asset_server.load("models/animated/Fox.glb#Scene0")); commands.insert_resource(FoxAnimations { walk: asset_server.load("models/animated/Fox.glb#Animation0"), }); } fn play_on_load( animations: Res, mut players: Query<&mut AnimationPlayer, Added>, ) { for mut player in players.iter_mut() { player.play(animations.walk.clone()).repeat(); } } Unlimited* Point Lights # authors: Rob Swain (@superdump), @robtfm Bevy can now render scenes with arbitrary numbers of point lights on platforms that support storage buffers (which is basically everything but WebGL). In the last Bevy release (0.6) we added Clustered Forward Rendering, which is a rendering technique that optimizes each fragment's light calculation costs by assigning lights to sub-volumes of the visible volume, called "clusters". However in the interest of platform compatibility (WebGL), we initially limited ourselves to 256 lights because that is what fit in a uniform buffer binding. In Bevy 0.7, we added the ability to automatically "upgrade" to using unbounded storage buffers for Clustered Forward Rendering on platforms that support them, enabling unlimited* point lights. There is an asterisk there because in practice this is limited by memory and hardware constraints. Light Clustering Features and Optimizations # authors: Rob Swain (@superdump), @robtfm, @dataphract, @cart With the upper limit of 256 point lights removed, the only limit on lights is what the hardware can support and bottlenecks in our algorithms. To increase the number of lights, we made a number of optimizations to our clustering algorithms. * Dynamic Light Clusters + By default cluster x/y slices are now dynamically configured based on the lights in the scene, which can significantly increase performance in some scenes. + The clustering behavior is now also user-configurable as FixedZ (the new default dynamic x/y behavior, fixing the number of z slices), custom fixed x/y/z slice values, single-cluster, and "no clustering", giving you control when you know a certain cluster configuration will perform even better. + Additionally, in 0.6 the visible volume that is covered by all the clusters basically matched the full visible volume of the view frustum. This meant that if all the point lights were in the foreground, all the clusters beyond the lights were wasted space. In 0.7, it is possible to limit the far bound to be closer than the camera far bound, which means the lights can be spread across more clusters, which can significantly increase rendering performance. * Iterative Sphere Refinement: Bevy now uses the Just Cause 3 iterative sphere refinement approach to cluster assignment, which gives us a ~10% performance increase on some benchmarks and more accurate clustering (which can also improve render performance). * Light Frustum Change Detection: We now use Bevy ECS's change detection feature to only recalculate the view frustum of lights that have changed. * Cluster Assignment Optimizations: The cluster assignment data access patterns and data structures received a variety of tweaks that improved performance. Here is a video illustrating a progression from the old limit of 256 point lights to 25,000 point lights at 60fps! (Note that the 25,000 lights example disables the debug light spheres to ensure that light calculations are the bottleneck) And we have even more clustering optimizations in the works! Configurable Light Visibility # authors: @robtfm Lights can now be turned on and off using Bevy's standard Visibility component: commands.spawn(PointLightBundle { visibility: Visibility { is_visible: false, }, ..default() }); Compressed GPU Textures # authors: Rob Swain (@superdump) As scenes grow larger, so do their assets. Compressing these assets is a great way to save space. The Amazon Bistro scene featured below has well over 1GB of compressed textures. PNG is a popular compressed format, but it must be decompressed before the GPU can use it. This can be a slow process for large scenes. Those textures are then used in their uncompressed form, taking up large quantities of limited memory. Compressed GPU textures can be used directly in their compressed format by the GPU and can be loaded without any additional processing. This reduces load times significantly. As they remain compressed, this also reduces RAM usage significantly. The Bistro scene took a total of 12.9s to load with PNG textures, but only 1.5s with compressed textures - taking approximately a tenth of the load time! The total RAM usage was ~12GB with uncompressed textures, and 5GB with compressed textures, less than half! The benefits don't stop there either - because the textures are compressed and can be used by the GPU in that format, reading from them uses less memory bandwidth, which can bring performance benefits. The Bistro scene gains about 10% in frame rate from using compressed textures. bistro compressed Another benefit is that mipmaps are supported, which makes for smoother, less noisy textures. Bevy currently doesn't have support for automatically generating mipmaps for uncompressed textures, so using compressed textures is a nice way to have mipmaps now! In summary, Bevy now supports loading compressed textures from .dds, .ktx2, and .basis files. This includes support for the standard ASTC, BCn, and ETC2 formats, as well as 'universal' formats like ETC1S and UASTC that can be transcoded to the standard formats supported by specific systems at runtime. The glTF loader was also extended to support loading these formats. These features can be enabled using the dds, ktx2, and basis-universal cargo features. Render To Texture # authors: @HackerFoo Bevy now has initial support for rendering to texture by configuring the render_target field on Camera. This enables scenarios such as mirrors, split screen, 2d UI in 3d space, portals, etc. Note that the current implementation is relatively low level. It will generally require interacting with Bevy's Render Graph and defining new camera types. If you would like to use this feature now, the render_to_texture example illustrates the steps required. We have plans for "high level render targets" that will make rendering to textures possible in just a few lines of code. Stay tuned for details! Bevy-Native Compute Shaders # authors: @Ku95 Bevy's flexible asset-driven shader system was ported to compute shaders/pipelines, enabling hot reloading, shader defs, shader imports, and pipeline specialization based on user-configurable keys: #import "shaders/game_of_life_texture_bind_group.wgsl" [[stage(compute), workgroup_size(8, 8, 1)]] fn game_of_life_update([[builtin(global_invocation_id)]] invocation_id: vec3) { let location = vec2(i32(invocation_id.x), i32(invocation_id.y)); let alive = is_location_alive(location); // shader defs are configurable at runtime, prompting new variants of the shader to be compiled #ifdef WRITE_OUTPUT storageBarrier(); textureStore(texture, location, vec4(f32(alive))); #endif } Flexible Mesh Vertex Layouts # authors: @cart, @parasyte In Bevy 0.7, it is now easy to make shaders support any Mesh vertex layout and arbitrary vertex attributes. Bevy's "shader pipeline specialization" system was extended to support "specializing on mesh vertex layouts". For most Bevy users, this means that Materials, including the built in StandardMaterial and custom shader materials now support arbitrary Meshes automatically, provided those Meshes have the vertex attributes required by the material shaders. It also means that if your Mesh is missing any attribute required by its material, rendering can fail gracefully. We also made use of this system to implement joint weights and indices for our new Skeletal Animation implementation. For Bevy users that like to write lower level graphics pipelines, this feature makes it possible to easily and efficiently specialize your pipelines according to Mesh vertex layouts: impl SpecializedMeshPipeline for SomeCustomPipeline { type Key = SomeCustomKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { // this is a layout that matches the requirements requested, // but catered to whatever mesh is currently being rendered let vertex_buffer_layout = layout.get_layout(&[ Mesh::ATTRIBUTE_POSITION.at_shader_location(0), Mesh::ATTRIBUTE_NORMAL.at_shader_location(1), Mesh::ATTRIBUTE_UV_0.at_shader_location(2), ])?; Ok(RenderPipelineDescriptor { vertex: VertexState { buffers: vec![vertex_buffer_layout], /* define the rest of the vertex state here */ }, /* define the rest of the mesh pipeline here */ }) } Camera Marker Components # authors: @jakobhellermann In Bevy 0.7, Cameras now use the "marker component" pattern to determine the "camera type" (ex: 3D, 2D, UI), rather than using string names. This means that it is now cheaper and easier to select cameras of a specific type: fn move_3d_camera_system(transforms: Query<&mut Transform, With>) { for mut camera in transforms.iter_mut() { // move camera here } } Ergonomic System Ordering # authors: @cart, @aevyrie, @alice-i-cecile, @DJMcNab Bevy uses "labels" to define ordering constraints between its ECS systems when they run in parallel. In previous versions of Bevy, the only way to order systems was to define custom labels: #[derive(SystemLabel, Clone, Hash, Debug, PartialEq, Eq)] struct UpdateVelocity; app .add_system(update_velocity.label(UpdateVelocity)) .add_system(movement.after(UpdateVelocity)) In Bevy 0.7, manually defining labels is no longer required. You can order systems using functions, just like you do when adding systems! app .add_system(update_velocity) .add_system(movement.after(update_velocity)) This is accomplished by "auto-labeling" systems with their TypeId (the label type is SystemTypeIdLabel). Internally ordering still uses labels. The Bevy ECS labeling system is powerful and there are still legitimate use cases for custom labels (such as labeling multiple systems with the same label and exporting a stable public API as a plugin author). But most common use cases can take advantage of the ergonomic auto-labeling functionality. Default Shorthand # authors: @cart Bevy makes heavy use of Rust's struct update pattern in combination with the Default trait when initializing entities. This significantly reduces the amount of typing required by enabling developers to only fill in the fields they want to change. The standard way of doing this is to write out ..Default::default(): commands.spawn_bundle(SpriteBundle { texture: some_texture, ..Default::default() }) This is much better than filling in each field's component manually: commands.spawn(SpriteBundle { texture: some_texture, sprite: Default::default(), transform: Default::default(), global_transform: Default::default(), visibility: Default::default(), }); However this can feel repetitive when you're doing it for tens or hundreds of entities. We added a way to make this even easier, without needing to resort to macros: commands.spawn_bundle(SpriteBundle { texture: some_texture, ..default() }) This is equivalent in functionality to ..Default::default(), it's just more compressed. And you can still use the longer form if you prefer. The default() function is included in Bevy's prelude, so you don't need to manually import it. Ergonomics for the win! Query::many # authors: @alice-i-cecile Bevy ECS solves a hard problem: providing easy and fast access to data in parallel while still respecting Rust's strict mutability and ownership rules. Since our first release, we've supported efficiently accessing specific entities in our ECS Queries: struct SomeEntities { a: Entity, b: Entity, } fn system(mut query: Query<&mut Transform>, entities: Res) { let a_transform = query.get_mut(entities.a).unwrap(); } However, to respect Rust's mutability rules, we need to disallow apis that might produce "aliased mutability". Seasoned Bevy users will probably recognize this Rust borrow checker error: fn system(mut query: Query<&mut Transform>, entities: Res) { let a_transform = query.get_mut(entities.a).unwrap(); // This line fails to compile because `query` is already mutably borrowed above let b_transform = query.get_mut(entities.b).unwrap(); } You know Entity A and Entity B are different entities at runtime. But Rust's borrow checker has no way to know that at compile time! I'm sure you can imagine game development scenarios that would benefit from having mutable access to multiple components at the same time. This borrow checker restriction was a common pain point and the workarounds were ... not fun (using scopes to ensure conflicting accesses are dropped, copying data, re-querying things, etc). Fortunately, Bevy 0.7 introduces a brand new set of apis to save the day! fn system(mut query: Query<&mut Transform>, entities: Res) { // Takes an array of entities and returns an array of mutable Query results // This will panic if there are entity conflicts or the entities do not exist let [a_transform, b_transform] = query.many_mut([entities.a, entities.b]); } There are plenty of variants: // Same as many_mut, but returns a Result instead of panicking if let Ok([a_transform, b_transform]) = query.get_many_mut([entities.a, entities.b]) { } // There are also immutable/read-only variants let [a_transform, b_transform] = query.many([entities.a, entities.b]); if let Ok([a_transform, b_transform]) = query.get_many([entities.a, entities.b]) { } And they all support arbitrary numbers of entities: let [a, b, c] = query.many([entity_a, entity_b, entity_c]); ParamSets # authors: @bilsen To prevent aliased mutability, Bevy ECS disallows systems that have parameters that conflict with each other. For example, if two Queries both request write access to the same component in the same "archetype", that could result in aliased mutable access, so Bevy disallows that system and errors out. Previous versions of Bevy supported conflicting Queries in the same system using QuerySets, which only allow access to one Query in the set at a time: // These queries could each return a mutable A component for the same entity, so they must be put in a set to be considered a valid system. fn system(mut set: QuerySet<(QueryState<(&mut A, &B)>, QueryState<(&mut A, &C)>)>) { for (a, b) in set.q0().iter_mut() { } } Bevy 0.7 removes QuerySet in favor of ParamSet, which generalizes the QuerySet pattern for any system parameter: fn system(mut set: ParamSet<(Query<(&mut A, &B)>, Query<(&mut A, &C)>)>) { for (a, b) in set.p0().iter_mut() { } } But ParamSets aren't just limited to Queries! Consider this example, where the EventWriter parameter (which internally accesses the Events resource) conflicts with the raw access to that resource. Previously, expressing this wouldn't be possible. But with ParamSets, it is! fn system(mut set: ParamSet<(EventWriter, ResMut>)>) { for jump_event in set.p1().drain() { } } We still recommend avoiding ParamSets where possible for clarity's sake. But every so often they are a necessary and useful tool! Deref / DerefMut Derives # authors: @MrGVSV Rust encourages the use of the newtype pattern when expanding a type with new functionality or meaning. This is also a useful tool in Bevy: #[derive(Component)] struct Items(Vec); fn give_sword(mut query: Query<&mut Items>) { for mut items in query.iter_mut() { items.0.push(Item::new("Flaming Poisoning Raging Sword of Doom")); } } This works just fine, but that 0 at the end of items.0 sticks out like a sore thumb. Many of us in the Bevy Org think .0 has no place in public apis. But the newtype pattern is still useful! Ideally, Rust would provide a way to express that Items is a new type, while transparently provided access to the Vec stored within. There are designs being discussed by the Rust team, but we don't want to wait for nice things! Fortunately, the Deref / DerefMut traits in std provide the behavior we want. Users can already manually implement these traits, but for such a common pattern, we decided that providing our own trait derives was worth it. In Bevy 0.7, you can now derive Deref and DerefMut, enabling much nicer public apis: #[derive(Component, Deref, DerefMut)] struct Items(Vec); fn give_sword(mut query: Query<&mut Items>) { for mut items in query.iter_mut() { // No more .0! items.push(Item::new("Flaming Poisoning Raging Sword of Doom")); } } Astute std doc readers might notice that the Rust team recommends only using Deref/DerefMut for smart pointers, to avoid confusion. Components like Items are not smart pointers. We choose to ignore this advice, as this pattern works, is already widely used in the Rust ecosystem, and Good UX Comes First. WorldQuery Derives # authors: @mvlabat Sometimes when building Bevy Apps you might find yourself repeating the same sets of components over and over in your queries: fn move_players(mut players: Query<(&mut Transform, &mut Velocity, &mut PlayerStats)>) { for (transform, velocity, stats) in players.iter_mut() { } } fn player_gravity(mut players: Query<(Entity, &mut Transform, &mut Velocity, &mut PlayerStats)>) { for (entity, transform, velocity, stats) in players.iter_mut() { } } Maybe you've gotten tired of typing the same components over and over. In Bevy 0.7, you can now easily create your own custom WorldQuery trait implementations with the WorldQuery derive: #[derive(WorldQuery)] struct PlayerMovementQuery<'w> { transform: &'w mut Transform, velocity: &'w mut Velocity, stats: &'w mut PlayerStats, } fn move_players(mut players: Query) { for player in players.iter_mut() { } } fn player_gravity(mut players: Query<(Entity, PlayerMovementQuery)>) { for (entity, player) in players.iter_mut() { } } World::resource # authors: @alice-i-cecile We've noticed that the majority of direct World resource access immediately unwraps the results of get_resource: let time = world.get_resource::