Bevy 0.19 Migration & bessere Gebäude

Hinweis: Dieser Blogpost wurde maschinell aus dem englischen Original übersetzt. Zum englischen Original.

Überblick

Im letzten Monat ging ein großer Teil der Arbeit in die Migration von Exofactory von Bevy 0.18 auf Bevy 0.19. Abgesehen von einer weiteren cursed "Ich habe eine schlechtere Version eines Features gebaut, bevor Bevy x Monate später eine bessere Version gebaut hat" Situation lief der Port selbst reibungslos.

Der RIESIGE Gewinn von Bevy 0.19 ist bsn! Da habe ich viel Arbeit reingesteckt und gehe unten darauf ein.

Zusätzlich habe ich in Blender mit Skein ein bisschen was gemacht, um ein paar visuelle Bugs zu fixen, ebenfalls unten behandelt.

Insgesamt hätte ich gerne mehr geschafft, besonders weil Steams Nextfest im Oktober mal wieder schnell näher rückt. Andererseits ließ es sich nicht vermeiden. Mehr als eine Woche ging für Bornhack 2026 drauf, ein großartiges Hacker-Campout. Zu viele schöne Ablenkungen und zu viel zu tun. Trotzdem habe ich es geschafft, dort noch einen kleinen, netten Bevy-Workshop unterzubringen.

Wie immer: Wenn du diesen Post interessant findest oder dich das Spiel interessiert, würde ich empfehlen, es auf Steam auf die Wunschliste zu setzen. Das hilft enorm.

Richtige Gebäudeschatten

Item-Ein- und -Ausgänge reagieren nicht mehr auf Licht und Schatten.

Wenn du jemals ein 3D-Fabrikspiel gespielt hast, kennst du vielleicht den "Item kommt aus einem dunklen Tunnel/Portal/Loch" Trope. Exofactory nutzt den auch.

Das Problem war, dass diese Item-Ein- und -Ausgänge ganz normal mit Licht und Schatten interagiert haben. Das führte zu einem emotionalen Bruch, wenn ein Schatten über den Punkt gewandert ist oder wenn hartes, helles Licht auf besagte Fläche fiel.

Es sah aus, als würde ein Item durch eine massive Fläche des Gebäudes gehen. Emotional nicht schön.

Die Lösung war, das Material besagter Fläche so zu ändern, dass es nicht mit Licht interagiert. Da ich, wann immer es geht, einen datenorientierten Ansatz bevorzuge, habe ich mich entschieden, Blender aufzumachen, die Asset-Dateien der Gebäude anzupassen und der frisch isolierten Fläche über Skein die passenden Bevy Components zu verpassen.

Wenn du an irgendeinem 3D-Bevy-Spiel arbeitest und Blender benutzt, empfehle ich Skein dringend. Es eliminiert eine Menge Boilerplate-Code.

Bevy 0.19 Migration

Ok, lustige Geschichte. Ich habe anscheinend eine cursed Situation, in der ich ein Feature baue, nur um dann rauszufinden, dass eine bessere Version besagten Features bald nativ zur Bevy-Engine dazukommt.

Hier ist das mit Bevys neuen 0.19 Resources as Components passiert.

Exofactorys Multiplayer-Funktionalität basiert größtenteils auf dem Syncen von Components über das hervorragende bevy_replicon.

Eine Einschränkung von Replicon ist, dass es AUSSCHLIESSLICH Components synct. Keine Resources. Entsprechend hat es vor Bevy 0.19 keine Resources gesynct.

Component-Definition, die die Pseudo-Resources zeigt.

Was ich gemacht habe: halb-cursed Bevy-Component-Singletons als Pseudo-Resources benutzen. Für alle "Resources", die von der Server-Game-Instanz zu den Client-Instanzen gesynct werden müssen.

Verwendung eines Singletons als Pseudo-Resource

Diese Pseudo-Resources habe ich dann in Systems abgefragt und behandelt, als wären sie Resources.

Mit Bevy 0.19 ist das ein bisschen redundant, aber ich fand es lustig genug, um es zu teilen. Erstmal behalte ich die Pseudo-Resources, die ich habe, aber in Zukunft sollte es einfacher werden.

BSN Boilerplate-Reduktion

