https://bevyengine.org/news/bevy-0-11/ [ ] [icon-hambu] Bevy Engine News [*] Bevy Engine [icon-times] Main Menu Post * Screen Space Ambient Occlusion * Temporal Anti-Aliasing * Robust Contrast Adaptive Sharpening * Morph Targets * Parallax Mapping * Skyboxes * WebGPU Support * Improved Shader Imports * UI Node Borders * Grid UI Layout * Schedule-First ECS APIs * Nested System Tuples and Chaining * Gizmos * ECS Audio APIs * Global Audio Volume * Resource Support in Scenes * Scene Filtering * Default Font * UI Texture Atlas Support * Gamepad Rumble API * New Default Tonemapping Method * EntityRef Queries * Screenshot API * RenderTarget::TextureView * Improved Text Wrapping * Faster UI Render Batching * Better Reflect Proxies * FromReflect Ergonomics * Deref Derive Attribute * Simpler RenderGraph Construction * #[reflect(default)] on Enum Variant Fields * Delayed Asset Hot Reloading * Custom glTF Vertex Attributes * Stable TypePath * run_if for Tuples of Systems * Has Queries * Derive Event * Cubic Curve Example * Size Constraints Example * Display and Visibility Example * No More Bors! * New CI Jobs * What's Next? * Support Bevy * Contributors * Full Changelog * Getting Started * Learn * News * Community * Assets * Examples Donate heart icon GitHub repo * Screen Space Ambient Occlusion * Temporal Anti-Aliasing * Robust Contrast Adaptive Sharpening * Morph Targets * Parallax Mapping * Skyboxes [*] * WebGPU Support [icon-chevr] + What is WebGPU? + How it Works + WebGPU Examples * Improved Shader Imports * UI Node Borders * Grid UI Layout [*] * Schedule-First ECS APIs [icon-chevr] + Unraveling the Complexity * Nested System Tuples and Chaining * Gizmos * ECS Audio APIs * Global Audio Volume * Resource Support in Scenes * Scene Filtering * Default Font * UI Texture Atlas Support * Gamepad Rumble API * New Default Tonemapping Method * EntityRef Queries * Screenshot API * RenderTarget::TextureView * Improved Text Wrapping * Faster UI Render Batching * Better Reflect Proxies * FromReflect Ergonomics * Deref Derive Attribute * Simpler RenderGraph Construction * #[reflect(default)] on Enum Variant Fields * Delayed Asset Hot Reloading * Custom glTF Vertex Attributes * Stable TypePath * run_if for Tuples of Systems * Has Queries * Derive Event * Cubic Curve Example * Size Constraints Example * Display and Visibility Example * No More Bors! * New CI Jobs * What's Next? * Support Bevy * Contributors [*] * Full Changelog [icon-chevr] + Rendering + Audio + Diagnostics + Scenes + Transform + Hierarchy + Gizmo + Reflection + App + Windowing + Reflection + Hierarchy + Time + Assets + Windowing + Animation + UI + ECS + Rendering + Reflection + Scenes + Tasks + Math + Rendering + Assets + Meta + ECS + Scenes + Util + Input + Upgrades + Examples Bevy 0.11 Posted on July 09, 2023 by Bevy Contributors [with_ssao] Thanks to 166 contributors, 522 pull requests, community reviewers, and our generous sponsors, we're happy to announce the Bevy 0.11 release on crates.io! For those who don't know, Bevy is a refreshingly simple data-driven game engine built in Rust. You can check out our Quick Start Guide to try it today. It's free and open source forever! You can grab the full source code on GitHub. Check out Bevy Assets for a collection of community-developed plugins, games, and learning resources. To update an existing Bevy App or Plugin to Bevy 0.11, check out our 0.10 to 0.11 Migration Guide. Since our last release a few months ago we've added a ton of new features, bug fixes, and quality of life tweaks, but here are some of the highlights: * Screen Space Ambient Occlusion (SSAO): Increase scene render quality by simulating occlusion of "indirect" diffuse light * Temporal Anti-Aliasing (TAA): A popular anti-aliasing technique that blends the current frame with past frames using motion vectors to smooth out artifacts * Morph Targets: Animate vertex positions on meshes between predefined states. Great for things like character customization! * Robust Constrast Adaptive Sharpening (RCAS): Intelligently sharpens renders, which pairs nicely with TAA * WebGPU Support: Bevy can now render on the web faster and with more features using the modern WebGPU web API * Improved Shader Imports: Bevy shaders now support granular imports and other new features * Parallax Mapping: Materials now support an optional depth map, giving flat surfaces a feel of depth through parallaxing the material's textures * Schedule-First ECS APIs: A simpler and more ergonomic ECS system scheduling API * Immediate Mode Gizmo Rendering: Easily and efficiently render 2D and 3D shapes for debugging and editor scenarios * ECS Audio APIs: A more intuitive and idiomatic way to play back audio * UI Borders: UI nodes can now have configurable borders! * Grid UI Layout: Bevy UI now supports CSS-style grid layout * UI Performance Improvements: The UI batching algorithm was changed, yielding significant performance wins Screen Space Ambient Occlusion # authors: @JMS55, @danchia, @superdump Drag this image to compare Without SSAO With SSAO SSAO Only ssao_only Bevy now supports Screen Space Ambient Occlusion (SSAO). While Bevy already supported shadows from direct lights (DirectionalLight, PointLight, SpotLight) via shadow mapping, Bevy now supports shadows from indirect diffuse lighting such as AmbientLight or EnvironmentMapLight. These shadows give scenes a more "grounded" feel, by estimating how much surrounding geometry blocks incoming light via the screen-space depth and normal prepasses. You can try it out in the new SSAO example. Note that using SSAO with the newly added Temporal Anti-Aliasing leads to a large increase in quality and noise reduction. Platform support is currently limited - Only Vulkan, DirectX12, and Metal are currently supported. WebGPU support will come at a later date. WebGL likely won't be supported because it doesn't have compute shaders. Special thanks to Intel for their open source XeGTAO project, which was a huge help in developing this feature. Temporal Anti-Aliasing # authors: @JMS55, @DGriffin91 aa Alongside MSAA and FXAA, Bevy now supports Temporal Anti-aliasing (TAA) as an anti-aliasing option. TAA works by blending the newly rendered frame with past frames in order to smooth out aliasing artifacts in the image. TAA has become increasingly popular in the industry because of its ability to cover up so many rendering artifacts: it smooths out shadows (both global illumination and "casted" shadows), mesh edges, textures, and reduces specular aliasing of light on reflective surfaces. However because the "smoothing" effect is so apparent, some people prefer other methods. Here's a quick rundown of the following advantages and disadvantages of each anti-aliasing method that Bevy supports: * Multi-Sample Antialiasing (MSAA) + Does a good job at smoothing the edges of meshes (anti geometric aliasing). Does not help with specular aliasing. Performance cost scales with triangle count, and performs very poorly on scenes with many triangles * Fast Approximate Antialiasing (FXAA) + Does a decent job of dealing with both geometric and specular aliasing. Very little performance cost in all scenes. Somewhat blurry and low quality results * Temporal Antialiasing (TAA) + Does a very good job at dealing with both geometric and specular aliasing. Does a good job at dealing with temporal aliasing, where high-frequency details flicker over time or as you move the camera around or as things animate. Performance cost is moderate, and scales only with screen resolution. Chance of "ghosting" where meshes or lighting effects may leave trails behind them that fade over time. Although TAA helps with reducing temporal aliasing, it may also introduce additional temporal aliasing, especially on thin geometry or texture detail rendered at a distance. Requires 2 view's worth of additional GPU memory, as well as enabling the motion vector and depth prepasses. Requires accurate motion vector and depth prepasses, which complicates custom materials TAA implementations are a series of tradeoffs and rely on heuristics that are easy to get wrong. In Bevy 0.11, TAA is marked as an experimental feature for the following reasons: * TAA does not currently work with the following Bevy features: skinning, morph targets, and parallax mapping * TAA currently tends to soften the image a bit, which can be worked around via post-process sharpening * Our TAA heuristics are not currently user-configurable (and these heuristics are likely to change and evolve) We will continue to improve quality, compatibility, and performance in future releases. Please report any bugs you encounter! You can compare all of our anti-aliasing methods in Bevy's improved anti-aliasing example. Robust Contrast Adaptive Sharpening # authors: @Elabajaba Effects like TAA and FXAA can cause the final render to become blurry. Sharpening post processing effects can help counteract that. In Bevy 0.11 we've added a port of AMD's Robust Constrast Adaptive Sharpening (RCAS). Drag this image to compare TAA TAA+RCAS Notice that the texture on the leather part of the helmet is much crisper! Morph Targets # authors: @nicopap, @cart Bevy, since the 0.7 release, supports 3D animations. But it only supported skeletal animations. Leaving on the sidewalk a common animation type called morph targets (aka blendshapes, aka keyshapes, and a slew of other name). This is the grandparent of all 3D character animation! Crash Bandicoot's run cycle used morph targets. Character model by Samuel Rosario ((c) all rights reserved), used with permission. Modified by nicopap, using the Snow character texture by Demeter Dzadik for Blender Studios ( CC-BY). Nowadays, an animation artist will typically use a skeleton rig for wide moves and morph targets to clean up the detailed movements. When it comes to game assets, however, the complex skeleton rigs used by artists for faces and hands are too heavy. Usually, the poses are "baked" into morph poses, and facial expression transitions are handled in the engine through morph targets. Morph targets is a very simple animation method. Take a model, have a base vertex position, move the vertices around to create several poses: Default A wireframe rendering of a character's face with a neutral expression Frown Wireframe rendering of a frowning character Smirk Wireframe rendering of a smirking character Store those poses as a difference between the default base mesh and the variant pose, then, at runtime, mix each pose. Now that we have the difference with the base mesh, we can get the variant pose by simply adding to the base vertices positions. That's it, the morph target shader looks like this: fn morph_vertex(vertex: Vertex) { for (var i: u32 = 0u; i < pose_count(); i++) { let weight = weight_for_pose(i); vertex.position += weight * get_difference(vertex.index, position_offset, i); vertex.normal += weight * get_difference(vertex.index, normal_offset, i); } } In Bevy, we store the weights per pose in the MorphWeights component. fn set_weights_system(mut morph_weights: Query<&mut MorphWeights>) { for mut entity_weights in &mut morph_weights { let weights = entity_weights.weights_mut(); weights[0] = 0.5; weights[1] = 0.25; } } Now assuming that we have two morph targets, (1) the frown pose, (2) the smirk pose: [0.0, 0.0] default pose Neutral face expression [1.0, 0.0] frown only Frowning [0.0, 1.0] smirk only Smirking [0.5, 0.0] half frown Slightly frowning [1.0, 1.0] both at max Making faces [0.5, 0.25] bit of both Slightly frowning/smirking While conceptually simple, it requires communicating to the GPU a tremendous amount of data. Thousand of vertices, each 288 bits, several model variations, sometimes a hundred. We store the vertex data as pixels in a 3D texture. This allows morph targets to not only run on WebGPU, but also on the WebGL2 wgpu backend. This could be improved in a number of ways, but it is sufficient for an initial implementation. Parallax Mapping # author: @nicopap Bevy now supports parallax mapping and depth maps. Parallax mapping puts normal maps to shame when it comes to giving "illusion of depth" to a material. The top half of this video uses parallax mapping plus a normal map, whereas the bottom half only uses a normal map: earth view, elevation & night view by NASA (public domain) Notice how it is not merely the shading of pixels that changes, but their actual position on screen. The mountaintops hide mountain ridges behind themselves. High mountains move faster than coastal areas. Parallax mapping moves pixels according to the perspective and depth on the surface of the geometry. Adding true 3D depth to flat surfaces. All of that, without adding a single vertex to the geometry. The whole globe has exactly 648 vertices. Unlike a more primitive shader, such as displacement mapping, parallax mapping only requires an additional grayscale image, called the depth_map. Games often use parallax mapping for cobblestones or brick walls, so let's make a brick wall in Bevy! First, we spawn a mesh: commands.spawn(PbrBundle { mesh: meshes.add(shape::Box::new(30.0, 10.0, 1.0).into()), material: materials.add(StandardMaterial { base_color: Color::WHITE, ..default() }), ..default() }); A 3D desert scene with two flat white walls and a pebble path winding between them Of course, it's just a flat white box, we didn't add any texture. So let's add a normal map: normal_map_texture: Some(assets.load("normal_map.png")), The same scene with normal maps This is much better. The shading changes according to the light direction too! However, the specular highlights on the corner are overbearing, almost noisy. Let's see how a depth map can help: depth_map: Some(assets.load("depth_map.png")), The same scene with a depth texture We eliminated the noise! There is also that sweet 3D feel reminiscent of 90's games pre-rendered cinematic sequences. So what's going on, why does parallax mapping eliminate the ugly specular lights on the wall? This is because parallax mapping insets the ridges between bricks, so that they are occluded by the bricks themselves. Illustration of the previous paragraph Since normal maps do not "move" the shaded areas, merely shade them differently, we get those awkward specular highlights. With parallax mapping, they are gone. Drag this image to compare Normal Mapping Only Parallax & Normal Mapping Parallax mapping in Bevy is still very limited. The most painful aspect is that it is not a standard glTF feature, meaning that the depth texture needs to be programmatically added to materials if they came from a GLTF file. Additionally, parallax mapping is incompatible with the temporal antialiasing shader, doesn't work well on curved surfaces, and doesn't affect object's silhouettes. However, those are not fundamental limitations of parallax mapping, and may be fixed in the future. Skyboxes # authors: @JMS55, @superdump skybox Bevy now has built-in support for displaying an HDRI environment as your scene background. Simply attach the new Skybox component to your Camera. It pairs well with the existing EnvironmentMapLight, which will use the environment map to light the scene. We also plan to add support for built-in procedural skyboxes sometime in the future! WebGPU Support # authors: @mockersf, many others throughout Bevy's development webgpu Bevy now supports WebGPU rendering on the web (in addition to WebGL 2). WebGPU support is still rolling out, but if you have a supported web browser you can explore our new live WebGPU examples page. What is WebGPU? # WebGPU is an exciting new web standard for doing modern GPU graphics and compute. It takes inspiration from Vulkan, Direct3D 12, and Metal. In fact, it is generally implemented on top of these APIs under the hood. WebGPU gives us access to more GPU features than WebGL2 (such as compute shaders) and also has the potential to be much faster. It means that more of Bevy's native renderer features are now also available on the web. It also uses the new WGSL shader language. We're very happy with how WGSL has evolved over time and Bevy uses it internally for our shaders. We also added usability features like imports! But with Bevy you still have the option to use GLSL if you prefer. How it Works # Bevy is built on top of the wgpu library, which is a modern low-level GPU API that can target pretty much every popular API: Vulkan, Direct3D 12, Metal, OpenGL, WebGL2, and WebGPU. The best backend API is selected for a given platform. It is a "native" rendering API, but it generally follows the WebGPU terminology and API design. Unlike WebGPU, it can provide direct access to the native APIs, which means Bevy enjoys a "best of all worlds" situation. WebGPU Examples # Click one of the images below to check out our live WebGPU examples (if your browser supports it): webgpu examples Improved Shader Imports # authors: @robtfm Bevy's rendering engine has a lot of great options and features. For example, the PBR StandardMaterial pipeline supports desktop/webgpu and webgl, 6 optional mesh attributes, 4 optional textures, and a plethora of optional features like fog, skinning, and alpha blending modes, with more coming in every release. Many feature combos need specialized shader variants, and with over 3000 lines of shader code split over 50 files in total, the text-substitution-based shader processor was beginning to creak at the seams. This release we've switched to using naga_oil, which gives us a module-based shader framework. It compiles each file individually to naga's IR and then combines them into a final shader on demand. This doesn't have much visible impact yet, but it does give a few immediate benefits: * The engine's shader code is easier to navigate and less magical. Previously there was only a single global scope, so items could be referenced even if they were only imported indirectly. This sometimes made it hard to locate the actual code behind the reference. Now items must be explicitly imported, so you can always tell where a variable or function originated just by looking at the current file: imported items * Shaders now have codespan reporting, an error will point you to the shader file and line number, preventing a lot of hair pulling in complex shader codebases: codespan * naga_oil's preprocessor supports a few more conditional directives, you can use #else if and #else ifndef as well as # else ifdef which was previously supported * Functions, variables and structs are all properly scoped so a shader file doesn't need to use globally unique names to avoid conflicts * Shader defs can be added to modules directly. For example, any shader that imports bevy_pbr::mesh_view_types now has MAX_DIRECTIONAL_LIGHTS automatically defined, there's no longer a need to remember to add it for every new pipeline that uses the module. The future possibilities are more exciting. Using naga IR opens the door to a bunch of nice features that we hope to bring in future releases: * Automatic bind slot allocation will let plugins extend the core view bindgroup, which means self-contained plugins for features like lighting and shadow methods, common material properties, etc become viable. This will allow us to modularise the core pipelines to make growing the codebase - while keeping support for multiple targets - more sustainable * "Virtual" shader functions will allow user modifications to core functions (like lighting), and potentially lead to a template-style material system, where users can provide "hooks" that will be called at the right point in the pipeline * Language interop: mix and match glsl and wgsl, so bevy's pbr pipeline features could be accessed from your glsl material shader, or utils written for glsl could be used in wgsl code. We're hopeful that this can extend to spirv (and rust-gpu) as well * More cool stuff we haven't thought of yet. Being able to inspect and modify shaders at runtime is very powerful and makes a lot of things possible! UI Node Borders # authors: @ickshonpe UI nodes now draws borders, whose color can be configured with the new BorderColor component: borders commands.spawn(ButtonBundle { style: Style { border: UiRect::all(Val::Px(5.0)), ..default() }, border_color: BorderColor(Color::rgb(0.9, 0.9, 0.9)), ..default() }) Each side of the border is configurable: border sides Grid UI Layout # authors: @nicoburns In Bevy UI we wired up the new grid feature in the layout library we use (Taffy). This enables CSS-style grid layouts: grid This can be configured on the Style component: Style { /// Use grid layout for this node display: Display::Grid, /// Make the grid have a 1:1 aspect ratio /// This means the width will adjust to match the height aspect_ratio: Some(1.0), // Add 24px of padding around the grid padding: UiRect::all(Val::Px(24.0)), /// Set the grid to have 4 columns all with sizes minmax(0, 1fr) /// This creates 4 exactly evenly sized columns grid_template_columns: RepeatedGridTrack::flex(4, 1.0), /// Set the grid to have 4 rows all with sizes minmax(0, 1fr) /// This creates 4 exactly evenly sized rows grid_template_rows: RepeatedGridTrack::flex(4, 1.0), /// Set a 12px gap/gutter between rows and columns row_gap: Val::Px(12.0), column_gap: Val::Px(12.0), ..default() }, Schedule-First ECS APIs # authors: @cart In Bevy 0.10 we introduced ECS Schedule V3, which vastly improved the capabilities of Bevy ECS system scheduling: scheduler API ergonomics, system chaining, the ability to run exclusive systems and apply deferred system operations at any point in a schedule, a single unified schedule, configurable System Sets, run conditions, and a better State system. However it pretty quickly became clear that the new system still had some areas to improve: * Base Sets were hard to understand and error prone: What is a Base Set? When do I use them? Why do they exist? Why is my ordering implicitly invalid due to incompatible Base Set ordering? Why do some schedules have a default Base Set while others don't? Base Sets were confusing! * There were too many ways to schedule a System: We've accumulated too many scheduling APIs. As of Bevy 0.10, we had SIX different ways to add a system to the "startup" schedule. Thats too many ways! * Too much implicit configuration: There were both default Schedules and default Base Sets. In some cases systems had default schedules or default base sets, but in other cases they didn't! A system's schedule and configuration should be explicit and clear. * Adding Systems to Schedules wasn't ergonomic: Things like add_system(foo.in_schedule(CoreSchedule::Startup)) were not fun to type or read. We created special-case helpers, such as add_startup_system(foo), but this required more internal code, user-defined schedules didn't benefit from the special casing, and it completely hid the CoreSchedule::Startup symbol!. Unraveling the Complexity # If your eyes started to glaze over as you tried to wrap your head around this, or phrases like "implicitly added to the CoreSet::Update Base Set" filled you with dread ... don't worry. After a lot of careful thought we've unraveled the complexity and built something clear and simple. In Bevy 0.11 the "scheduling mental model" is much simpler thanks to Schedule-First ECS APIs: app .add_systems(Startup, (a, b)) .add_systems(Update, (c, d, e)) .add_systems(FixedUpdate, (f, g)) .add_systems(PostUpdate, h) .add_systems(OnEnter(AppState::Menu), enter_menu) .add_systems(OnExit(AppState::Menu), exit_menu) * There is exactly one way to schedule systems + Call add_systems, state the schedule name, and specify one or more systems * Base Sets have been entirely removed in favor of Schedules, which have friendly / short names + Ex: The CoreSet::Update Base Set has become Update * There is no implicit or implied configuration + Default Schedules and default Base Sets don't exist * The syntax is easy on the eyes and ergonomic + Schedules are first so they "line up" when formatted To compare, expand this to see what it used to be! app // Startup system variant 1. // Has an implied default StartupSet::Startup base set // Has an implied CoreSchedule::Startup schedule .add_startup_systems((a, b)) // Startup system variant 2. // Has an implied default StartupSet::Startup base set // Has an implied CoreSchedule::Startup schedule .add_systems((a, b).on_startup()) // Startup system variant 3. // Has an implied default StartupSet::Startup base set .add_systems((a, b).in_schedule(CoreSchedule::Startup)) // Update system variant 1. // `CoreSet::Update` base set and `CoreSchedule::Main` are implied .add_system(c) // Update system variant 2 (note the add_system vs add_systems difference) // `CoreSet::Update` base set and `CoreSchedule::Main` are implied .add_systems((d, e)) // No implied default base set because CoreSchedule::FixedUpdate doesn't have one .add_systems((f, g).in_schedule(CoreSchedule::FixedUpdate)) // `CoreSchedule::Main` is implied, in_base_set overrides the default CoreSet::Update set .add_system(h.in_base_set(CoreSet::PostUpdate)) // This has no implied default base set .add_systems(enter_menu.in_schedule(OnEnter(AppState::Menu))) // This has no implied default base set .add_systems(exit_menu.in_schedule(OnExit(AppState::Menu))) Note that normal "system sets" still exist! You can still use sets to organize and order your systems: app.add_systems(Update, ( (walk, jump).in_set(Movement), collide.after(Movement), )) The configure_set API has also been adjusted for parity: // Bevy 0.10 app.configure_set(Foo.after(Bar).in_schedule(PostUpdate)) // Bevy 0.11 app.configure_set(PostUpdate, Foo.after(Bar)) Nested System Tuples and Chaining # authors: @cart It is now possible to infinitely nest tuples of systems in a .add_systems call! app.add_systems(Update, ( (a, (b, c, d, e), f), (g, h), i )) At first glance, this might not seem very useful. But in combination with per-tuple configuration, it allows you to easily and cleanly express schedules: app.add_systems(Update, ( (attack, defend).in_set(Combat).before(check_health) check_health, (handle_death, respawn).after(check_health) )) .chain() has also been adapted to support arbitrary nesting! The ordering in the example above could be rephrased like this: app.add_systems(Update, ( (attack, defend).in_set(Combat) check_health, (handle_death, respawn) ).chain() ) This will run attack and defend first (in parallel), then check_health, then handle_death and respawn (in parallel). This allows for powerful and expressive "graph-like" ordering expressions: app.add_systems(Update, ( (a, (b, c, d).chain()), (e, f), ).chain() ) This will run a in parallel with b->c->d, then after those have finished running it will run e and f in parallel. Gizmos # authors: @devil-ira, @jannik4, @lassade, @The5-1, @Toqozz, @nicopap It is often helpful to be able to draw simple shapes and lines in 2D and 3D for things like editor controls, and debug views. Game development is a very "spatial" thing and being able to quickly draw shapes is the visual equivalent of "print line debugging". It helps answer questions like "is this ray casting in the right direction?" and "is this collider big enough?" In Bevy 0.11 we've added an "immediate mode" Gizmos drawing API that makes these things easy and efficient. In 2D and 3D you can draw lines, rects, circles, arcs, spheres, cubes, line strips, and more! 2D Gizmos 2d gizmos 3D Gizmos 3d gizmos From any system you can spawn shapes into existence (for both 2D and 3D): fn system(mut gizmos: Gizmos) { // 2D gizmos.line_2d(Vec2::new(0., 0.), Vec2::new(0., 10.), Color::RED); gizmos.circle_2d(Vec2::new(0., 0.), 40., Color::BLUE); // 3D gizmos.circle(Vec3::ZERO, Vec3::Y, 3., Color::BLACK); gizmos.ray(Vec3::new(0., 0., 0.), Vec3::new(5., 5., 5.), Color::BLUE); gizmos.sphere(Vec3::ZERO, Quat::IDENTITY, 3.2, Color::BLACK) } Because the API is "immediate mode", gizmos will only be drawn on frames where they are "queued up", which means you don't need to worry about cleaning up gizmo state! Gizmos are drawn in batches, which means they are very cheap. You can have hundreds of thousands of them! ECS Audio APIs # authors: @inodentry Bevy's audio playback APIs have been reworked to integrate more cleanly with Bevy's ECS. In previous versions of Bevy you would play back audio like this: #[derive(Resource)] struct MyMusic { sink: Handle, } fn play_music( mut commands: Commands, asset_server: Res, audio: Res