π 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?
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 C++ Dear ImGui library).
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
:
[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 introduced Bevy ECS to the project in a previous Macroquad Rapier ECS post. 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
:
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 callset_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 (line111
) 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
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
):
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
:
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
):
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 reach out on other channels.
ππ½ 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 get the full project code on the Rodney Lab GitHub repo. 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 consider supporting me through Buy me a Coffee.
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 @askRodney on X (previously Twitter) and also, join the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Game Dev as well as Rust and C++ (among other topics). Also, subscribe to the newsletter to keep up-to-date with our latest projects.