Using egui for Bevy ECS Introspection with Macroquad Rendering
Tags: rust, gamedev, bevy
Published: 2024-06-05 17:19:46

---
title: "Using egui for Bevy ECS Introspection with Macroquad Rendering"
published: "true"
description: "Using egui for Bevy ECS Introspection: adding DevTools to a Rust Macroquad game, using Rapier physics and Bevy ECS, without Bevy itself."
tags: "rust, gamedev, bevy"
canonical_url: "https://rodneylab.com/using-egui-for-bevy-ecs-introspection/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2fwfjpk26xh19u4v140o.png"
---

## 👀 Using egui for Bevy ECS Introspection

In this post, we look at using egui for Bevy Introspection with Macroquad rendering.  This continues the series of recent posts, which started with adding egui DevTools to Macroquad, then added Rapier physics, units of measurement and an Entity Component system (**ECS**).

Here, we bring everything together, adding DevTools-like introspection to the Bevy ECS demo from the previous post.  As before, the complete project code is on GitHub (link further down).

## 🧱 What are we Building Here?

![Using egui for Bevy ECS Introspection: A number of blue, yellow and orange bubbles are trapped at the top of the screen.  The Developer tools panel appears to contain details for all of them, with the details for just 2 or 3 scrolled into view.  The headings Entity Generation:ID 1:8 and Entity Generation:ID 1:9 are visible.  These top sections with Position, Velocity, Colour, and Radius data for their respective bubble.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ythoji3g090q87wkak96.png)

The demo simulation, built in Rust, shows a series of coloured, floating bubbles release and float to top of the window.  The bubbles subsequently get trapped at the top of the screen; filling the window, from the top down.

I employed the following tools to build the demo:

- **Macroquad** — a fast, quick-to-get going on Rust tool built with game prototyping in mind;
- **Rapier** — mature, pure Rust physics engine for game dev;
- **uom** — a crate with applications in Aerospace for maintaining consistency in physical quantity units and helping to avoid unit errors;
- **Bevy ECS** — ECS implementation from the Bevy game engine, added to this project as the stand-alone `bevy_ecs` crate; and
- **egui** — an immediate Graphics User Interface (**GUI**) library for Rust (inspired, in fact, by the <a href="https://rodneylab.com/using-raylib-with-dear-imgui/">C++ Dear ImGui library</a>).

I focus on the egui integration in this post, so feel free to jump back through the series to learn more about how I put the other elements together.

## ⚙️ Crate Setup

You might need to use slightly older (than the latest) versions of some crates to get everything working together.  Here is my `Cargo.toml`, which you can use as a starting point for finding compatible versions of `egui`, `egui-macroquad` and `macroquad`:


```toml
[package]
name = "macroquad-bevy-ecs-introspection"
version = "0.1.0"
edition = "2021"
license = "BSD-3-Clause"
repository = "https://github.com/rapier-example"
# bevy_ecs 0.13 requires MSRV 1.76
rust-version = "1.76"
description = "Macroquad Rapier ECS 🦀 Rust game dev — using bevy's 🧩 Entity Component System in a Macroquad game with Rapier physics."

[dependencies]
bevy_ecs = "0.13.2"
crossbeam = "0.8.4"
egui = "0.21.0"
egui-macroquad = "0.15"
macroquad = { version = "0.3.26", default-features = false }
rand = "0.8.5"
rand_distr = "0.4.3"
rapier2d = { version = "0.19.0", features = ["simd-stable"] }
uom = "0.36.0"
```

## 🔄 Update egui Introspection System

I <a href="https://rodneylab.com/macroquad-rapier-ecs/">introduced Bevy ECS to the project in a previous Macroquad Rapier ECS post</a>.  Continuing, we need to add a couple of ECS systems for updating and rendering the egui DevTools.  First up is the `update_dev_tools_system` in `src/systems.rs`:

```rust
pub fn update_dev_tools_system(query: Query<(Entity, &Position, &Velocity, &CircleMesh)>) {
    egui_macroquad::ui(|egui_ctx| {
        egui_ctx.set_pixels_per_point(4.0);
        egui::Window::new("Developer Tools").show(egui_ctx, |ui| {
            ScrollArea::vertical().show(ui, |ui| {
                CollapsingHeader::new("Bubbles")
                    .default_open(false)
                    .show(ui, |ui| {
                        for (entity, position, bubble_velocity, circle_mesh) in &query {
                            draw_ball_ui_data(ui, entity, position, bubble_velocity, circle_mesh);
                        }
                    });
            });
        });
    });
}
```

### Bevy ECS Queries

Typically, for Bevy ECS, this function takes a query over ECS components as its argument (line `107`).  Here, the query generates a collection of all ECS entities that have `Position`, `Velocity`  and `CircleMesh` components (essentially the bubbles). Additionally:

- In line `109`, I call `set_pixels_per_point` this scales up the UI. `4.0` worked for screen captures, though for general use, you will probably want something a touch lower.
- We need a `ScrollArea` in the UI (line `111`) to make viewing data for the dozens of balls in the simulation more practical.
- You can iterate over the ECS entities satisfying the initial query, with a simple for loop (line `115`).

Next, we look at the `draw_ball_ui_data` function called within that last loop.


## 🫧 Individual Bubble Updates

![Using egui for Bevy ECS Introspection: An orange bubble is trapped in the top, right corner of the window.  Towards the left, is the Developer Tools Panel.  This show properties for the bubble, reading Entity Generation:ID 1:0, Position x: 26.26 m, y: -0.65m, Velocity x: -0.00 m/s, y: 0.00 m/s, Colour (orange dot is displayed), Radius 0.6m](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fk4gjr4t05kuexwp6c7u.png)

