scaffold-bevy-game
Use when starting a new Bevy game or fixing a Bevy project that compiles slowly, has fat code in main.rs, or has no CI — creates a lib+bin cargo project with fast dev builds, plugin composition, asset
SKILL.mdskills/game/scaffold-bevy-game/SKILL.md
GitHubScaffold Bevy Game
Target: Bevy 0.20, Rust edition 2024. Versions and the "newest Bevy unless a crate is behind" rule: stack-versions. Rationale per rule: scaffold-patterns.md.
1. Audit current state (change nothing)
cat .claude/stack-profile.md 2>/dev/null # languages, task_runner, ci, lint_format.rust, tests.layout
ls Cargo.toml src/lib.rs src/main.rs rust-toolchain.toml .cargo/config.toml assets .github/workflows 2>/dev/null
grep -nE '^bevy|^edition|rust-version|dynamic_linking|\[profile' Cargo.toml 2>/dev/null
grep -c "add_systems\|spawn(" src/main.rs 2>/dev/null # logic in main.rs = to extract
cargo --version; cargo nextest --version; ls justfile mise.toml Makefile 2>/dev/null
Read the profile first. Without it, detect: Cargo.toml → cargo; justfile → just; .github/ → github-actions. Ask only about the target platform (desktop only, or desktop + web) and only if the repo does not say — it changes the CI and profile output.
Third-party Bevy crates (physics, input): check each crate's bevy requirement now (see stack-versions). It decides the Bevy line.
2. Decide what to do
- No
Cargo.toml→ full scaffold (steps 4–7). - Project exists → apply only the delta: missing
[profile]tuning,devfeature, lib/bin split, CI,rust-toolchain.toml, lints. - All present and
cargo clippy --all-targets -- -D warningsis clean → say "already in place" and stop.
3. Detect track
- Desktop only → skip the wasm profile and CI job.
- Desktop + web → add
wasm-releaseprofile, thewasm32-unknown-unknowntarget, and the web CI job. - Workspace only when a second crate exists (dedicated server, tools, shared protocol). One game = one package with
lib.rs+main.rs.
4. Install only what's missing
rustup toolchain install stable --component clippy rustfmt # skip if present
cargo install cargo-nextest --locked # skip if `cargo nextest --version` works
rustup target add wasm32-unknown-unknown # web track only
cargo install wasm-bindgen-cli --locked # web track only; version must equal the wasm-bindgen crate in Cargo.lock
Linux build dependencies (Debian/Ubuntu): g++ pkg-config libx11-dev libasound2-dev libudev-dev libxkbcommon-x11-0.
macOS needs nothing extra. Bevy CLI is optional alpha (git install only) — never required here.
5. Generate the seams
cargo new --lib <name> then add src/main.rs. Canonical files (full text in scaffold-patterns.md):
Cargo.toml # lib+bin, dev feature, profiles, lints
rust-toolchain.toml # stable + clippy + rustfmt
rustfmt.toml # only if you deviate from defaults (usually: none)
justfile # run / check / test / web
.cargo/config.toml # Linux aarch64 / Windows linker only; macOS and x86_64 Linux need none
.github/workflows/ci.yml
assets/ # sprites/ audio/ fonts/ — see manage-bevy-assets
src/lib.rs # GamePlugin: composes feature plugins
src/main.rs # 6 lines: App + DefaultPlugins + GamePlugin
src/<feature>.rs # one plugin per feature — see structure-bevy-app
tests/ # headless integration tests — see test-bevy-systems
main.rs stays thin so the whole game is a library that tests can build without a window:
use bevy::prelude::*;
use my_game::GamePlugin;
fn main() -> AppExit {
App::new().add_plugins((DefaultPlugins, GamePlugin)).run()
}
// src/lib.rs
use bevy::prelude::*;
pub mod core;
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_plugins(core::CorePlugin);
}
}
6. Wire it
Cargo.toml:bevy = { version = "0.20", default-features = false, features = ["2d", "ui", "audio"] }(add"3d"for 3D). Featuredev = ["bevy/dev", "bevy/dynamic_linking"].[profile.dev] opt-level = 1,[profile.dev.package."*"] opt-level = 3.- Daily loop:
cargo run --features dev(hot reload + dynamic linking). Release and CI builds never passdev. [lints.clippy] too_many_arguments = "allow"andtype_complexity = "allow": Bevy systems take injected parameters.- CI: fmt → clippy
-D warnings→cargo nextest run(+cargo test --doc). Web track adds acargo check --target wasm32-unknown-unknownjob. - Commit
Cargo.lock. Ignoretarget/anddist/.
7. Verify
cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo nextest run
cargo run --features dev # a window opens; edit a file in assets/ and see it reload
cargo build --release # builds without the dev feature (no dylib in target/release/)
Expected: all three commands exit 0; ls target/release | grep -i bevy_dylib prints nothing.
Web track: cargo check --target wasm32-unknown-unknown --lib exits 0.
Also follow: engineering principles, security baseline (CI permissions, no secrets in assets), then structure-bevy-app.