Skip to content

Instantly share code, notes, and snippets.

@edhaase
Last active August 14, 2026 15:55
Show Gist options
  • Select an option

  • Save edhaase/98e28acc1e875c5adf26f6b05c7548f7 to your computer and use it in GitHub Desktop.

Select an option

Save edhaase/98e28acc1e875c5adf26f6b05c7548f7 to your computer and use it in GitHub Desktop.
Bevy bsn examples
pub(super) fn spawn_main_menu_container(mut commands: Commands, camera_query: Single<Entity, With<UiCamera>>) {
// 1. Fetch your specific UI camera entity
let camera_entity = camera_query.into_inner();
commands.spawn_scene(bsn! {
#MainMenuContainer
// 2. Pass the camera entity into the component
DespawnOnExit::<AppState>(AppState::Menu)
Node {
display: Display::Flex,
width: percent(100),
height: percent(100)
}
UIContainer::<MainMenuNavigation>::default()
})
.insert(UiTargetCamera(camera_entity))
;
}
pub(crate) fn spawn_main_menu_main(mut commands: Commands, query: Single<Entity, With<UIContainer<MainMenuNavigation>>>) {
let ce = query.into_inner();
commands.spawn_scene(bsn! {
ChildOf(ce)
DespawnOnExit::<MainMenuNavigation>(MainMenuNavigation::Main)
RenderLayers::layer(RENDER_LAYER_UI)
#MainMenu Node {
padding: UiRect::all(px(8)),
flex_direction: FlexDirection::Column,
align_items: AlignItems::FlexStart,
justify_content: JustifyContent::FlexEnd,
width: percent(100),
height: percent(100)
}
BackgroundColor(Color::srgba(0.2, 0.2, 0.2, 0.4))
Children [
// Sprite works
/* (
#Background
RenderLayers::layer(RENDER_LAYER_UI)
Sprite {
image: "embedded://client/asset/embed/textures/main_menu.png",
// image_mode: SpriteImageMode::Auto
image_mode: SpriteImageMode::Tiled {
tile_x: true,
tile_y: true,
stretch_value: 0.6
}
}
) */
(
#BtnMainMenuSP
button(LKey::MainMenuSp)
InitialFocusItem
BorderHighlightedAutoNav
on(move |_ev: On<Activate>| info!("clicked sp"))
),
(
#BtnMainMenuMP
button(LKey::MainMenuMp)
BorderHighlightedAutoNav
on(move |_ev: On<Activate>| info!("clicked mp"))
),
(
#BtnMainMenuSettings
button(LKey::MainMenuSettings)
BorderHighlightedAutoNav
on(move |_ev: On<Activate>, mut s: ResMut<NextState<MainMenuNavigation>>| NextState::set_if_neq(&mut s, MainMenuNavigation::Settings))
),
(
#BtnMainMenuExit
button(LKey::MainMenuQuit)
BorderHighlightedAutoNav
on(move |_ev: On<Activate>, mut commands: Commands| { commands.write_message(AppExit::Success); })
)
]
});
}
pub fn spawn_audio_settings(
mut commands: Commands,
query: Single<Entity, With<UIContainer<SettingsMenuNavigation>>>,
audio_settings: Res<AudioSettings>,
) {
let ce = query.into_inner();
info!("audio_settings.master_volume: {}", audio_settings.master);
// let slider_mv = slider(0., 1., 0.01, 2);
commands.spawn_scene(bsn! {
#AudioMenu
ChildOf(ce)
DespawnOnExit::<SettingsMenuNavigation>(SettingsMenuNavigation::Audio)
RenderLayers::layer(RENDER_LAYER_UI)
// Outer container
Node {
flex_direction: FlexDirection::Row,
width: percent(100),
height: percent(100)
}
Children [
// ScrollArea
(
#MenuScrollArea
Node {
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center,
justify_content: JustifyContent::FlexStart,
width: percent(100),
height: percent(100),
overflow: Overflow::scroll(),
// padding: UiRect::all(px(4)),
padding: UiRect::top(px(60)),
}
BackgroundColor(Color::srgb(0.3, 0.3, 0.3))
ScrollArea::default()
// Scrollable content
Children [
( Text("Master Volume") ),
(
#MasterVolumeSlider
slider(0., 1., 0.01, 2, audio_settings.master)
on(|ev: On<ValueChange<f32>>, mut audio: ResMut<AudioSettings>| audio.master = ev.value )
),
( Text("Music Volume") ),
(
#MusicVolumeSlider
slider(0., 1., 0.01, 2, audio_settings.music)
on(|ev: On<ValueChange<f32>>, mut audio: ResMut<AudioSettings>| audio.music = ev.value )
),
( Text("SFX Volume") ),
(
#SFXVolumeSlider
slider(0., 1., 0.01, 2, audio_settings.sfx)
on(|ev: On<ValueChange<f32>>, mut audio: ResMut<AudioSettings>| audio.sfx = ev.value )
),
( Text("Dialogue Volume") ),
(
#DialogueVolumeSlider
slider(0., 1., 0.01, 2, audio_settings.dialogue)
on(|ev: On<ValueChange<f32>>, mut audio: ResMut<AudioSettings>| audio.dialogue = ev.value )
),
]),
// Scrollbar
( Node {
// flex_direction: FlexDirection::Column,
min_width: px(16),
// height: percent(100),
justify_content: JustifyContent::FlexStart,
}
Scrollbar {
target: #MenuScrollArea,
orientation: ControlOrientation::Vertical,
min_thumb_length: 8.0
}
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)) // Darker track background
Children [
// The Scrollbar Thumb (The moving part)
(
OnHoverColor::new(MENU_HOVER_COLOR, Color::srgb(1.0, 0.2, 0.2)) // Darker track background
ScrollbarThumb {
border_radius: BorderRadius::all(px(4)),
border: { px(1).all() }
}
)
])
]
});
}
pub fn slider(start: f32, end: f32, step: f32, precision: i32, current: f32) -> impl Scene {
bsn! {
RenderLayers::layer(RENDER_LAYER_UI)
#Slider
Node {
display: Display::Flex,
flex_direction: FlexDirection::Column,
justify_content: JustifyContent::Center,
align_items: AlignItems::Stretch,
width: percent(30),
height: px(30.0),
flex_shrink: 0.0
}
OnHoverColor {
hover: MENU_HOVER_COLOR, normal: Color::NONE
}
Slider {
track_click: TrackClick::Snap,
orientation: SliderOrientation::Horizontal
}
SliderRange::new(start, end)
SliderPrecision(precision)
SliderStep(step)
SliderValue(current)
Children [
// THE VISUAL TRACK
(
Node {
display: Display::Flex,
position_type: PositionType::Absolute
width: percent(100),
height: px(6),
justify_content: JustifyContent::FlexStart,
align_items: AlignItems::Center
}
BackgroundColor(Color::srgb(0.1, 0.1, 0.1)) // Dark groove line
Children [
(
SliderThumb // Marker component to let Bevy know this is the handle
Node {
display: Display::Flex,
position_type: PositionType::Absolute, // Allows absolute shifting on the track
width: px(16),
height: px(24),
flex_shrink: 0.0
}
BackgroundColor(Color::srgb(0.9, 0.9, 0.9)) // Bright draggable block
)])
]
}
}
pub fn sample_scene(mut commands: Commands) {
commands.spawn_scene(bsn! {
DespawnOnExit::<AppState>(AppState::InGame)
// Requird to prevent an error with the children
Transform::default()
InheritedVisibility::default()
Children [
(
InfiniteGrid
RenderLayers::layer(0)
)
(
#Cube
Mesh3d(asset_value(Cuboid::new(1.0, 1.0, 1.0)))
MeshMaterial3d::<StandardMaterial>(asset_value(Color::srgb_u8(124, 144, 255)))
Transform::from_xyz(0.0, 0.5, 0.0)
RenderLayers::layer(0)
),
(
#CubeInterior
Mesh3d(asset_value(Cuboid::new(1.0, 1.0, 1.0)))
MeshMaterial3d::<StandardMaterial>(asset_value(Color::srgb_u8(124, 144, 255)))
Transform::from_xyz(3.0, 0.0, 0.0)
RenderLayers::layer(1)
),
(
PointLight {
shadow_maps_enabled: true,
}
Transform::from_xyz(4.0, 8.0, 4.0)
RenderLayers::layer(0)
),
(
#PLInterior
PointLight {
shadow_maps_enabled: true,
}
Transform::from_xyz(4.0, 8.0, 4.0)
RenderLayers::layer(1)
),
(
DirectionalLight {
// We should cast shadows
shadow_maps_enabled: true,
contact_shadows_enabled: true,
illuminance: 25000.0,
}
Transform::from_xyz(0.0,0.0,10.0)
RenderLayers::layer(0)
Orbit {
pivot: Vec3::ZERO,
speed: 0.5
}
LookAt(Vec3::ZERO)
SunDisk::EARTH
VolumetricLight
)
]
});
}
fn tab_group() -> impl Scene {
// set_if_neq(NextState::Pending)
bsn! {
RenderLayers::layer(RENDER_LAYER_UI)
// TabGroup::new(0)
Node {
position_type: PositionType::Absolute,
top: px(0), left: px(0), width: percent(100), height: px(50),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
flex_direction: FlexDirection::Row,
flex_shrink: 0.0
}
BackgroundColor(Color::srgb(0.1, 0.1, 0.1))
Children [
(
#TabSetMenuGameplay
Node { margin: px(4), width: percent(16) height: percent(100), justify_content: JustifyContent::Center, align_items: AlignItems::Center,
border: UiRect::all(px(1))
}
// Node { align_self: AlignSelf::Stretch, width: percent(100), height: percent(100), justify_content: JustifyContent::Center, align_items: AlignItems::Center }
// TabIndex(0)
BorderHighlightedAutoNav
Button
InitialFocusItem
OnHoverColor::new(MENU_HOVER_COLOR, Color::NONE)
Children [LocalizedText { key: LKey::SetMenuGameplay }]
on(move |_ev: On<Activate>, mut s: ResMut<NextState<SettingsMenuNavigation>>| NextState::set_if_neq(&mut s, SettingsMenuNavigation::Gameplay))
),
(
#TabSetMenuAudio
Node { margin: px(4), width: percent(16) height: percent(100), justify_content: JustifyContent::Center, align_items: AlignItems::Center }
// Node { align_self: AlignSelf::Stretch, width: percent(100), height: percent(100), justify_content: JustifyContent::Center, align_items: AlignItems::Center }
// TabIndex(1)
BorderHighlightedAutoNav
Button
OnHoverColor::new(MENU_HOVER_COLOR, Color::NONE)
Children [LocalizedText { key: LKey::SetMenuAudio }]
on(move |_ev: On<Activate>, mut s: ResMut<NextState<SettingsMenuNavigation>>| NextState::set_if_neq(&mut s, SettingsMenuNavigation::Audio))
),
(
#TabSetMenuVideo
Node { margin: px(4), width: percent(16) height: percent(100), justify_content: JustifyContent::Center, align_items: AlignItems::Center }
// Node { align_self: AlignSelf::Stretch, width: percent(100), height: percent(100), justify_content: JustifyContent::Center, align_items: AlignItems::Center }
// TabIndex(2)
BorderHighlightedAutoNav
Button
OnHoverColor::new(MENU_HOVER_COLOR, Color::NONE)
Children [LocalizedText { key: LKey::SetMenuVideo }]
on(move |_ev: On<Activate>, mut s: ResMut<NextState<SettingsMenuNavigation>>| NextState::set_if_neq(&mut s, SettingsMenuNavigation::Video))
),
(
#TabSetMenuControls
Node { margin: px(4), width: percent(16) height: percent(100), justify_content: JustifyContent::Center, align_items: AlignItems::Center }
// Node { align_self: AlignSelf::Stretch, width: percent(100), height: percent(100), justify_content: JustifyContent::Center, align_items: AlignItems::Center }
// TabIndex(3)
BorderHighlightedAutoNav
Button
OnHoverColor::new(MENU_HOVER_COLOR, Color::NONE)
Children [LocalizedText { key: LKey::SetMenuControls }]
on(move |_ev: On<Activate>, mut s: ResMut<NextState<SettingsMenuNavigation>>| NextState::set_if_neq(&mut s, SettingsMenuNavigation::Controls))
)
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment