HullStart
Start

From cargo new to a main menu

Seven steps. The first two already give you a window, a main menu, settings that save and Esc to pause.

  1. 01

    Add the crate

    Depend on the engine with its defaults off and pick features: physics (avian3d) and fps (the first-person controller). Name bevy yourself with no features, so its derive macros resolve; the engine picks every feature. Your dev feature turns the debug tools on.

    Placeholder Not on crates.io yet: a path dependency for now.

    Cargo.toml
    [dependencies.engine]
    path = "../engine"
    default-features = false
    features = ["physics", "fps"]
    
    [dependencies]
    bevy = { version = "=0.19.1", default-features = false }
    
    [features]
    # cargo run / cargo test keep every debug tool
    default = ["dev"]
    dev = ["engine/devtools", "engine/trace"]
  2. 02

    Write main.rs

    One call builds the window or headless app, CLI flags, data and asset folders, settings, the main menu and the pause menu. Add your game as a plugin and put its flow under AppState::InGame.

    src/main.rs
    use engine::prelude::*;
    
    fn main() {
        engine::app(
            GameInfo::new("orbs", "Orbs").tagline("Collect them all."),
        )
        .add_plugins(MyGamePlugin)
        .run();
    }
  3. 03

    Add your settings

    Your section is a serde struct. It is saved next to the core settings in settings.json, 0.6 s after the last change and on exit. A corrupt file is moved aside and defaults load.

    src/main.rs
    #[derive(Resource, Serialize, Deserialize, Default, Clone, PartialEq)]
    #[serde(default)]
    struct MySettings { difficulty: usize, assist: bool }
    
    fn main() {
        engine::app(GameInfo::new("id", "Title"))
            .add_plugins((engine::settings::SettingsPlugin::<MySettings>::default(), MyPlugin))
            .run();
    }
  4. 04

    Put them on screen

    Each FieldDef is a row on the generated settings screen: Toggle, Slider, Choice, Cards or Swatches, on the tab you name. Reset tab restores the defaults.

    courier/src/settings.rs
    app.register_setting(FieldDef {
        key: "assist",
        tab: "Gameplay",
        label: "Steering assist",
        hint: "More grip and calmer steering at speed. Off: the bike slides and drifts.",
        kind: FieldKind::Toggle,
        get: |w| b(s(w).assist),
        set: |w, v| s_mut(w).assist = v > 0.5,
    })
  5. 06

    Run it, test it

    Dev builds take engine flags such as --play and --headless. The control port lets a test or an agent press keys, read state and save screenshots from a hidden window.

    terminal
    cargo run --example minimal --features fps      # the smallest game: ground, cube, camera
    cargo run -- --play                               # skip the main menu (dev builds)
    cargo run -- --headless                           # no window, for tests
  6. 07

    Ship without the tools

    Build with --no-default-features. The debug tools live in the ashlar crates (control port, MCP, trace); the packaging script fails if any of them is still in the tree or the binary, and copies only the binary and assets.

    terminal
    scripts/package.sh --manifest Cargo.toml --package mygame --bin mygame \
        --features "physics fps" --assets assets
    cargo tree -p <pkg> --no-default-features --features "<ship features>" -e normal | grep ashlar
    # prints nothing