News

Draft Page

The page is still under development (Tracking Issue: 1188). This page and all pages under it will be hidden from search engines and menus!

Bevy 0.14

Posted on May 17, 2024 by Bevy Contributors

Thanks to TODO contributors, TODO pull requests, community reviewers, and our generous sponsors, we're happy to announce the Bevy 0.14 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.14, check out our 0.13 to 0.14 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:

  • Animation blending: our new low-level animation graph adds support for animation blending, and sets the stage for first- and third-party graphical, asset-driven animation tools.
  • Shiny 3D rendering features: meshlets, hierarchical levels of detail, depth of field, god rays, motion blur and more!
  • Better colors: type-safe colors make it clear which color space you're operating in, and offer an awesome array of useful methods.
  • Observers and hooks: automatically respond to component addition and removal as we lay the foundations for fast, reliable relations between entities.
  • Computed states and substates: modeling complex app state is a breeze with these type-safe extensions to our States abstraction.
  • Rounded corners: rounding off one of bevy_ui's roughest edges, you can now procedurally set the corner radius on your UI elements.

Add support for KHR_texture_transform #

The GLTF extension KHR_texture_transform is used to transform a texture before applying it. By reading this extension, Bevy can now support a variety of new workflows. The one we want to highlight here is the ability to easily repeat textures a set number of times. This is useful for creating textures that are meant to be tiled across a surface. We will show how to do this using Blender, but the same principles apply to any 3D modeling software.

Let's look at an example scene without textures that we've prepared in Blender, exported as a GLTF file and loaded into Bevy.

Scene without textures

Now, let's add some textures to the scene. We will first use the most basic shader node setup available in Blender:

Basic shader node setup

We can use a similar graph for all objects in the scene by using a different texture in each material. The result is the following scene in Bevy:

Scene with stretched textures

Oh no! Everything is stretched! This is because we have set up our UVs in a way that maps the texture exactly once onto the mesh. There are a few ways of how to deal with this, but the most convenient is to add shader nodes that scale the texture so that it repeats:

Repeating shader node setup

The data of the Mapping node is the one exported to KHR_texture_transform. Look at the part in red. These scaling factors determine how often the texture should be repeated in the material. In our case, we eyeballed that the brick texture looks nice when repeated 3 times horizontally and 5.4 times vertically. Tweaking this value for all textures brings us to the final scene:

Scene with repeated textures

Looks nice, doesn't it? You can use this approach for other textures as well. In fact, we cheated a bit: the screenshots above already use the scaling factor for their normal, roughness, and metallic maps as well, which works analogously. Simply drag the Mapping node's output into the vector input of however many textures you want.

Upstreaming bevy_color. #

Colors are a huge part of building a good game: UI, effects, shaders and more all need fully-featured, correct and convenient color tools.
Bevy now supports a broad selection of color spaces, each with their own type (e.g. LinearRgba, Hsla, Oklaba), and offers a wide range of fully documented operations on and conversions between them.

The new API is more error-resistant, more idiomatic and allows us to save work by storing the LinearRgba type in our rendering internals. This solid foundation has allowed us to implement a wide range of useful operations, clustered into traits like Hue or Alpha, allowing you to operate over any color space with the required property. Critically, color mixing / blending is now supported: perfect for procedurally generating color palettes and working with animations.

use bevy_color::prelude::*;

// Each color space now corresponds to a specific type
let red = Srgba::rgb(1., 0., 0.);

// All non-standard color space conversions are done through the shortest path between  
// the source and target color spaces to avoid a quadratic explosion of generated code.  
// This conversion...  
let red = Oklcha::from(red);  
// ...is implemented using  
let red = Oklcha::from(Oklaba::from(LinearRgba::from(red)));  

// We've added the `tailwind` palette colors: perfect for quick-but-pretty prototyping!
// And the existing CSS palette is now actually consistent with the industry standard :p
let blue = Oklcha = tailwind::BLUE_500;

// The color space that you're mixing your colors in has a huge impact!
// Consider using the scientifically-motivated `Oklcha` or `Oklaba` for a perceptually uniform effect.
let purple =  red.mix(blue, 0.5);

Most of the user-facing APIs still accept a colorspace-agnostic Color (which now wraps our color-space types), while rendering internals use the physically-based LinearRgba type. For an overview of the different color spaces, and what they're each good for, please check out our color space usage documentation.

bevy_color offers a solid, type-safe foundation, but it's just getting started. If you'd like another color space or there are more things you'd like to do to your colors, please open an issue or PR and we'd be happy to help!

P.S. bevy_color is intended to operate effectively as a stand-alone crate: feel free to take a dependency on it for your non-Bevy projects as well.

Commands and exclusive world access: working together in harmony #

Working with Commands when you have exclusive world access has always been a pain. Create a CommandQueue, generate a Commands out of that, send your commands and then apply it? Not exactly the most intuitive solution.

Now, you can access the World's own command queue:

let mut world = World::new();
let mut commands = world.commands();
commands.spawn(TestComponent);
world.flush_commands();

While this isn't the most performant approach (just apply the mutations directly to the world and skip the indirection), this API can be great for quickly prototyping with or easily testing your custom commands (and is used internally to power lifecycle hooks).

As a bonus, one-shot systems now apply their commands (and other deferred system params) immediately when run! We already have exclusive world access: why introduce delays and subtle bugs?

Run the multi-threaded executor at the end of each system task. #

TODO

New gizmos types #

  • Authors: @mweatherley, @Kanabenki, @MrGVSV, @solis-lumine-vorago, @alice-i-cecile
  • Pull Request

Gizmos in Bevy allow to easily draw arbitrary shapes to help debugging or authoring content, but also to visualize specific properties of your scene, such has the AABB of your meshes.

In 0.14, several new gizmos have been added to bevy::gizmos:

Grid Gizmos #

New grid gizmo types were added with Gizmos::grid_2d and Gizmos::grid to draw a plane grid in either 2D or 3D, alongside Gizmos::grid_3d to draw a 3D grid.

Each grid type can be skewed, scaled and subdivided along its axis, and you can separately control which outer edges to draw.

Grid gizmos screenshot

Coordinate Axes Gizmo #

The new Gizmos::axes add a simple way to show the position, orientation and scale of any object from its Transform plus a base size. The size of each axis arrow is proportional to the corresponding axis scale in the provided Transform.

Axes gizmo screenshot

Light Gizmos #

The new ShowLightGizmo component implements a retained gizmo to visualize lights for SpotLight, PointLight and DirectionalLight. Most light properties are visually represented by the gizmos, and the gizmo color can be set to match the light instance or use a variety of other behaviors.

Similar to other retained gizmos, ShowLightGizmo can be configured per-instance or globally with LightGizmoConfigGroup.

Light gizmos screenshot

Decouple BackgroundColor from UiImage #

UI images can now be given solid background colors:

UI image with background color

The BackgroundColor component now works for UI images instead of applying a color tint on the image itself. You can still apply a color tint by setting UiImage::color. For example:

