Skip to content

Instantly share code, notes, and snippets.

@n1ght-hunter
Last active June 26, 2026 23:43
Show Gist options
  • Select an option

  • Save n1ght-hunter/6f6a8763dc7753d65ffca9558146e5c3 to your computer and use it in GitHub Desktop.

Select an option

Save n1ght-hunter/6f6a8763dc7753d65ffca9558146e5c3 to your computer and use it in GitHub Desktop.

BSN Cheatsheet

Syntax Reference

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.

Basics

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
}

Children

bsn! {
    Player
    Children [
        Sword,
        Shield,
    ]
}

// Custom relationship
bsn! {
    Player
    Inventory [ Apple, Potion ]
}

Scene Functions

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
    }
}

Scene Lists

// 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} ]
    }
}

Observers

bsn! {
    Button
    on(|_: On<Pointer<Press>>| { info!("pressed!"); })
    on(my_handler_system)
}

Implicit Into

bsn! {
    Name("player")          // &str → String
    Node { border: px(2) } // Val → UiRect::all
}

Entity References

bsn! {
    #Root
    Children [
        SelfRef(#Root)   // #Root usable anywhere in the same bsn! scope
    ]
}

// Graph structures
bsn_list! [
    (#A PointsTo(#B)),
    (#B PointsTo(#A)),
]

Enum Components

// 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.

Scene Components (@)

#[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
    }
}

Asset Templates

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. } }
});

Helper Functions

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,
    }))
}

Spawning API

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

Gotchas

// 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment