Bevy 0.19 Migration & Better Buildings
Overview
Over the last month a large amount of work was focused on migrating Exofactory from Bevy 0.18 to Bevy 0.19. Beyond an additional cursed "I implemented a worse version of a feature before Bevy implemented a better version x months later" situation the port itself went swimmingly.
The HUGE win of Bevy 0.19 is bsn! I did a lot of work here and cover it below.
Additionally I did some work in Blender with Skein to fix a few visual bugs, also addressed below.
Overall, I did wish to get more done, particularly with the Steam's October Nextfest once again quickly approaching. This being said it couldn't be avoided. More than a week was spent at Bornhack 2026, an amazing hacker campout. Too many fun distractions to be seen and too much to do. This being said, I did manage to sneak in a fun little Bevy workshop while I was there.
As always, if you find this post interesting, or if you are interested in the game, I would recommend wishlisting it on Steam. It helps a ton.
Proper Building Shadows
If you've ever played a 3D factory game you may be aware of the "item coming out of a dark tunnel/portal/hole" trope. Exofactory also uses this.
The issue was that these item entry/exit points interacted with light and shadows normally. This resulted in emotional disconnect when a shadow passed over the point or when harsh bright light shined over said surface.
It looked like an item was passing through a solid surface of the building. Not emotionally nice.
The solution was to change the material of said surface to not interact with light. Given that I prefer to go with a data oriented approach whenever I can I opted to open up Blender, modify the building asset files, and add the appropriate Bevy Components via Skein to the freshly isolated surface.
If you are working on any 3D Bevy game and use Blender I strongly recommend using Skein. It eliminates a ton of boilerplate code.
Bevy 0.19 Migration
Ok, funny story. I seemingly have a cursed situation where I implement a feature only to find out that a better version of said feature will soon be added to the Bevy engine natively.
Here this happened with Bevy's new 0.19 resources as components.
Exofactory's multiplayer functionality is largely based on syncing of components via the excellent bevy_replicon.
A limitation of replicon is that it EXCLUSIVELY syncs components. Not resources. Resultingly it did not sync resources prior to Bevy 0.19.

What I had been doing is using semi-cursed Bevy component singletons as pseudo resources. For any "resources" that need to be synced from the server game instance to client instances.

I would then access these pseudo resources in systems and would treat them as if they were resources.
With Bevy 0.19 this is a bit redundant, but I thought it was funny enough to share. For now I am keeping the pseudo resources that I have, but going forward things should be simpler.
BSN Boiler Plate Reduction
This is where I spent most of my time. Prior to Bevy 0.19 when people would ask me what it's like to work in Bevy I would say:
"Bevy is amazing, but the Bevy UI system is miserable very unfun to work in."
Prior to Bevy 0.19 I would have described it as the verbosity of Rust mixed with the alignment fun of CSS. Now it's just the CSS part which frankly is acceptable once you drop the verbosity element.
How much of a verbosity difference do you ask? Well for Exofactory my codebase was 7,364 lines of code smaller after switching to bsn. Just under 10% of the total code base.
20,673 lines of imperative UI code swapped out for 13,309 lines of descriptive bsn. Amazing.
git diff 837c64ca...01bb5769 --stat-width=55
Cargo.toml | 4 +-
assets/fast.assets.ron | 5 +-
crates/exo_base_ui/src/lib.rs | 4 -
.../src/ui/control_guide.rs | 463 +--
crates/exo_base_ui/src/ui/mod.rs | 104 +-
crates/exo_base_ui/src/ui/topbar.rs | 405 +--
.../inventory_grid/buffer_ops.rs | 30 +-
.../widgets/inventory_grid/mod.rs | 6 +-
.../inventory_grid/spawning.rs | 250 +-
.../src/gas_extractor.rs | 189 +-
crates/exo_building_ui/src/lib.rs | 343 +-
.../src/logistics_inventory/mod.rs | 6 +-
.../logistics_inventory/scroll.rs | 101 +-
.../logistics_inventory/transfer.rs | 18 +-
.../ui_builder.rs | 505 +--
.../src/mining_machine/layout.rs | 1329 +++----
.../src/mining_machine/mod.rs | 11 +-
.../src/power_grid_rows.rs | 166 +
.../src/power_relay/grid_display.rs | 202 +-
.../src/power_relay/interaction.rs | 15 +-
.../src/power_relay/layout.rs | 707 ++--
.../src/power_relay/mod.rs | 7 +-
.../grid_display.rs | 241 +-
.../power_substation/interaction.rs | 15 +-
.../src/power_substation/layout.rs | 681 ++--
.../src/power_substation/mod.rs | 7 +-
.../src/production/navigation.rs | 37 +-
.../src/production/scroll.rs | 37 +-
.../src/production/tabs.rs | 27 +-
.../src/production_components.rs | 27 +-
.../close_button.rs | 52 -
.../header_status.rs | 426 ---
.../production_layout/materials.rs | 114 -
.../src/production_layout/mod.rs | 9 +-
.../production_mode.rs | 240 --
.../production_tab.rs | 893 -----
.../src/production_layout/root.rs | 100 -
.../src/production_layout/scene.rs | 1100 ++++++
.../src/tier_1_core/crafting.rs | 102 -
.../src/tier_1_core/inventory.rs | 179 -
.../src/tier_1_core/io_panel.rs | 574 +--
.../src/tier_1_core/layout.rs | 419 +++
.../src/tier_1_core/mod.rs | 66 +-
.../src/tier_1_core/recipes.rs | 412 +--
.../src/tier_1_core/screen.rs | 705 ++--
.../src/tier_1_core/scrolling.rs | 16 +-
.../src/display_dialogue/mod.rs | 317 +-
crates/exo_exoframe_ui/Cargo.toml | 1 -
.../src/exoframe_mode_ui/mod.rs | 704 ++--
.../src/fleet_ui/layout.rs | 285 ++
.../src/fleet_ui/mod.rs | 1073 +-----
.../src/info_ui/layout.rs | 422 +++
.../logistics_settings/layout.rs | 755 ++++
.../logistics_settings/mod.rs | 1819 ++--------
.../src/info_ui/mod.rs | 1420 ++------
crates/exo_exoframe_ui/src/lib.rs | 29 +-
.../src/name_editing.rs | 221 ++
crates/exo_exoframes/src/lib.rs | 4 +-
crates/exo_exoframes/src/mining.rs | 387 +-
crates/exo_fast_assets/src/lib.rs | 2 +
.../exo_global_mode_ui/src/lib.rs | 75 +-
.../exo_loading_screen/src/lib.rs | 153 +-
crates/exo_main_menu/Cargo.toml | 8 +-
.../src/crash_report.rs | 481 +--
.../src/join_game/direct_connect.rs | 624 ----
.../src/join_game/focus.rs | 455 ---
.../src/join_game/lan.rs | 630 ++--
.../src/join_game/layout.rs | 753 ++++
.../src/join_game/mod.rs | 1345 ++++---
.../src/join_game/steam.rs | 369 +-
.../src/join_game/tabs.rs | 242 --
crates/exo_main_menu/src/lib.rs | 139 +-
.../src/load_game_ui.rs | 1749 ++++-----
.../src/main_menu_screen.rs | 672 ++--
.../src/build_status_hud.rs | 338 +-
crates/exo_placement/src/input.rs | 13 +-
crates/exo_placement/src/menu.rs | 1021 +++---
.../src/menu_content.rs | 771 ++--
crates/exo_placement/src/plugin.rs | 46 +-
crates/exo_placement/src/states.rs | 52 +-
.../src/assessment.rs | 2 +-
.../src/build_status.rs | 2 +-
crates/exo_save_runtime/src/lib.rs | 77 +-
.../src/navigation.rs | 1 +
.../journal/mod.rs | 597 ++-
.../journal/scroll.rs | 31 +-
.../in_game_main_menu/mod.rs | 900 ++---
.../multiplayer/direct_connect.rs | 643 ++--
.../multiplayer/input.rs | 768 ++--
.../multiplayer/lan.rs | 630 ++--
.../multiplayer/mod.rs | 1299 +++----
.../multiplayer/steam.rs | 196 +-
src/core_game/mod.rs | 27 +-
src/lib.rs | 2 -
src/splash_screen/mod.rs | 83 +-
95 files changed, 13309 insertions(+), 20673 deletions(-)
What I am REALLY looking forward to is separate .bsn files along with hopefully, an appropriate LSP.
I imagine I could do a further 13,000 line of code "reduction" simply by moving all of my bsn! macros to *.bsn files. Playing around with .bsn files could be done recompile free. Additionally .bsn files could make game modding ergonomically feasible. Whole levels, scenarios, settings, and the like could be player defined in hot loadable community written bsn files.
BSN UI Ergonomic Improvements
Line of code reduction alone isn't the only benefit though. Once you have bsn you can write performant retained mode (UI retained between frames) UIs with the ease of immediate mode (whole UI regenerated every frame) toolkits like bevy_egui.
As of Bevy 0.19 I don't think it makes sense to write any immediate mode UIs unless you have a very special use case.
// Bevy 0.18 "traditional" UI
fn setup(mut commands: Commands) {
commands.spawn((
Button,
Node { width: Val::Px(150.0), height: Val::Px(65.0), ..default() },
))
.with_children(|parent| {
parent.spawn(Text::new("Press me"));
});
}
fn button_system(
query: Query<&Interaction, Changed<Interaction>>,
) {
for interaction in &query {
if *interaction == Interaction::Pressed {
info!("hello");
}
}
}
Above we can see that pre Bevy 0.19 UI code can be pretty boilerplate heavy and tedious. Less fun.
// bevy_egui style UI
fn ui_system(mut contexts: EguiContexts) {
let Ok(ctx) = contexts.ctx_mut() else { return };
egui::CentralPanel::default().show(ctx, |ui| {
let press_me = ui.add_sized([150.0, 65.0], egui::Button::new("Press me"));
if press_me.clicked() {
info!("hello");
}
});
}
Above we have an immediate mode UI. Much more ergonomic than traditional 0.18 style Rust but re-generated every frame.
// Bevy 0.19 with bsn! macros
fn setup(mut commands: Commands) {
commands.spawn_scene(bsn! {
Button
Node { width: px(150), height: px(65) }
on(|_event: On<Pointer<Press>>| info!("hello"))
Children [ Text::new("Press me") ]
});
}
Lastly we have our bsn! macro. I would say this is simpler and even more intuitive than bevy_egui. You get the ease of simple UI without the need to regenerate the UI every frame.
The built in observer style on(...) syntax makes making the UI properly interactive easy.
Bevy Scene Notation is really a nice jump for Bevy devs. I see why stabilizing it has been the priority as a prerequisite for a lot of upcoming Bevy work.
Conclusion
The past month was a bit less feature driven than I would have liked to have seen, but essential work has been done regardless. I think part of working with Bevy is accepting that as you work on your game new Bevy releases will happen. Personally I think that the "tax" of migrating and modernizing your game, like certain real world taxes, is worth it. I think not migrating to the latest release of Bevy only puts Bevy games at risk of failure. That and you miss out on the latest ergonomic niceties that make Bevy ever nicer to use.
And once again, if you found this enjoyable, informative, or just like the game, I would suggest giving the game a wishlist.