commands.spawn((
    ImageBundle {
        image: UiImage::new(texture_handle).with_color(image_color_tint),
        ..default()
    },
    BackgroundColor(ANTIQUE_WHITE.into()),
    Outline::new(Val::Px(8.0), Val::ZERO, CRIMSON.into()),
));

Add WinitEvent aggregate event for synchronized window event reading #

When handling inputs, the exact ordering of the events received is often very significant, even when the events are not the same type! Consider a simple drag-and-drop operation. When, exactly, did the user release the mouse button relative to the many tiny movements that they performed? Getting these details right goes a long way to a responsive, precise user experience.

We now expose the blanket WinitEvent event stream, in addition to the existing separated event streams, which can be read and matched on directly whenever these problems arise.

bevy_reflect: Recursive registration #

Bevy uses reflection in order to dynamically process data for things like serialization and deserialization. A Bevy app has a TypeRegistry to keep track of which types exist. Users can register their custom types when initializing the app or plugin.

#[derive(Reflect)]
struct Data<T> {
  value: T,
}

#[derive(Reflect)]
struct Blob {
  contents: Vec<u8>,
}

app
  .register_type::<Data<Blob>>()
  .register_type::<Blob>()
  .register_type::<Vec<u8>>()

In the code above, Data<Blob> depends on Blob which depends on Vec<u8>, which means that all three types need to be manually registered— even if we only care about Data<Blob>.

