| Syntax |
Meaning |
TypeName |
Insert component with all fields defaulted. Also selects the #[default] enum variant. |
TypeName { field: val } |
Insert component with specific fields; unset fields still default — no ..default() needed. |
TypeName(val) |
Tuple / newtype constructor. val is implicitly .into()'d. |
{ expr } |
Arbitrary expression at component position — must implement Scene. |
field: { expr } |
Arbitrary expression inside a struct field. |
#Name |
Sets Name("Name") and declares an entity ref usable anywhere in the same bsn! scope. |
"path/to/asset" |
Loads an asset at spawn time via AssetServer. Field type must support path-based FromTemplate. |
:"path/to/asset" |
Same, but the resolved scene is cached — cheaper for repeated spawns. |
RelType [ a, b, ] |
Spawns related entities via RelType (e.g. Children). Parentheses per entry are optional. |
{ scene_expr } |
Inside RelType [ ]: inlines a Scene or SceneList value as entries. |
on(fn_or_closure) |
Attaches an entity observer. Can be a named system fn or inline closure. Multiple allowed. |
@TypeName { ... } |
Spawns a SceneComponent — inserts the component and applies its associated scene. |
@field: val |
Prop inside @SceneComponent { }. Passed to the scene fn; does not persist as a component field. |
base_scene() |
Composes another scene as a patch. Fields applied first; later entries overwrite per-field. |
template_value(expr) |
Wraps any Rust value as an inline template. Use for non-default enum variants or computed values. |
bsn! { Player } // all fields default
bsn! { Player { score: 10 } } // specific fields; rest default
bsn! { Player { score: {base + bonus} } } // expression in field position
bsn! {
Player { score: 10 }
Transform
Visibility
}
bsn! {
Player
Children [
Sword,
Shield,
]
}
// Custom relationship
bsn! {
Player
Inventory [ Apple, Potion ]
}
fn player() -> impl Scene {
bsn! { Player Children [ Sword ] }
}
// Static name — # is idiomatic and also declares an entity ref in scope
fn player_named() -> impl Scene {
bsn! { #Player Children [ Sword ] }
}
// Dynamic name — # requires a literal ident, so use Name(val) for runtime values
fn named_player(name: &str) -> impl Scene {
bsn! { Name(name) Player } // &str → String via implicit Into
}
// Compose scenes — last write wins per field
fn my_button() -> impl Scene {
bsn! {
button() // base scene
Node { height: px(50) } // patches Node on top
}
}
// bsn! = one entity bsn_list! = multiple sibling entities
fn items() -> impl SceneList {
bsn_list! [
(#ItemA Sword),
(#ItemB Shield),
]
}
// Pass a SceneList as a children argument
fn widget(children: impl SceneList) -> impl Scene {
bsn! {
Widget
Children [ {children} ]
}
}
bsn! {
Button
on(|_: On<Pointer<Press>>| { info!("pressed!"); })
on(my_handler_system)
}
bsn! {
Name("player") // &str → String
Node { border: px(2) } // Val → UiRect::all
}
bsn! {
#Root
Children [
SelfRef(#Root) // #Root usable anywhere in the same bsn! scope
]
}
// Graph structures
bsn_list! [
(#A PointsTo(#B)),
(#B PointsTo(#A)),
]
// Default variant — bare type name
ButtonVariant // → ButtonVariant::default() → #[default] variant
// Non-default variant — template_value wraps it inline
bsn! { template_value(ButtonVariant::Primary) }
// Non-default as a field value — always works with ::
bsn! { Foo { mode: BarVariant::Special } }
// Alternative: capture in a local, then use { }
let variant = ButtonVariant::Primary;
bsn! { { variant } }
{ ButtonVariant::Primary } at component position is a parse error — :: is not
valid inside { } wrappers at the top level. Use template_value() instead.
#[derive(SceneComponent, Default, Clone)]
struct Player { score: usize }
impl Player {
fn scene() -> impl Scene {
bsn! { Transform Children [ LeftHand, RightHand ] }
}
}
world.spawn_scene(bsn! { @Player { score: 10 } });
// With props
bsn! {
@Player {
@alignment: Alignment::Good, // prop — passed to scene fn
score: 10, // normal component field
}
}
bsn! { Sprite { image: "player.png" } } // path-based
bsn! { Mesh3d(asset_value(Cuboid::new(1., 1., 1.))) } // inline asset
commands.queue_spawn_scene(bsn! { // cached scene asset
:"player.bsn"
Transform { translation: Vec3 { x: 10. } }
});
| Function |
Returns |
Use |
px(f) |
Val::Px(f) |
Pixel shorthand; converts to UiRect::all via implicit Into on border/padding fields |
template_value(val) |
impl Template |
Wraps any value as an inline template — use for non-default enum variants |
asset_value(val) |
impl Template |
Registers a value into Assets<T> at spawn time, inserts the Handle<T> |
bsn! {
Node {
width: px(220.0),
padding: px(8.0),
}
template_value(ButtonVariant::Primary)
Mesh3d(asset_value(Cuboid::new(1., 1., 1.)))
MeshMaterial3d(asset_value(StandardMaterial {
base_color: Color::WHITE,
}))
}
| Situation |
Call |
| Spawn new entity |
commands.spawn_scene(scene) |
| Spawn, wait for asset deps |
commands.queue_spawn_scene(scene) |
| Spawn multiple root entities |
commands.spawn_scene_list(list) |
| Apply to existing entity |
entity_cmds.apply_scene(scene) |
| Apply to existing entity (deferred) |
entity_cmds.queue_apply_scene(scene) |
| Spawn list as children |
entity_cmds.queue_spawn_related_scenes::<Children>(list) |
| Apply scene as child (in with_children) |
spawner.spawn_empty().apply_scene(scene) |
// Scene function as a Bevy startup system
app.add_systems(Startup, level.spawn());
fn level() -> impl SceneList {
bsn_list![ Camera2d, Sprite { image: "bg.png" } ]
}
// Applying scenes inside with_children
parent.with_children(|ch| {
ch.spawn_empty().apply_scene(ok_button());
ch.spawn_empty().apply_scene(cancel_button());
});
// WRONG — :: inside { } at component level is a parse error
{ ButtonVariant::Normal }
// RIGHT — bare name calls Default, or use template_value for non-default
ButtonVariant
template_value(ButtonVariant::Primary)
// WRONG — { } inside Children [] is a sub-scene, not a component
Children [ ({ Text::new("hi") }) ]
// RIGHT
Children [ (Text("hi")) ]
// VALID — { } inside a struct field is fine
Player { score: {compute()} }
Macro failure symptom: when bsn! fails to parse, all types used inside it
show as "unused imports". Fix the parse error — the imports are not actually unused.