Another advantage of using the `uom` crate for handling physical quantities is formatting of those values for output, which we use in the `draw_ball_ui_data` function (`src/systems`):

```rust
fn draw_ball_ui_data(
    ui: &mut Ui,
    entity: Entity,
    position: &Position,
    bubble_velocity: &Velocity,
    circle_mesh: &CircleMesh,
) {
    let m = Length::format_args(length::meter, Abbreviation);
    let m_s = VelocityUnit::format_args(velocity::meter_per_second, Abbreviation);
    CollapsingHeader::new(format!(
        "Entity Generation:ID {}:{}",
        entity.generation(),
        entity.index()
    ))
    .default_open(false)
    .show(ui, |ui| {
        ui.horizontal(|ui| {
            ui.label("Position");
            ui.label(format!(
                "x: {:.2}, y: {:.2}",
                m.with(position.0.x),
                m.with(position.0.y)
            ))
        });
        // TRUNCATED...
    });
}
```

### Quantity Formatting

To select preferred formatting, in lines `55` and `56`, we add unit formatting for metre and metre per second quantities.  These uom format args incorporate type checking, so you would get a compile-time error if you tried to format a length quantity using metres per second, for example.

Notice, in for example, in lines `67`-`70` that we can use standard Rust formatting arguments with the `uom` format arg to specify the precision we want position output data with.  Here, the `{:.2}` format specifiers indicates we want to round the position values to two decimal places.

### ECS Entity Generations and IDs

ECS entities are not a lot more than an integer ID and a generation.  The generation provides a mechanism for reusing IDs.  For example, the first bubble spawned might have ID `0` and generation `1`.  If we then despawned that bubble from the ECS, the next bubble can re-use ID `0`, which is now free.  However, the ECS would give it a generation of `2`, so it can be distinguished from the earlier one.

We access, both `generation` an `id` from the Bevy ECS `Entity` struct calling `.generation()` and `.index()` in lines `59` and `60`.

## ✍🏽 Draw UI System

The system for actually drawing the DevTools panel, using `egui` is a little simpler — we just need to call `egui_macroquad::draw`:

```rust
pub fn draw_dev_tools_system() {
    egui_macroquad::draw();
}
```

## 📆 Bringing it all together: ECS Schedule

I added the two new systems to the Bevy ECS schedule, to give us some control over the order Bevy ECS runs them in (`src/main.rs`):

```rust
let mut playing_schedule = Schedule::default();
playing_schedule
        .configure_sets(
                (
                        ScheduleSet::BeforeSimulation,
                        ScheduleSet::Simulation,
                        ScheduleSet::AfterSimulation,
                )
                        .chain(),
        )
        .add_systems(
                (
                        create_ball_physics_system,
                        (
                                update_dev_tools_system,
                                draw_balls_system,
                                draw_dev_tools_system,
                        )
                                .chain(),
                )
                        .chain()
                        .in_set(ScheduleSet::BeforeSimulation),
        )
        .add_systems(step_physics_engine_system.in_set(ScheduleSet::Simulation))
        .add_systems(
                (
                        update_balls_system,
                        spawn_new_ball_system,
                        end_simulation_system,
                )
                        .in_set(ScheduleSet::AfterSimulation),
        );
```

With Bevy ECS, schedule systems grouped into a tuple will run in parallel.  Tacking `.chain()` onto the end of the tuple tells the ECS that you want the systems to run in series, in the order of appearance.

## 🏁 What Next?

I have the basic, minimal DevTools panel working now.  In terms of extensions and where to go next, I am considering:

- adding a wireframe view mode, with toggle, to show/hide the Rapier colliders;
- buttons for pausing, stepping and restarting the simulation from the dev panel tool; and
- adding the collider properties to the panel.

Interested to know if you have some ideas on and also, what else might be a good direction to take this in.  Drop a comment below, or <a href="https://links.rodneylab.com/">reach out on other channels</a>.

## 🙌🏽 Using egui for Bevy ECS Introspection: Wrapping Up

In this post on using egui for Bevy ECS introspection, we looked at displaying  Rapier physical properties with egui and Macroquad rendering.  In particular, we saw:

- code for adding **egui update and draw ECS systems**;
- how you can add an **egui ScrollArea to display large data collections**; and
- how you can **format uom quantities for display, including rounding to a fixed number of decimal places**.

I hope you found this useful.  As promised, you can <a href="https://github.com/rodneylab/rapier-bevy-ecs-introspection">get the full project code on the Rodney Lab GitHub repo</a>.  I would love to hear from you, if you are also new to Rust game development.  Do you have alternative resources you found useful? How will you use this code in your own projects?

## 🙏🏽 Using egui for Bevy ECS Introspection: Feedback

If you have found this post useful, see links below for further related content on this site.  Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on X, giving me a mention, so I can see what you did. Finally, be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on X (previously Twitter) and also, join the <a href="https://matrix.to/#/%23rodney:matrix.org">#rodney</a> Element Matrix room. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact/">further ways to get in touch with Rodney Lab</a>. I post regularly on <a href="https://rodneylab.com/tags/gaming/">Game Dev</a> as well as <a href="https://rodneylab.com/tags/rust/">Rust</a> and <a href="https://rodneylab.com/tags/c++/">C++</a> (among other topics). Also, <a aria-label="Subscribe to the   Rodney Lab newsletter" href="https://newsletter.rodneylab.com/issue/latest-issue">subscribe to the newsletter to keep up-to-date</a> with our latest projects.