Bevy Forge 0.1.0: visual scene editor for Bevy 0.20 BSN
This commit is contained in:
@@ -0,0 +1,486 @@
|
||||
# BSN export
|
||||
|
||||
Bevy Forge's primary export target is **Bevy 0.20 BSN**: Rust source code written with the `bsn!`
|
||||
and `bsn_list!` macros from `bevy::scene`. This is the modern scene notation in Bevy 0.20 - not the
|
||||
legacy `DynamicScene` / `.scn.ron` format. The output is ordinary Rust that your game compiles; there
|
||||
is no runtime `.bsn` asset to load.
|
||||
|
||||
Related pages: [scene format](format.md), [architecture](architecture.md), [README](../README.md).
|
||||
|
||||
## Where the code comes from
|
||||
|
||||
- **Editor:** File > Export > BSN scene. The dialog shows a preview with the generated function name,
|
||||
lets you copy it to the clipboard, and can write it to a file (default `export/<function>.rs`).
|
||||
- **CLI:**
|
||||
|
||||
```bash
|
||||
cargo run -p bevy_forge_cli -- export examples/demo-project/scenes/garden.forge.ron \
|
||||
--format bsn -o /tmp/garden.rs
|
||||
```
|
||||
|
||||
Without `-o`, the code is printed to stdout. `--function <name>` sets the generated function name;
|
||||
the default is `scene`. The name must be a valid Rust identifier that is not a Rust keyword,
|
||||
otherwise the exporter returns an error.
|
||||
|
||||
Generated files begin with `use bevy::prelude::*;` (plus a macro import when needed). With
|
||||
`CodegenOptions::include_header` enabled - the editor's default - a short "generated by Bevy Forge"
|
||||
comment precedes the imports; the CLI exports without that header, so its stdout and `-o` files start
|
||||
directly with the import. Generated files end with exactly one newline. Generation is deterministic
|
||||
for a given scene and options.
|
||||
|
||||
## Output shape
|
||||
|
||||
One root scene:
|
||||
|
||||
```rust
|
||||
// Generated by Bevy Forge. Paste into a Bevy 0.20 project.
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub fn scene() -> impl Scene {
|
||||
bsn! {
|
||||
Name("Player")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { image: "textures/player.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(64.0, 64.0)) }, flip_x: false, flip_y: false }
|
||||
Children [
|
||||
Name("Hat")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 1.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { color: Color::srgba(1.0, 0.5, 0.2, 1.0), flip_x: false, flip_y: false }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Multiple roots use `bsn_list!` and return `impl SceneList`. Roots (and children) are separated by
|
||||
`--`:
|
||||
|
||||
```rust
|
||||
// Generated by Bevy Forge. Paste into a Bevy 0.20 project.
|
||||
use bevy::prelude::*;
|
||||
use bevy::scene::bsn_list;
|
||||
|
||||
pub fn scene() -> impl SceneList {
|
||||
bsn_list! {
|
||||
Name("Left")
|
||||
Transform {
|
||||
translation: Vec3::new(-2.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { color: Color::srgba(0.4, 0.7, 1.0, 1.0), flip_x: false, flip_y: false }
|
||||
--
|
||||
Name("Right")
|
||||
Transform {
|
||||
translation: Vec3::new(2.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Sprite { color: Color::srgba(1.0, 0.6, 0.4, 1.0), flip_x: false, flip_y: false }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Rules the generator follows:
|
||||
|
||||
- `bsn!` accepts exactly one root. Two roots inside one `bsn!` are a parse error; use `bsn_list!`.
|
||||
- `--` separates sibling scenes inside `bsn_list!` and `Children [ ... ]`. The editor's scene order is
|
||||
preserved.
|
||||
- Every entity emits `Name("<escaped display name>")` and a complete `Transform` block with all
|
||||
three fields. Display names are emitted as escaped Rust strings, never as `#Identifier` references.
|
||||
- Rotation is converted from stored XYZ Euler **degrees** to a quaternion:
|
||||
`Quat::from_euler(EulerRot::XYZ, 45.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians())`.
|
||||
The `f32` suffix is part of the generated output; it keeps the literal type unambiguous.
|
||||
- Components are emitted in stored order after `Transform`; children appear as a nested `Children`
|
||||
block.
|
||||
- Macro imports are normal `use` statements. Do not fully qualify the macros
|
||||
(`bevy::scene::bsn_list! { ... }` compiles but prints a build-time macro-root warning).
|
||||
|
||||
## Component templates
|
||||
|
||||
### Name
|
||||
|
||||
```rust
|
||||
Name("Player")
|
||||
```
|
||||
|
||||
The string is Rust-escaped. `Name` takes a string-like value; `Name(42)` does not compile.
|
||||
|
||||
### Transform
|
||||
|
||||
```rust
|
||||
Transform {
|
||||
translation: Vec3::new(1.0, 0.5, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 45.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
```
|
||||
|
||||
All three fields are always present. (Here `45.0f32.to_radians()` is 45 degrees.) BSN component patches
|
||||
do not support struct-update syntax: `Transform { ..default() }` fails with a type inference error,
|
||||
so the generator writes every field.
|
||||
|
||||
### Sprite
|
||||
|
||||
```rust
|
||||
Sprite { image: "textures/player.png", color: Color::srgba(1.0, 1.0, 1.0, 1.0), custom_size: { Some(Vec2::new(64.0, 32.0)) }, flip_x: false, flip_y: false }
|
||||
```
|
||||
|
||||
- `image` is omitted when the sprite has no image; `custom_size` is omitted when it is `None`.
|
||||
- `color` is always emitted.
|
||||
- `Some(...)` **must be braced** inside a BSN patch; bare `Some(..)` is a parse error. Bare `None`
|
||||
is accepted, but the generator simply omits optional fields instead.
|
||||
- For a solid-colour sprite the output is `Sprite { color: ..., flip_x: false, flip_y: false }`.
|
||||
|
||||
### Text2d
|
||||
|
||||
```rust
|
||||
Text2d("Hello, Bevy!")
|
||||
TextFont { font_size: FontSize::Px(32.0) }
|
||||
TextColor(Color::srgba(1.0, 1.0, 1.0, 1.0))
|
||||
```
|
||||
|
||||
Text is a Rust string literal; newlines and quotes are escaped normally. `font_size` is in pixels.
|
||||
|
||||
### Cameras
|
||||
|
||||
```rust
|
||||
Camera2d
|
||||
```
|
||||
|
||||
```rust
|
||||
Camera3d::default()
|
||||
```
|
||||
|
||||
`Camera2d` is emitted bare. `Camera3d` is a constructor call, not a bare identifier.
|
||||
|
||||
### Point light
|
||||
|
||||
```rust
|
||||
PointLight { color: Color::srgba(1.0, 0.8, 0.6, 1.0), intensity: 800.0, range: 20.0, shadow_maps_enabled: true }
|
||||
```
|
||||
|
||||
### Directional light
|
||||
|
||||
```rust
|
||||
DirectionalLight { color: Color::srgba(1.0, 0.95, 0.85, 1.0), illuminance: 10000.0, shadow_maps_enabled: true }
|
||||
```
|
||||
|
||||
**Negative literal caveat:** BSN patches do not accept a leading `-` in a direct field value. A
|
||||
negative number must be wrapped in a block and suffixed:
|
||||
`intensity: { -5.0f32 }`. Values produced by calls or paths (`Vec3::new(-1.0, 0.0, 0.0)`,
|
||||
`f32::NEG_INFINITY`) are fine. The generator uses the braced form when a direct literal is negative.
|
||||
|
||||
### Visibility
|
||||
|
||||
```rust
|
||||
Visibility::Inherited
|
||||
```
|
||||
|
||||
```rust
|
||||
Visibility::Visible
|
||||
```
|
||||
|
||||
```rust
|
||||
Visibility::Hidden
|
||||
```
|
||||
|
||||
An entity without a visibility component emits nothing; Bevy treats it as inherited.
|
||||
|
||||
### Mesh3d and materials
|
||||
|
||||
Primitive meshes and materials need asset handles. The generated function takes parameters for the
|
||||
asset collections the scene uses:
|
||||
|
||||
```rust
|
||||
pub fn scene(
|
||||
meshes: &mut Assets<Mesh>,
|
||||
materials: &mut Assets<StandardMaterial>,
|
||||
) -> impl Scene {
|
||||
bsn! {
|
||||
Name("Ground")
|
||||
Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 0.0),
|
||||
rotation: Quat::from_euler(EulerRot::XYZ, 0.0f32.to_radians(), 0.0f32.to_radians(), 0.0f32.to_radians()),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
}
|
||||
Mesh3d({ meshes.add(Cuboid::new(12.0, 0.2, 12.0)) })
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgba(0.35, 0.55, 0.3, 1.0),
|
||||
metallic: 0.0,
|
||||
perceptual_roughness: 0.9,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Primitive mesh handles must be **braced** because method calls are not accepted as direct BSN
|
||||
values: `Mesh3d({ meshes.add(..) })`, not `Mesh3d(meshes.add(..))`.
|
||||
- `MeshMaterial3d` requires the turbofish `::<StandardMaterial>`; without it the compiler reports
|
||||
missing generics.
|
||||
- `..default()` is accepted inside the raw Rust `StandardMaterial` literal (it is a constructor
|
||||
argument), but **not** inside a BSN component patch.
|
||||
- The generator includes `meshes` only when a primitive mesh exists, and `materials` whenever a
|
||||
`Mesh3d` exists. A scene with no meshes has no asset parameters.
|
||||
- Primitive constructors (each wrapped in `Mesh3d({ meshes.add(...) })`):
|
||||
- Cuboid: `Cuboid::new(x, y, z)`
|
||||
- Sphere: `Sphere::new(radius)`
|
||||
- Plane: `Plane3d::new(Vec3::Y, Vec2::new(width * 0.5, height * 0.5))` - the scene stores the full
|
||||
size, while Bevy's `Plane3d` takes half extents along X and Z
|
||||
- Cylinder: `Cylinder::new(radius, height)`
|
||||
- Capsule: `Capsule3d::new(radius, length)`
|
||||
|
||||
An asset mesh is addressed by path instead of an added handle:
|
||||
|
||||
```rust
|
||||
Mesh3d("models/robot.gltf#Mesh0/Primitive0")
|
||||
MeshMaterial3d::<StandardMaterial>({ materials.add(StandardMaterial {
|
||||
base_color: Color::srgba(0.8, 0.8, 0.85, 1.0),
|
||||
metallic: 0.1,
|
||||
perceptual_roughness: 0.4,
|
||||
unlit: false,
|
||||
..default()
|
||||
}) })
|
||||
```
|
||||
|
||||
An asset mesh still needs `materials`, but not `meshes`. The path is resolved by Bevy's `AssetServer`
|
||||
at spawn time; see [runtime asset caveats](#runtime-asset-caveats).
|
||||
|
||||
### Custom components
|
||||
|
||||
```rust
|
||||
forge_demo::Orbit {
|
||||
radius: 2.0,
|
||||
speed: { -0.5f32 },
|
||||
}
|
||||
```
|
||||
|
||||
A custom component exports as a struct literal: the stored `type_path`, then each `name: expr` pair.
|
||||
A custom component with zero fields is emitted as the bare type path:
|
||||
|
||||
```rust
|
||||
forge_demo::Marker
|
||||
```
|
||||
|
||||
Field expressions are copied from the scene file. The generator wraps an expression in
|
||||
`{ ... }` when it starts with `-` or is a bare enum-variant call such as `Some(..)`, because neither
|
||||
form is accepted as a direct BSN field value. Duplicate field names are rejected. In the example
|
||||
above the scene expression is `-0.5f32`, so the `f32` suffix survives bracing.
|
||||
|
||||
## Custom component requirements
|
||||
|
||||
The generated code must compile in the game crate that defines the component. Bevy 0.20's BSN macro
|
||||
needs a `FromTemplate` implementation, provided either by the blanket `Default + Clone + Unpin`
|
||||
implementation or by a derived `FromTemplate`.
|
||||
|
||||
| Component shape | Minimum derives |
|
||||
|---|---|
|
||||
| Simple data fields (`f32`, `Color`, `Vec3`, ...) | `#[derive(Component, Default, Clone)]` |
|
||||
| Unit marker | `#[derive(Component, Default, Clone)]` |
|
||||
| Fields like `Handle<Image>` or `Entity` | `#[derive(Component, FromTemplate)]` |
|
||||
| `Option<Handle<T>>` or other `#[template(built_in)]` fields | `#[derive(Component, FromTemplate)]` plus `#[template(built_in)]` on that field |
|
||||
|
||||
Notes:
|
||||
|
||||
- `FromTemplate` is in Bevy's prelude. The derive generates a companion template type, so it can be
|
||||
combined with `Default + Clone` in 0.20.
|
||||
- A component with `Handle<Image>` and only `Default + Clone` fails to compile because
|
||||
`Handle<Image>` does not convert from a string path through the blanket template; use
|
||||
`FromTemplate`.
|
||||
- Add `#[template(built_in)]` in your Rust source where a field type needs it. The editor does not
|
||||
introspect your types, so it cannot infer this option.
|
||||
- The exported `type_path` must be importable where the generated file is compiled. Use a qualified
|
||||
path such as `my_game::Health` if the type is not in scope.
|
||||
- The generated BSN binds no entity references (`#Child`), so custom components that link to other
|
||||
entities should use whatever handle/entity value your scene code provides; a raw `Entity` field
|
||||
expression is the author's responsibility.
|
||||
|
||||
Example component definitions:
|
||||
|
||||
```rust
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Default, Clone)]
|
||||
pub struct Health {
|
||||
pub current: f32,
|
||||
pub max: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, FromTemplate)]
|
||||
pub struct Icon {
|
||||
pub image: Handle<Image>,
|
||||
pub tint: Color,
|
||||
}
|
||||
```
|
||||
|
||||
## Pasting generated code into a Bevy app
|
||||
|
||||
1. Export the file, for example into `src/scenes/garden.rs` in your game.
|
||||
2. Declare the module and call the generated function from a startup system:
|
||||
|
||||
```rust
|
||||
use bevy::prelude::*;
|
||||
|
||||
mod scenes {
|
||||
pub mod garden;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, spawn_garden)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn spawn_garden(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
commands.spawn_scene(scenes::garden::scene(&mut meshes, &mut materials));
|
||||
}
|
||||
```
|
||||
|
||||
For a scene whose generated signature has no asset parameters:
|
||||
|
||||
```rust
|
||||
commands.spawn_scene(scenes::garden::scene());
|
||||
```
|
||||
|
||||
For multiple roots:
|
||||
|
||||
```rust
|
||||
commands.spawn_scene_list(scenes::garden::scene());
|
||||
```
|
||||
|
||||
- `spawn_scene`, `spawn_scene_list` and the related traits are in Bevy's prelude.
|
||||
- Matching the generated parameter list is required: passing unused asset collections is harmless,
|
||||
but omitting a required one does not compile.
|
||||
- On `World` (for example in exclusive systems or tests), use
|
||||
`world.spawn_scene(scene)` or `world.spawn_scene_list(scene)`; `spawn_scene_list` returns a
|
||||
`Result<Vec<Entity>, _>`.
|
||||
- `commands.queue_spawn_scene(..)` (and the list variant) defers the spawn. Handles created by
|
||||
`meshes.add` / `materials.add` are evaluated when `scene()` is called and are owned by the scene,
|
||||
so deferred spawning is safe.
|
||||
- Use `DefaultPlugins` or the specific plugins your components need (`ScenePlugin`, `SpritePlugin`,
|
||||
`PbrPlugin`, ...). A headless test harness needs at least task pool, asset and scene plugins.
|
||||
|
||||
## Runtime asset caveats
|
||||
|
||||
- **Primitives and inline materials work immediately.** `meshes.add(Cuboid::new(..))` and
|
||||
`materials.add(StandardMaterial { .. })` create values when `scene()` runs; the entity renders as
|
||||
soon as it is spawned and the asset is registered.
|
||||
- **Sprite images and asset meshes are path handles.** A string like `"textures/player.png"` becomes
|
||||
a path handled by `AssetServer` at spawn time. If the file is missing, or the relevant asset loader
|
||||
is not enabled in your game (`bevy_gltf` for `.gltf`, for example), the handle silently stays
|
||||
`Loading` or `Failed` - spawning does not error.
|
||||
- **Material asset paths need a loader.** `MeshMaterial3d::<StandardMaterial>("materials/cube.toml")`
|
||||
compiles, but unless your project registers a `StandardMaterial` asset loader it never resolves.
|
||||
This is why Forge emits inline material values instead of material asset paths. Do not replace the
|
||||
generated material expression with a bare path and expect it to render.
|
||||
- **No runtime BSN scene assets.** The generated code is compiled into your crate. Bevy's scene
|
||||
asset/caching features are not used by this exporter.
|
||||
|
||||
## Determinism and export errors
|
||||
|
||||
`ExportFormat::generate` is pure and deterministic. It returns errors instead of panicking:
|
||||
|
||||
| Error | Cause |
|
||||
|---|---|
|
||||
| `EmptyScene` | The scene has no root entities (including an entirely empty scene); there is nothing to generate. |
|
||||
| `InvalidIdentifier` | The function name is not a valid Rust identifier, or is a Rust keyword. |
|
||||
| `NonFinite` | A transform, material or light value is `NaN` or infinite. |
|
||||
| `InvalidCustomComponent` | A custom type path or field name is invalid, or a field expression is empty. The BSN generator additionally rejects duplicate field names. |
|
||||
|
||||
The CLI exits `1` for these codegen errors and `2` for I/O, RON parse or usage errors. The editor
|
||||
shows the error in the export dialog instead of writing a file.
|
||||
|
||||
## Troubleshooting compile errors
|
||||
|
||||
| Compiler message | Cause | Fix |
|
||||
|---|---|---|
|
||||
| `error: Unexpected input after function name` | Method call used as a direct BSN value, e.g. `Mesh3d(meshes.add(..))` | Brace it: `Mesh3d({ meshes.add(..) })` |
|
||||
| `error[E0107]: missing generics for struct MeshMaterial3d` | Missing turbofish | Use `MeshMaterial3d::<StandardMaterial>(...)` |
|
||||
| `error: expected type, found variant 'Some'` | Unbraced `Some(..)` in a patch | Use `custom_size: { Some(Vec2::new(..)) }` |
|
||||
| `error: expected identifier` at a negative number | Negative direct field literal | Use `intensity: { -5.0f32 }` (or a call/path value) |
|
||||
| `error: expected f32, found integer` | Integer literal in an `f32` field | Write `10.0`, not `10` |
|
||||
| `error[E0282]: type annotations needed` | `..default()` or `..Default::default()` inside a BSN component patch | Not supported; write the fields or omit them to keep defaults. `..default()` is only valid inside raw Rust literals in `{}` arguments |
|
||||
| `error: expected HashedStr, found integer` | `Name(42)` | `Name` takes a string: `Name("42")` |
|
||||
| `error: unexpected token` at `--` inside `bsn!` | Multiple roots in a single-root macro | Use `bsn_list!` and return `impl SceneList` |
|
||||
| `cannot find macro bsn_list` | Missing import | Add `use bevy::scene::bsn_list;` (or `use bevy::prelude::*;`) |
|
||||
| `Unknown macro root!` printed to stderr | Fully qualified macro path | Import the macro with `use` instead |
|
||||
| `warning: unused variable: meshes` | Asset parameter present but unused (e.g. hand-edited) | Let the exporter include only needed parameters, or allow the warning |
|
||||
| `warning: float_literal_f32_fallback` | Unsuffixed negative float in a bracing block | Use an `f32` suffix: `{ -5.0f32 }` |
|
||||
| Duplicate sibling entities visually merged | Missing `--` between scenes | Separate sibling patches with `--` |
|
||||
|
||||
If an exported scene fails to compile, first check that the pasted file is in a crate that depends on
|
||||
Bevy 0.20 and that custom component types and their derives are present. Then compare against the
|
||||
templates above. The repository's `compat/bsn-smoke` crate compiles representative generated output
|
||||
against real Bevy 0.20 and is the reference for supported syntax.
|
||||
|
||||
## Imperative Rust alternative
|
||||
|
||||
`forge export --format rust` (or File > Export > Rust spawn code) produces a function that spawns the
|
||||
scene immediately with `Commands`. It targets the same Bevy 0.20 API surface but does not use BSN.
|
||||
|
||||
```rust
|
||||
// Generated by Bevy Forge. Paste into a Bevy 0.20 project.
|
||||
// Imperative `Commands::spawn` code: call this function from a startup system.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub fn scene(
|
||||
commands: &mut Commands,
|
||||
asset_server: &AssetServer,
|
||||
) {
|
||||
commands.spawn((
|
||||
Name::new("Player"),
|
||||
Transform::from_xyz(0.0, 0.0, 0.0)
|
||||
.with_rotation(Quat::from_euler(EulerRot::XYZ, 0.0_f32.to_radians(), 0.0_f32.to_radians(), 0.0_f32.to_radians()))
|
||||
.with_scale(Vec3::new(1.0, 1.0, 1.0)),
|
||||
Sprite { image: asset_server.load("textures/player.png"), color: Color::srgba(1.0, 1.0, 1.0, 1.0), flip_x: false, flip_y: false, ..default() },
|
||||
)).with_children(|parent| {
|
||||
parent.spawn((
|
||||
Name::new("Hat"),
|
||||
Transform::from_xyz(0.0, 1.0, 0.0)
|
||||
.with_rotation(Quat::from_euler(EulerRot::XYZ, 0.0_f32.to_radians(), 0.0_f32.to_radians(), 0.0_f32.to_radians()))
|
||||
.with_scale(Vec3::new(1.0, 1.0, 1.0)),
|
||||
Sprite { color: Color::srgba(1.0, 0.5, 0.2, 1.0), flip_x: false, flip_y: false, ..default() },
|
||||
));
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Usage in a system:
|
||||
|
||||
```rust
|
||||
fn spawn_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
scene(&mut commands, &asset_server);
|
||||
}
|
||||
```
|
||||
|
||||
Rules for the imperative generator:
|
||||
|
||||
- `commands` is always a parameter. `asset_server` appears only when a sprite image or asset mesh is
|
||||
used. `meshes` and `materials` are both emitted whenever the scene contains any `Mesh3d`
|
||||
(primitive or asset), because the mesh handle and the inline material are created at the same
|
||||
time.
|
||||
- Each entity spawns a tuple of `Name::new(..)`, a `Transform`, then its components. Omitted `Sprite`
|
||||
fields use `..default()`.
|
||||
- Hierarchy uses `.with_children(|parent| { ... })`; sibling order is preserved.
|
||||
- Primitive meshes and materials use the same constructors as BSN. Text2d uses `Text2d::new`,
|
||||
`TextFont { font_size: FontSize::Px(..), ..default() }` and `TextColor(..)`.
|
||||
- Custom components are emitted as struct literals; the type must be imported where the generated
|
||||
code compiles.
|
||||
- Empty scenes are an error, exactly as for BSN.
|
||||
|
||||
The imperative form is useful when a project cannot use BSN macros or when code must run against an
|
||||
API where a scene is spawned imperatively. BSN remains the primary, recommended export.
|
||||
Reference in New Issue
Block a user