Last active
August 14, 2026 15:55
-
-
Save edhaase/98e28acc1e875c5adf26f6b05c7548f7 to your computer and use it in GitHub Desktop.
Bevy bsn examples
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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