Hier ist der Großteil meiner Zeit reingegangen. Vor Bevy 0.19 habe ich, wenn Leute mich gefragt haben, wie es ist, mit Bevy zu arbeiten, gesagt:

"Bevy ist großartig, aber im Bevy-UI-System zu arbeiten ist grauenhaft macht sehr wenig Spaß."

Vor Bevy 0.19 hätte ich es als die Verbosity von Rust gemischt mit dem Alignment-Spaß von CSS beschrieben. Jetzt ist es nur noch der CSS-Teil, was ehrlich gesagt akzeptabel ist, sobald das Verbosity-Element wegfällt.

Wie groß der Verbosity-Unterschied ist, fragst du? Also, für Exofactory war meine Codebase nach dem Wechsel zu bsn 7.364 Zeilen Code kleiner. Knapp unter 10% der gesamten Codebase.

20.673 Zeilen imperativer UI-Code ersetzt durch 13.309 Zeilen deskriptives bsn. Großartig.

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(-)

full_git_diff_width.txt

Worauf ich mich WIRKLICH freue, sind separate .bsn Dateien und hoffentlich ein passender LSP.

Ich stelle mir vor, dass ich weitere 13.000 Zeilen Code "reduzieren" könnte, indem ich einfach alle meine bsn! Makros in *.bsn Dateien verschiebe. Mit .bsn Dateien herumzuspielen ginge ohne Recompile. Zusätzlich könnten .bsn Dateien Game-Modding ergonomisch machbar machen. Ganze Level, Szenarien, Settings und dergleichen könnten von Spielenden in hot-loadbaren, von der Community geschriebenen bsn-Dateien definiert werden.

BSN UI-Ergonomie-Verbesserungen

Die Reduktion der Codezeilen ist aber nicht der einzige Vorteil. Sobald du bsn hast, kannst du performante Retained-Mode-UIs (UI wird zwischen Frames behalten) mit der Leichtigkeit von Immediate-Mode-Toolkits (ganze UI wird jeden Frame neu generiert) wie bevy_egui schreiben.

Seit Bevy 0.19 glaube ich nicht, dass es Sinn ergibt, überhaupt noch Immediate-Mode-UIs zu schreiben, außer man hat einen sehr speziellen Use Case.


// Bevy 0.18 "traditionelle" 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");
        }
    }
}

Oben sehen wir, dass UI-Code vor Bevy 0.19 ziemlich boilerplate-lastig und mühsam sein kann. Weniger lustig.


// UI im bevy_egui-Stil
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");
        }
    });
}

Oben haben wir eine Immediate-Mode-UI. Viel ergonomischer als traditionelles Rust im 0.18-Stil, aber sie wird jeden Frame neu generiert.


// Bevy 0.19 mit bsn! Makros
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") ]
    });
}

Zum Schluss haben wir unser bsn! Makro. Ich würde sagen, das ist einfacher und sogar intuitiver als bevy_egui. Du bekommst die Leichtigkeit einer simplen UI, ohne die UI jeden Frame neu generieren zu müssen.


Die eingebaute on(...) Syntax im Observer-Stil macht es einfach, die UI richtig interaktiv zu machen.

Bevy Scene Notation ist wirklich ein schöner Sprung für Bevy-Devs. Ich verstehe, warum das Stabilisieren davon Priorität hatte, als Voraussetzung für viel kommende Bevy-Arbeit.

Fazit

Der letzte Monat war ein bisschen weniger feature-getrieben, als ich es gerne gesehen hätte, aber es wurde trotzdem essenzielle Arbeit gemacht. Ich glaube, ein Teil der Arbeit mit Bevy ist zu akzeptieren, dass es, während du an deinem Spiel arbeitest, neue Bevy-Releases geben wird. Persönlich finde ich, dass die "Steuer" fürs Migrieren und Modernisieren deines Spiels es wert ist, wie manche Steuern in der echten Welt auch. Ich glaube, nicht auf das neueste Bevy-Release zu migrieren setzt Bevy-Spiele nur dem Risiko aus, zu scheitern. Und dann verpasst du auch noch die neuesten ergonomischen Annehmlichkeiten, die Bevy immer angenehmer machen.

Und noch einmal: Wenn dir das Spaß gemacht hat, informativ war oder du das Spiel einfach magst, würde ich vorschlagen, es auf die Wunschliste zu setzen.