This is both tedious and error-prone, especially when these type dependencies are only used in the context of other types (i.e. they aren't used as standalone types).

In 0.14, any type that derives Reflect will automatically register all of its type dependencies. So when we register Data<Blob>, Blob will be registered as well (which will register Vec<u8>), thus simplifying our registration down to a single line:

app.register_type::<Data<Blob>>()

Note that removing the registration for Data<Blob> now also means that Blob and Vec<u8> may not be registered either, unless they were registered some other way. If those types are needed as standalone types, they should be registered separately.

Implement the AnimationGraph, allowing for multiple animations to be blended together. #

TODO

Query Joins #

TODO

A new Rotation2d type #

Ever wanted to work with rotations in 2D and get frustrated with having to choose between quaternions and a raw f32? Us too!

We've added a convenient Rot2 type for you, with plenty of helper methods. Feel free to replace that helper type you wrote, and submit little PRs for any useful functionality we're missing.

Rot2 is a great complement to the Dir2 type (formerly Direction2d). The former represents an angle, while the latter is a unit vector. These types are similar but not interchangeable, and the choice of representation depends heavily on the task at hand. You can rotate a direction using direction = rotation * Dir2::X. To recover the rotation, use Dir2::X::rotation_to(direction) or in this case the helper Dir2::rotation_from_x(direction).

While these types aren't used widely within the engine yet, we are aware of your pain and are evaluating proposals for how we can make working with transforms in 2D more straightforward and pleasant.

Alignment API for Transforms #

TODO

Random sampling of shapes and directions #

In the context of game development, it's often helpful to have access to random values, whether that's in the interest of driving behavior for NPCs, creating effects, or just trying to create variety. To help support this, a few random sampling features have been added to bevy_math, gated behind the rand feature. These are primarily geometric in nature, and they come in a couple of flavors.

First, one can sample random points from the boundaries and interiors of a variety of mathematical primitives: Image of several primitives side-by-side with points randomly sampled from their interiors

In code, this can be performed in a couple different ways, using either the sample_interior/sample_boundary or interior_dist/boundary_dist APIs:

use bevy::math::prelude::*;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;

let sphere = Sphere::new(1.5);

// Instantiate an Rng:
let rng = &mut ChaCha8Rng::seed_from_u64(7355608);

// Using these, sample a random point from the interior of this sphere:
let interior_pt: Vec3 = sphere.sample_interior(rng);
// or from the boundary:
let boundary_pt: Vec3 = sphere.sample_boundary(rng);

// Or, if we want a lot of points, we can use a Distribution instead...
// to sample 100000 random points from the interior:
let interior_pts: Vec<Vec3> = sphere.interior_dist().sample_iter(rng).take(100000).collect();
// or 100000 random points from the boundary:
let boundary_pts: Vec<Vec3> = sphere.boundary_dist().sample_iter(rng).take(100000).collect();

Note that these methods explicitly require an Rng object, giving you control over the randomization strategy and seed.

The currently supported shapes are as follows:

2D: Circle, Rectangle, Triangle2d, Annulus, Capsule2d.

3D: Sphere, Cuboid, Triangle3d, Tetrahedron, Cylinder, Capsule3d, and extrusions of sampleable 2D shapes (Extrusion).


Similarly, the direction types (Dir2, Dir3, Dir3A) and quaternions (Quat) can now be constructed randomly using from_rng:

use bevy::math::prelude::*;
use rand::{random, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;

// Instantiate an Rng:
let rng = &mut ChaCha8Rng::seed_from_u64(7355608);

// Get a random direction:
let direction = Dir3::from_rng(rng);

// Similar, but requires left-hand type annotations or inference:
let another_direction: Dir3 = rng.gen();

// Using `random` to grab a value using implicit thread-local rng:
let yet_another_direction: Dir3 = random();

Add pipeline statistics #

TODO

Add a gizmo-based overlay to show UI node outlines (Adopted) #

  • Authors: @pablo-lua, @nicopap, @alice-i-cecile
  • Pull Request

When working with UI on the web, being able to quickly debug the size of all your boxes is wildly useful. We now have a native layout tool which adds gizmos outlines to all Nodes

An example of what the tool looks like after enabled

Ui example with the overlay tool enabled

use bevy::prelude::*;

// You first have to add the DebugUiPlugin to your app
let mut app = App::new()
    .add_plugins(bevy::dev_tools::ui_debug_overlay::DebugUiPlugin);

// In order to enable the tool at runtime, you can add a system to toggle it
fn toggle_overlay(
    input: Res<ButtonInput<KeyCode>>,
    mut options: ResMut<bevy::dev_tools::ui_debug_overlay::UiDebugOptions>,
) {
    info_once!("The debug outlines are enabled, press Space to turn them on/off");
    if input.just_pressed(KeyCode::Space) {
        // The toggle method will enable the debug_overlay if disabled and disable if enabled
        options.toggle();
    }

}

// And add the system to the app
app.add_systems(Update, toggle_overlay);

Add border radius to UI nodes (adopted) #

  • Authors: @chompaa, @pablo-lua, @alice-i-cecile, @bushrat011899
  • Pull Request

Border radius for UI nodes has been a long-requested feature for Bevy. Now it's supported!

To apply border radius to a UI node, there is a new component BorderRadius. The NodeBundle and ButtonBundle bundles have a new field in place for this called border_radius. For example:

commands.spawn(NodeBundle {
    style: Style {
        width: Val::Px(50.0),
        height: Val::Px(50.0),
        // We need a border to round a border, after all!
        border: UiRect::all(Val::Px(5.0)),
        ..default()
    },
    border_color: BorderColor(Color::BLACK),
    // Apply the radius to all corners. 
    // Optionally, you could use `BorderRadius::all`.
    border_radius: BorderRadius {
        top_left: Val::Px(50.0),
        top_right: Val::Px(50.0),
        bottom_right: Val::Px(50.0),
        bottom_left: Val::Px(50.0),
    },
    ..default()
});

There's a new example showcasing this new API, a screenshot of which can be seen below:

rounded_borders example

Add Triangle3d primitive to bevy_math::primitives #

TODO

Meshlet rendering (initial feature) #

  • Authors: @JMS55, @atlv24, @mockersf, @ricky26
  • Pull Request

TODO

Get Bevy building for WebAssembly with multithreading #

  • Authors: @kettle11, @mockersf, @alice-i-cecile, @daxpedda
  • Pull Request

TODO

Gizmo line styles and joints #

Bevy gizmos are composed of lines and linestrips and are a powerful tool for debugging games.

use bevy::{color::palettes::css::*, prelude::*};

fn draw_gizmos(mut gizmos: Gizmos) {
    gizmos.line_2d(Vec2::ZERO, Vec2::splat(-80.), RED);
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera2dBundle::default());
}

App::new()
	.add_plugins(DefaultPlugins)
	.add_systems(Startup, setup)
	.add_systems(Update, draw_gizmos);

Previously however, the only way to customize gizmos was to change their colour, which may be limiting for some use cases. Additionally, the meeting points of two lines in a linestrip, their joints, had little gaps.

As of bevy 0.14, you can change the style of the lines and their joints for each gizmo config group:

use bevy::{color::palettes::css::*, prelude::*};

fn draw_gizmos(mut gizmos: Gizmos) {
    gizmos.line_2d(Vec2::ZERO, Vec2::splat(-80.), RED);
}

fn setup(mut commands: Commands, mut config_store: ResMut<GizmoConfigStore>) {
    commands.spawn(Camera2dBundle::default());

	// Get the config for you gizmo config group
    let (config, _) = config_store.config_mut::<DefaultGizmoConfigGroup>();
	// Set the line style and joints for this config group
	config.line_style = GizmoLineStyle::Dotted;
	config.line_joints = GizmoLineJoint::Bevel;
}

App::new()
	.add_plugins(DefaultPlugins)
	.add_systems(Startup, setup)
	.add_systems(Update, draw_gizmos);

The new line styles can be used in both 2D and 3D and respect the line_perspective option of their config groups.

Available line styles are

  • GizmoLineStyle::Dotted, which draws a dotted line with each dot being a square
  • GizmoLineStyle::Solid, which draws a solid line - this is the default behaviour and the only one available before bevy 0.14,

new gizmos line styles

Similarly, the new line joints offer a variety of options:

  • GizmoLineJoint::Miter, which extends both lines until they meet at a common miter point,
  • GizmoLineJoint::Round(resolution), which will approximate an arc filling the gap between the two lines. The resolution determines the amount of triangles used to approximate the geometry of the arc.
  • GizmoLineJoint::Bevel, which connects the ends of the two joining lines with a straight segment, and
  • GizmoLineJoint::None, which uses no joints and leaves small gaps - this is the default behaviour and the only one available before bevy 0.14.

new gizmos line joints

You can check out the 2D gizmos example, which demonstrates the use of line styles and joints!

Move Point out of cubic splines module and expand it #

  • Authors: @mweatherley, @bushrat011899, @JohnTheCoolingFan, @NthTensor, @Jondolf, @IQuick143, @alice-i-cecile
  • Pull Request

Linear algebra is used everywhere in games, and we want to make sure it's easy to get right. That's why we've added a new VectorSpace trait, as part of our work to make bevy_math more general, expressive, and mathematically sound. Anything that implements VectorSpace behaves like a vector. More formally, the trait requires that implementations satisfy the vector space axioms for vector addition and scalar multiplication. We've also added a NormedVectorSpace trait, which includes an api for distance and magnitude.

These traits underpin the new curve and shape sampling apis. VectorSpace is implemented for f32, the glam vector types, and several of the new color-space types. It completely replaces bevy_math::Point.

The splines module in bevy has been lacking some features for a long time. Splines are extremely useful in game development, so improving them would improve everything that uses them.

The biggest addition is NURBS support! It is a variant of a B-Spline with much more parameters that can be tweaked to create specific curve shapes.

We also added a LinearSpline, which can be used to put straight line segments in a CubicCurve, which now acts as a sequence of cure segments, so you can mix various spline types together to form a single path.

And as a small improvement, the VectorSpace trait has been implemented and 4-dimensional Vectors. This trait is implemented for types that can be used in cubic curves, so now you have more types that can be used. For example, you can use the 4th element of a Vec4 to store tilt along the path on the curve.

Support wireframes for 2D meshes #

Wireframe materials are used to render the individual edges and faces of a mesh. They are often used as a debugging tool to visualize geometry, but can also be used for various stylized effects. Bevy supports displaying 3D meshes as wireframes, but lacked the ability to do this for 2D meshes until now.

To render your 2D mesh as a wireframe, add Wireframe2dPlugin to your app and a Wireframe2d component to your sprite. The color of the wireframe can be configured per-object by adding the Wireframe2dColor component, or globally by inserting a Wireframe2dConfig resource.

For an example of how to use the feature, have a look at the new wireframe_2d example:

A screenshot demonstrating the new 2D wireframe material

Improve performance by binning together opaque items instead of sorting them. #

TODO

Add AsyncSeek trait to Reader to be able to seek inside asset loaders #

TODO

Add Tetrahedron primitive to bevy_math::primitives #

TODO

Add methods iter_resources and iter_resources_mut #

  • Authors: @matiqo15, @james7132, @alice-i-cecile, @pablo-lua
  • Pull Request

TODO

Error info has been added to LoadState::Failed #

Rust prides itself on its error handling, and Bevy has been steadily catching up. Previously, when checking if an asset was loaded using AssetServer::get_load_state, all you'd get back was a data-less LoadState::Failed if something went wrong. Not very useful for debugging!

Now, a full AssetLoadError is included, with 14 different variants telling you exactly what went wrong. Great for troubleshooting, and it opens the door to proper error handling in more complex apps.

Implement percentage-closer filtering (PCF) for point lights. #

TODO

Optimize Event Updates #

TODO

AppExit Errors #

When running an app, there might be many reasons to trigger an exit. Maybe the user has pressed the quit button, or the render thread has encountered an error and died. You might want to distinguish between these two situations and return an appropriate exit code from your application.

In Bevy 0.14, you can. The AppExit event is now an enum with two variants: Success and Error. The error variant also holds a non-zero code, which you're allowed to use however you wish. Since AppExit events now contain useful information, app runners and App::run now return the event that resulted in the application exiting.

For plugin developers, App has gained a new method, App::should_exit, which will check if any AppExit events were sent in the last two updates. To make sure AppExit::Success events won't drown out useful error information, this method will return any AppExit::Error events, even if they were sent after an AppExit::Success.

Finally, AppExit also implements the Termination trait, so it can be returned from main.

use bevy::prelude::*;

fn exit_with_a_error_code(mut events: EventWriter<AppExit>) {
    events.send(AppExit::from_code(42));
}

fn main() -> AppExit {
    App::new()
        .add_plugins(MinimalPlugins)
        .add_systems(Update, exit_with_a_error_code)
        .run() // There's no semicolon here, `run()` returns `AppExit`.
}

App returning a 42 exit code

Contextually clearing gizmos #

Gizmos are drawn via an immediate mode API. This means that every update you draw all gizmos you want to display. Only those will be shown. Previously, update referred to "once every time the Main schedule runs". This matches the frame rate, so it usually works great! But when you try to draw gizmos during FixedMain, they will flicker or be rendered multiple times. In Bevy 0.14, this now just works!

This can be extended for use with custom schedules. Instead of a single storage, there now are multiple storages differentiated by a context type parameter. You can also set a type parameter on the Gizmos system param to choose what storage to write to. You choose when storages you add get drawn or cleared: Any gizmos in the default storage (the () context) during the Last schedule will be shown.

Improve par_iter and Parallel #

TODO

Meshlet continuous LOD #

  • Authors: @JMS55, @pcwalton, @ricky26, @mockersf, @atlv24
  • Pull Request

TODO

Make dynamic_linking a no-op on WASM targets #

WASM does not support dynamic libraries that can be linked to during runtime. Before, Bevy would fail to compile if you enabled the dynamic_linking feature.

$ cargo build --target wasm32-unknown-unknown --features bevy/dynamic_linking
error: cannot produce dylib for `bevy_dylib v0.13.2` as the target `wasm32-unknown-unknown` does not support these crate types

Now, Bevy will fallback to static linking for all WASM targets. If you enable dynamic_linking for development, you no longer need to disable it for WASM.

Per-Object Motion Blur #

TODO

Throttle render assets #

TODO

Implement GPU frustum culling. #

TODO

Implement filmic color grading. #

Computed States & Sub States #

  • Authors: @lee-orr, @marcelchampagne, @MiniaczQ, @alice-i-cecile
  • Pull Request

Bevy's States are a simple but powerful abstraction for managing the control flow of your app.

use bevy::prelude::*;

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
enum GameState {
 #[default]
  Menu,
  InGame,
}

fn handle_escape_pressed(mut next_state: ResMut<NextState<GameState>>) {
    // Get keyboard input...

    if escape_pressed {
        next_state.set(GameState::Menu);
    }
}

fn open_menu() {
    // Spawn the settings menu...
}

App::new()
    .init_state::<GameState>()
    .add_systems(Update, handle_escape_pressed.run_if(in_state(GameState::InGame)));
    .add_systems(OnEnter(GameState::Menu), open_settings_menu);

But as users' games (and non-game applications!) grew in complexity, their limitations became more apparent. What happens if we want to capture the notion of "in a menu", but then have different states corresponding to which submenu should be open? What if we want to ask questions like "is the game paused", but that question only makes sense while we're within a game?

Finding a good abstraction for this required several attempts and a great deal of both experimentation and discussion.

While your existing states code will work exactly as before, there are now two additional tools you can reach for if you're looking for more expressiveness: computed states and sub states.

/// Let's begin with our simple state above.
#[derive(States, Clone, PartialEq, Eq, Hash, Debug, Default)]
enum GameState {
    #[default]
    Menu,
    // The addition of `pause` field means that simply checking for `GameState::InGame` doesn't work:
    // the states are different depending on its value and we may want to distinguish between game systems
    // that run when the game is paused or not!
    InGame {
        paused: bool
    },
}

// While we can simply do `OnEnter(GameState::InGame{paused: true})`,
// we need to be able to reason about "while we're in the game, paused or not".
// To this end, we define the `InGame` computed state.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
struct InGame;

impl ComputedStates for InGame {
    // Computed states can be calculated from one or many source states.
    type SourceStates = GameState;

    // Now, we define the rule that determines the value of our computed state.
    fn compute(sources: GameState) -> Option<InGame> {
        match sources {
            // We can use pattern matching to express the
            //"I don't care whether or not the game is paused" logic!
            GameState::InGame {..} => Some(InGame),
            _ => None,
        }
    }
}

// In contrast, substates should be used when you want to keep manual
// control over the value through `NextState`, but still bind their
// existence to some parent state.
#[derive(SubStates, Clone, PartialEq, Eq, Hash, Debug, Default)]
// This macro means that `GamePhase` will only exist when we're in the `InGame` computed state.
// The intermediate computed state is helpful for clarity here, but isn't required:
// you can manually `impl SubStates` for more control, multiple parent states and non-default initial value!
#[source(InGame = InGame)]
enum GamePhase {
    #[default]
    Setup,
    Battle,
    Conclusion
}

// Initializing our states is easy: just call the appropriate method on `App`
// and all of the required machinery will be set up for you.
App::new()
   .init_state::<GameState>()
   .add_computed_state::<InGame>()
   .add_substate::<GamePhase>()

Just like any other state, computed states and substates work with all of the tools you're used to: the State and NextState resources, OnEnter, OnExit and OnTransition schedules and the in_state run condition. Make sure to visit both examples for more information!

The only exception is that, for correctness, computed states cannot be mutated through NextState. Instead, they are strictly derived from their parent states; added, removed and updated automatically during state transitions based on the provided compute method.

All of Bevy's state tools are now found in a dedicated bevy_state crate, which can be controlled via a feature flag. Yearning for the days of state stacks? Wish that there was a method for re-entering states? All of the state machinery relies only on public ECS tools: resources, schedules and run conditions. We know that state machines are very much a matter of taste; so if our design isn't to your taste consider taking advantage of Bevy's modularity and writing your own abstraction or using one supplied by the community!

State Identity Transitions #

Users have sometimes asked for us to trigger exit and entry steps when moving from a state to itself. While this has its uses (refreshing is the core idea), it can be surprising and unwanted in other cases. We've found a compromise that lets users hook into this type of transition if it's something they need.

StateEventTransition events will now include transitions from a state to itself, which will also propagate to all dependent ComputedStates and SubStates.

Because it is a niche feature, OnExit and OnEnter schedules will ignore the new identity transitions by default, but you can visit the new custom_transitions example to see how you can bypass or change that behavior!

State Scoped Entities #

  • Authors: @MiniaczQ, @alice-i-cecile, @mockersf
  • Pull Request

State scoped entities is a pattern that naturally emerged in community projects and has finally been integrated into Bevy codebase.

use bevy::prelude::*;

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
enum GameState {
 #[default]
  Menu,
  InGame,
}

fn spawn_player(mut commands: Commands) {
    commands.spawn((
        // We mark out entity with the `StateScoped` component.
        // When the provided state is exited, the entity will be deleted recursively with all children.
        StateScoped(GameState::InGame)
        SpriteBundle { ... }
    ))
}

App::new()
    .init_state::<GameState>()
    // We need to install the appropriate machinery for the cleanup code to run, once for each state type.
    .enable_state_scoped_entities::<GameState>()
    .add_systems(OnEnter(GameState::InGame), spawn_player);

By binding entity lifetime to a state during setup, we can dramatically reduce the amount of cleanup code we have to write!

Implement visibility ranges, also known as hierarchical levels of detail (HLODs). #

Add a process for working groups #

Bevy has a ton of incredibly talented contributors, so keeping track of what's going on and making informed decisions can be a real challenge. We're experimenting with working groups: ad hoc groups tackling harder issues by creating a design document, getting sign-off from the experts and then implement it. If you'd like to help make complex, high-impact changes to Bevy: join or form a working group!

Add BufferVec, an higher-performance alternative to StorageBuffer, and make GpuArrayBuffer use it. #

  • Authors: @kristoff3r, @IceSentry, @alice-i-cecile, @pcwalton
  • Pull Request

TODO

Implement Auto Exposure plugin #

Add Annulus-gizmos #

TODO

Extrusion #

Bevy 0.14 introduces an entirely new group of primitives: Extrusions!

An extrusion is a 2D primitive (the base shape) that is extruded into a third dimension by some depth. The resulting shape is a prism (or in the special case of the circle, a cylinder).

// Create an ellipse with width 2 and height 1.
let my_ellipse = Ellipse::from_size(2.0, 1.0);

// Create an extrusion of this ellipse with a depth of 1.
let my_extrusion = Extrusion::new(my_ellipse, 1.);

All extrusions are extruded along the Z-axis. This guarantees that an extrusion of depth 0 and the corresponding base shape are identical, just as one would expect.

Measuring and Sampling #

Since all extrusions with base shapes that implement Measured2d implement Measured3d, you can easily get the surface area or volume of an extrusion. If you have an extrusion of a custom 2D primitive, you can simply implement Measured2d for your primitive and Measured3d will be implemented automatically for the extrusion.

Likewise, you can sample the boundary and interior of any extrusion if the base shape of the extrusion implements ShapeSample<Output = Vec2> and Measured2d.

// Create a 2D capsule with radius 1 and length 2, extruded to a depth of 3
let extrusion = Extrusion::new(Capsule2d::new(1.0, 2.0), 3.0);

// Get the volume of the extrusion
let volume = extrusion.volume();

// Get the surface area of the extrusion
let surface_area = extrusion.area();


// Create a random number generator
let mut rng = StdRng::seed_from_u64(4);

// Sample a random point inside the extrusion
let interior_sample = extrusion.sample_interior(&mut rng);

// Sample a random point on the surface of the extrusion
let boundary_sample = extrusion.sample_boundary(&mut rng);

Bounding #

You can also get bounding spheres and Axis Aligned Bounding Boxes (AABBs) for extrusions. If you have a custom 2D primitive that implements Bounded2d, you can simply implement BoundedExtrusion for your primitive. The default implementation will give optimal results but may be slower than a solution fitted to your primitive.

struct Heart {
	// ... some properties
}
impl Primitive2d for Heart {}

impl Bounded2d for Heart {
	// ... your implementation for the 2D bounding
}

// Implement bounding for extrusions of hearts 
impl BoundedExtrusion for Heart {
    // You could override the default implementation in here, if you want to
}

Meshing #

Extrusions do not exist in the world of maths only though. They can also be meshed and displayed on the screen!

selected rendered extrusions

And again, adding meshing support for your own primitives is made easy by bevy! You simply need to implement meshing for your 2D primitive and then implement Extrudable for your 2D primitive's MeshBuilder.

When implementing Extrudable, you have to provide information about whether segments of the perimeter of the base shape are to be shaded smooth or flat, and what vertices belong to each of these perimeter segments.

impl Meshable for Heart {
    type Output = HeartMeshBuilder;

    fn mesh(&self) -> Self::Output {
        Self::Output { heart: *self }
    }
}
struct HeartMeshBuilder {
    /// The heart primitive 
	heart: Heart,
	/// The number of vertices to use for each wing of the heart
	resolution: usize,
}
impl MeshBuilder for HeartMeshBuilder {
    fn build(&self) -> Mesh {
        // ... your implementation for meshing the 2D primitive
    }
}

impl Extrudable for HeartMeshBuilder {
    fn perimeter(&self) -> Vec<bevy::render::mesh::PerimeterSegment> {
        let resolution = self.resolution as u32;
        vec![
			// The left wing of the heart
            PerimeterSegment::Smooth {
				// the normals of the first and last vertices of smooth segments have to be specified manually
                first_normal: Vec2::X,
                last_normal: Vec2::new(-1.0, -1.0).normalize(),
				// These indices are used to index into the `ATTRIBUTE_POSITION` vec of your 2D mesh.
                indices: (0..resolution).collect(),
            },
			// The bottom tip of the heart
            PerimeterSegment::Flat {
                indices: vec![resolution - 1, resolution, resolution + 1],
            },
			// The right wing of the heart
            PerimeterSegment::Smooth {
                first_normal: Vec2::new(1.0, -1.0).normalize(),
                last_normal: Vec2::NEG_X,
                indices: (resolution + 1..2 * resolution).chain([0]).collect(),
            },
        ]
    }
}

a 2D heart primitive and its extrusion

The Extrudable trait allows you to easily implement meshing for extrusions of custom primitives. Of course, you could also implement meshing manually for your extrusion.

If you want to see a full implementation of this, you can check out the custom primitives example.

Add UV channel selection to StandardMaterial #

TODO

Implement fast depth of field as a postprocessing effect. #

TODO

Add Rounded box gizmos #

Bevy's gizmos are a powerful tool for debugging and testing and you may want to draw alot of different shapes with them.

As of bevy 0.14 you can also draw rounded rectangles and cuboids!

gizmos
	.rounded_rect_2d(Vec2::ZERO, 0., Vec2::splat(630.), WHITE)
	.corner_radius(100.);

my_gizmos
	.rounded_cuboid(
		Vec3::ZERO,
		Quat::IDENTITY,
		Vec3::splat(2.),
		WHITE,
	)
	.edge_radius(0.1)
	.arc_resolution(4);

If you set the corner_radius or edge_radius to a positive value, the corners will be rounded outwards. However, if you provide a negative value, the corners will flip and curve inwards.

rounded gizmos cuboids rounded gizmos rectangles

#12502 Remove limit on RenderLayers. #

Implement volumetric fog and volumetric lighting, also known as light shafts or god rays. #

TODO

Add on_unimplemented Diagnostics to Most Public Traits #

  • Authors: @bushrat011899, @alice-i-cecile, @BD103, @Themayu
  • Pull Request

Bevy takes full advantage of the powerful type system Rust provides, but with that power can often come confusion when even minor mistakes are made.

use bevy::prelude::*;

struct MyResource;

fn main() {
    App::new()
        .insert_resource(MyResource)
        .run();
}

Running the above will produce a compiler error, let's see why...

Click to expand...
error[E0277]: the trait bound `MyResource: Resource` is not satisfied
   --> example.rs:6:32
    |
6   |     App::new().insert_resource(MyResource).run();
    |                --------------- ^^^^^^^^^^ the trait `Resource` is not implemented for `MyResource`
    |                |
    |                required by a bound introduced by this call
    |
    = help: the following other types implement trait `Resource`:
              AccessibilityRequested
              ManageAccessibilityUpdates
              bevy::a11y::Focus
              DiagnosticsStore
              FrameCount
              bevy::prelude::Axis<T>
              WinitActionHandlers
              ButtonInput<T>
            and 127 others
note: required by a bound in `bevy::prelude::App::insert_resource`
   --> /bevy/crates/bevy_app/src/app.rs:537:31
    |
537 |     pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self {
    |                               ^^^^^^^^ required by this bound in `App::insert_resource`

The compiler suggests we use a different type that implements Resource, or that we implement the trait on MyResource. The former doesn't help us at all, and the latter fails to mention the available derive macro.

With the release of Rust 1.78, Bevy can now provide more direct messages for certain types of errors during compilation using diagnostic attributes.

error[E0277]: `MyResource` is not a `Resource`
   --> example.rs:6:32
    |
6   |     App::new().insert_resource(MyResource).run();
    |                --------------- ^^^^^^^^^^ invalid `Resource`
    |                |
    |                required by a bound introduced by this call
    |
    = help: the trait `Resource` is not implemented for `MyResource`
    = note: consider annotating `MyResource` with `#[derive(Resource)]`
    = help: the following other types implement trait `Resource`:
              AccessibilityRequested
...

Now, the error message has a more approachable entry point, and a new note section pointing to the derive macro for resources. If Bevy's suggestions aren't the solution to your problem, the rest of the compiler error is still included just in case.

These diagnostics have been implemented for various traits across Bevy, and we hope to improve this experience as new features are added to Rust. For example, we'd really like to improve the experience of working with tuples of Component's, but we're not quite there yet. You can read more about this change in the pull request and associated issue.

bevy_reflect: Custom attributes #

One of the features of Bevy's reflection system is the ability to attach arbitrary "type data" to a type. This is most often used to allow trait methods to be called dynamically. However, some users saw it as an opportunity to do other awesome things.

The amazing bevy-inspector-egui used type data to great effect in order to allow users to configure their inspector UI per field:

use bevy_inspector_egui::prelude::*;
use bevy_reflect::Reflect;

#[derive(Reflect, Default, InspectorOptions)]
#[reflect(InspectorOptions)]
struct Slider {
    #[inspector(min = 0.0, max = 1.0)]
    value: f32,
}

Taking inspiration from this, Bevy 0.14 adds proper support for custom attributes when deriving Reflect, so users and third-party crates should no longer need to create custom type data specifically for this purpose. These attributes can be attached to structs, enums, fields, and variants using the #[reflect(@...)] syntax, where the ... can be any expression that resolves to a type implementing Reflect.

For example, we can use Rust's built-in RangeInclusive type to specify our own range for a field:

use std::ops::RangeInclusive;
use bevy_reflect::Reflect;

#[derive(Reflect, Default)]
struct Slider {
    #[reflect(@RangeInclusive<f32>::new(0.0, 1.0))]
    // Since this accepts any expression,
    // we could have also used Rust's shorthand syntax:
    // #[reflect(@0.0..=1.0_f32)]
    value: f32,
}

Attributes can then be accessed dynamically using TypeInfo:

let TypeInfo::Struct(type_info) = Slider::type_info() else {
    panic!("expected struct");
};

let field = type_info.field("value").unwrap();

let range = field.get_attribute::<RangeInclusive<f32>>().unwrap();
assert_eq!(*range, 0.0..=1.0);

This feature opens up a lot of possibilities for things built on top of Bevy's reflection system. And by making it agnostic to any particular usage, it allows for a wide range of use cases, including aiding editor work down the road.

In fact, this feature has already been put to use by bevy_reactor to power their custom inspector UI:

#[derive(Resource, Debug, Reflect, Clone, Default)]
pub struct TestStruct {
    pub selected: bool,

    #[reflect(@ValueRange::<f32>(0.0..1.0))]
    pub scale: f32,

    pub color: Srgba,
    pub position: Vec3,
    pub unlit: Option<bool>,

    #[reflect(@ValueRange::<f32>(0.0..10.0))]
    pub roughness: Option<f32>,

    #[reflect(@Precision(2))]
    pub metalness: Option<f32>,

    #[reflect(@ValueRange::<f32>(0.0..1000.0))]
    pub factors: Vec<f32>,
}

A custom UI inspector built using the code above in bevy_reactor

Deprecate dynamic plugins #

bevy_dynamic_plugin was a tool added in Bevy's original 0.1 release: intended to serve as a tool for dynamically loading / linking Rust code for use with things like modding. Unfortunately, this feature didn't see much community uptake, and as a result had a vanishingly small number of contributions to refine and document it over the years.

Combined with a challenging, intrinsically unsafe API that was producing worrying failures for users, we've decided to deprecate bevy_dynamic_plugin and will be removing it completely in Bevy 0.15. If you were a happy user of this, simply copy the rather-small crate into your own project and proceed as before.

We still think that both modding and hot-reloading code for faster development times are valuable use cases that Bevy should help support one day. Our hope is that by removing this as a first-party crate, we can spur on third-party experiments and avoid wasting users' time as they investigate a complex potential solution before concluding that it doesn't yet meet their needs.

implement the full set of sort methods on QueryIter #

TODO

Implement a SystemBuilder for building SystemParams #

TODO

New circular primitives: Arc2d, CircularSector, CircularSegment #

  • Authors: @aristaeus, @spectria-limina, @Jondolf, @alice-i-cecile
  • Pull Request

TODO

Implement Rhombus 2D primitive. #

  • Authors: @salvadorcarvalhinho, @LuisFigueiredo73
  • Pull Request

Implement opt-in sharp screen-space reflections for the deferred renderer, with improved raymarching code. #

Implement motion vectors and TAA for skinned meshes and meshes with morph targets. #

glTF labels: add enum to avoid misspelling and keep up-to-date list documented #

  • Authors: @mockersf, @rparrett, @alice-i-cecile
  • Pull Request

fix: upgrade to winit v0.30 #

Winit v0.30 changed its API to support a trait based architecture instead of a plain event-based one.

Bevy 0.14 now implements that new architecture, making the event loop handling easier to follow.

It's now possible to define a custom winit user event, that can be used to trigger App updates, and that can be read inside systems to trigger specific behaviours. This is particularly useful to send events from outside the winit event loop and manage them inside Bevy systems (see the window/custom_winit_event.rs example).

The UpdateMode now accepts only two values: Continuous and Reactive. The latter exposes 3 new properties to enable reactivity to device, user, or window events. The previous UpdateMode::Reactive is now equivalent to UpdateMode::reactive(), while UpdateMode::ReactiveLowPower maps to UpdateMode::reactive_low_power().

The ApplicationLifecycle has been renamed as AppLifecycle, and now contains the possible values of the application state inside the event loop:

  • Idle: the loop has not started yet
  • Running (previously called Started): the loop is running
  • WillSuspend: the loop is going to be suspended
  • Suspended: the loop is suspended
  • WillResume: the loop is going to be resumed

Note: the Resumed state has been removed since the resumed app is just Running.

add handling of all missing gltf extras: scene, mesh & materials #

The glTF 3D model file format allows passing additional user defined metadata in the extras properties, and in addition to the glTF extras at the primitive/node level , Bevy now has specific GltfExtras for:

  • scenes: SceneGltfExtras injected at the scene level if any
  • meshes: MeshGltfExtras, injected at the mesh level if any
  • materials: MaterialGltfExtras, injected at the mesh level if any: ie if a mesh has a material that has gltf extras, the component will be injected there.

You can now easily query for these specific extras

fn check_for_gltf_extras(
    gltf_extras_per_entity: Query<(
        Entity,
        Option<&Name>,
        Option<&GltfSceneExtras>,
        Option<&GltfExtras>,
        Option<&GltfMeshExtras>,
        Option<&GltfMaterialExtras>,
    )>,
) {
    // use the extras' data 
    for (id, name, scene_extras, extras, mesh_extras, material_extras) in
        gltf_extras_per_entity.iter()
    {

    }
}

This makes passing information from programs such as Blender to Bevy via gltf files more spec compliant, and more practical !

Map entities from a resource when written to the world. #

Bevy's DynamicScene is a collection of resources and entities that can be serialized to create collections like prefabs or savegame data. When a DynamicScene is deserialized and written into a World - such as when a saved game is loaded - the dynamic entity identifiers inside the scene must be mapped to their newly spawned counterparts.

Previously, this mapping was only available to Entity identifiers stored on Components. In Bevy 0.14, Resources can reflect MapEntitiesResource and implement the MapEntities trait to get access to the EntityMapper.

    // This resource reflects MapEntitiesResource and implements the MapEntities trait.
    #[derive(Resource, Reflect, Debug)]
    #[reflect(Resource, MapEntitiesResource)]
    struct TestResource {
        entity_a: Entity,
        entity_b: Entity,
    }

    // A simple and common use is a straight mapping of the old entity to the new.
    impl MapEntities for TestResource {
        fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
            self.entity_a = entity_mapper.map_entity(self.entity_a);
            self.entity_b = entity_mapper.map_entity(self.entity_b);
        }
    }

Normalise matrix naming #

Most game engines use a matrix stack to represent the space transformations in the game world. The stack usually contains transformations for the following spaces:

  • Normalized Device Coordinates: used by the graphics API directly
  • Clip Space: coordinates after projection but before perspective divide
  • View Space: coordinates in the camera's view
  • World Space: global coordinates (this is the one we most often talk about!)
  • Model Space: (or local space) coordinates relative to an entity

A common example is the 'model view projection matrix', which is the transformation from model space to NDC space (peculiarly in this shorthand, the view matrix is often a transformation from world to view space, but the model matrix is a transformation from model (or local) space to world space). Usually, matrices in the stack are referred to as part of that shorthand, so for example, the projection matrix transforms from view coordinates to NDC coordinates.

In a couple of places, Bevy had a view matrix, which was the transformation from view to world space (rather than from world to view space as above). Additionally, even when used consistently, the single-word shorthands are ambiguous and can cause confusion. We felt that a clearer convention was needed.

From now on, matrices in Bevy are named y_from_x, for example world_from_local, which would denote the transformation from local to world-space coordinates. One tidy benefit of this is that the inverse matrices are named x_from_y, and when multiplying between spaces, it's easy to see that it's correct.

For example, instead of writing:

let model_view_projection = projection * view * model;

You might now write:

let clip_from_local = clip_from_view * view_from_world * world_from_local;

Added CompassQuadrant and CompassOctant as per #13647 #

There are many instances in game development where its important to know the compass facing for a given direction. This is particularly true in 2D games that use four or eight direction sprites, or want to map analog input into discrete movement directions.

In order to make this easier the enums CompassQuadrant (for a four-way division) and CompassOctant (for an eight-way division) have been added with implementations to and From<Dir2> for ease of use.

Implement PBR anisotropy per KHR_materials_anisotropy. #

Implement subpixel morphological antialiasing, or SMAA. #

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:

  • Better scenes: Scenes are one of Bevy's core building blocks: designed to be a powerful tool for saving games, authoring levels and creating reusable game objects, whether they're a radio button widget or a monster. More features and a unified syntax between assets and code should unblock progress on a UI widget abstraction, tools less boilerplates. Check out the design doc for more information.
  • Relations please?: Relations (a first-class feature for linking entities together) is wildly desired but remarkably complex, driving features and refactors to our ECS internals. The working group has been patiently laying out what we need to do and why in this RFC.
  • Better audio: Bevy's built-in audio solution has never really hit the right notes. The Better Audio working group is plotting a path forward and exploring ECS-ified interface to the popular kira audio backend.
  • Contributing book: Our documentation on how to contribute is scattered to the four corners of our repositories. By gathering this together, the Contributing Book working group hopes to make it easier to discover and maintain.
  • Curve abstraction: Curves come up all of the time in game dev, and the mathmagicians that make up the Curve Crew are designing a trait to unify and power them.
  • Better text: our existing text solution isn't up to the demands of modern UI. The "Lorem Ipsum" working group is looking into replacing it with a better solution.
  • A unified view on dev tools: In 0.14, we've added a stub bevy_dev_tools crate: a place for tools and overlays that speed up game development such as performance monitors, fly cameras, or in-game commands to spawn game objects. We're working on adding more tools, and creating a dev tool abstraction. This will give us a unified way to enable/disable, customise and group this grab bag of tools into toolboxes to create something like Quake console or VSCode Command Palette with tools from around the ecosystem.
  • Bevy Remote Protocol: Communicating with actively running Bevy games is an incredibly powerful tool for building editors, debuggers and other tools. We're developing a reflection-powered protocol to create a solution that's ready to power a whole ecosystem.
  • A modular, maintainable render graph: Bevy's existing rendering architecture is already quite good at providing reusable renderer features like RenderPhases, batching, and draw commands. However, the render graph itself is one remaining pain point: since it's distributed across many files the control flow is hard to understand, and its heavy use of ECS resources for passing around rendering data actively works against modularity. While the exact design hasn't been finalized (and feedback is very welcome!), we've been actively working to redesign the render graph in order to build up to a larger refactor of the renderer towards modularity and ease of use.

Support Bevy #

Sponsorships help make our work on Bevy sustainable. If you believe in Bevy's mission, consider sponsoring us… every bit helps!

Donate heart icon

Contributors #

A huge thanks to the 221 contributors that made this release (and associated docs) possible! In random order:

  • @TheNeikos
  • @IQuick143
  • @VictorBulba
  • @juliohq
  • @eerii
  • ebola
  • @Waridley
  • @stepancheg
  • @adithramachandran
  • @umut-sahin
  • @JoshuaSchlichting
  • @andriyDev
  • @UkoeHB
  • @jake8655
  • @robtfm
  • @Davier
  • @moonlightaria
  • @zuiyu1998
  • @cart
  • @R081n
  • @eira-fransham
  • @Elabajaba
  • @emilyselwood
  • @TheCarton
  • @s-puig
  • Fpgu
  • @waywardmonkeys
  • @mintlu8
  • @coolreader18
  • @yyogo
  • @simbleau
  • @atlv24
  • @BeastLe9enD
  • @Gingeh
  • @eero-lehtinen
  • @DGriffin91
  • @StephenTurley
  • @daxpedda
  • @Hexorg
  • @nicoburns
  • @msvbg
  • @StrikeForceZero
  • @miroim
  • @Zoomulator
  • @SludgePhD
  • @orzogc
  • @NthTensor
  • @marcelchampagne
  • @spectria-limina
  • @RobWalt
  • @pcwalton
  • @rmsthebest
  • @MarcoMeijer
  • @viridia
  • @tjamaan
  • @tomara-x
  • @Olle-Lukowski
  • @pietrosophya
  • @13ros27
  • @Xzihnago
  • @Chubercik
  • @unknownue
  • @JMS55
  • @Testare
  • @lee-orr
  • @franklinblanco
  • @cBournhonesque
  • @MrGVSV
  • @salvadorcarvalhinho
  • @OneFourth
  • @sampettersson
  • @NixyJuppie
  • @ShadowMitia
  • Kellner, Robin
  • @AmionSky
  • @Aztro-dev
  • @honungsburk
  • @mweatherley
  • @jirisvd
  • @MTFT-Games
  • @Themayu
  • @ycysdf
  • @aevyrie
  • @NoahShomette
  • @yrns
  • @sam-kirby
  • @vertesians
  • @solis-lumine-vorago
  • @TrialDragon
  • @Shatur
  • @Maximetinu
  • @vitorfhc
  • @Kanabenki
  • @NiseVoid
  • Alice Cecile
  • @chescock
  • @Zeophlite
  • @ekropotin
  • @mockersf
  • @Brezak
  • @TimLeach635
  • @mnmaita
  • @targrub
  • @janhohenheim
  • @pablo-lua
  • @bushrat011899
  • @bcolloran
  • @djeedai
  • @chompaa
  • @gabrielkryss
  • @IceSentry
  • @komadori
  • @Aceeri
  • @uwuPyxl
  • @mghildiy
  • @jgayfer
  • @A-Walrus
  • @ickshonpe
  • @torsteingrindvik
  • @thebluefish
  • @TheNullicorn
  • @LeshaInc
  • @pkupper
  • @blukai
  • @theon
  • @Victoronz
  • @JoJoJet
  • @spooky-th-ghost
  • @aristaeus
  • @infmagic2047
  • @benfrankel
  • @soqb
  • @porkbrain
  • @JeanMertz
  • @66OJ66
  • @TimJentzsch
  • @Multirious
  • @allsey87
  • @xStrom
  • @ricky26
  • @philpax
  • @oyasumi731
  • @nbielans
  • @re0312
  • @EmiOnGit
  • @mgi388
  • @Remi-Godin
  • @greytdepression
  • @jnhyatt
  • @tygyh
  • @Vrixyz
  • @jakobhellermann
  • @nzhao95
  • @AldanTanneo
  • @james-j-obrien
  • @CptPotato
  • @AxiomaticSemantics
  • @iiYese
  • @geckoxx
  • @matiqo15
  • @LuisFigueiredo73
  • @hi-names-nat
  • @IWonderWhatThisAPIDoes
  • @geekvest
  • @JohnTheCoolingFan
  • @kristoff3r
  • @dmlary
  • @snendev
  • @ameknite
  • @oli-obk
  • @TheRawMeatball
  • @tychedelia
  • @rparrett
  • @kettle11
  • @stowmyy
  • @coreh
  • @awwsmm
  • @Kurble
  • @andristarr
  • @ghost
  • @tguichaoua
  • @bugsweeper
  • @Earthmark
  • @Jondolf
  • @gibletfeets
  • @mrtolkien
  • @dependabot[bot]
  • @jkb0o
  • @SkiFire13
  • @paolobarbolini
  • @MiniaczQ
  • @superdump
  • @jdm
  • @forgemo
  • @inodentry
  • @Friz64
  • @floppyhammer
  • @mrchantey
  • @SolarLiner
  • @ArthurBrussee
  • Franklin
  • @findmyhappy
  • @afonsolage
  • @rib
  • @agiletelescope
  • @Zeenobit
  • @doonv
  • @ManevilleF
  • @hymm
  • @SpecificProtagonist
  • @notmd
  • @james7132
  • @arcashka
  • @alice-i-cecile
  • @nicopap
  • @ChristopherBiscardi
  • @BD103
  • @feyokorenhof
  • @stinkytoe
  • @maniwani
  • @mamekoro

Full Changelog #

The changes mentioned above are only the most appealing, highest impact changes that we've made this cycle. Innumerable bug fixes, documentation changes and API usability tweaks made it in too. For a complete list of changes, check out the PRs listed below.

Accessibility + Windowing #

Animation #

Animation + Assets #

Animation + Color + Math + Rendering #

Animation + Color + Rendering #

App #

Assets #

Assets + Diagnostics #

Assets + ECS #

Assets + Reflection #

Assets + Rendering #

Audio #

Build-System #

Build-System + Cross-Cutting #

Build-System + Dev-Tools #

Build-System + ECS + Reflection #

Build-System + Meta #

Build-System + Rendering #

Color #

Color + Gizmos #

Color + Gizmos + Rendering + Text + UI #

Color + Rendering #

Color + Rendering + UI #

Core #

Cross-Cutting #

Cross-Cutting + Dev-Tools + Gizmos #

Cross-Cutting + ECS #

Dev-Tools #

Dev-Tools + Diagnostics #

Dev-Tools + Diagnostics + Rendering #

Dev-Tools + ECS + Networking #

Dev-Tools + Gizmos + UI #

Dev-Tools + Reflection #

Dev-Tools + Windowing #

Diagnostics #

Diagnostics + ECS #

Diagnostics + Rendering #

ECS #

ECS + Pointers #

ECS + Reflection #

ECS + Rendering #

ECS + Tasks #

ECS + Utils #

Editor #

Editor + Gizmos #

Editor + Reflection #

Gizmos #

Gizmos + Math #

Gizmos + Reflection #

Gizmos + Rendering #

Hierarchy #

Hierarchy + Scenes #

Input #

Input + Windowing #

Math #

Math + Reflection #

Math + Rendering #

Math + Transform #

Math + Utils #

Math + Windowing #

Meta #

Modding #

Pointers #

Reflection #

Reflection + Rendering #

Reflection + Scenes #

Reflection + Time #

Reflection + Utils #

Rendering #

Rendering + Transform #

Rendering + UI #

Rendering + Utils #

Rendering + Windowing #

Scenes #

Tasks #

Tasks + Windowing #

Text #

Time #

Transform #

Transform + UI #

UI #

Utils #

Windowing #

No area label #