Skip to main content

game_menu/
game_menu.rs

1//! This example will display a simple menu using Bevy UI where you can start a new game,
2//! change some settings or quit. There is no actual game, it will just display the current
3//! settings for 5 seconds before going back to the menu.
4
5use bevy::prelude::*;
6
7const TEXT_COLOR: Color = Color::srgb(0.9, 0.9, 0.9);
8
9// Enum that will be used as a global state for the game
10#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
11enum GameState {
12    #[default]
13    Splash,
14    Menu,
15    Game,
16}
17
18// One of the two settings that can be set through the menu. It will be a resource in the app
19#[derive(Resource, Debug, PartialEq, Eq, Clone, Copy)]
20enum DisplayQuality {
21    Low,
22    Medium,
23    High,
24}
25
26#[derive(Component)]
27struct Setting<T>(T);
28
29// One of the two settings that can be set through the menu. It will be a resource in the app
30#[derive(Resource, Debug, PartialEq, Eq, Clone, Copy)]
31struct Volume(u32);
32
33fn main() {
34    App::new()
35        .add_plugins(DefaultPlugins)
36        // Insert as resource the initial value for the settings resources
37        .insert_resource(DisplayQuality::Medium)
38        .insert_resource(Volume(7))
39        // Declare the game state, whose starting value is determined by the `Default` trait
40        .init_state::<GameState>()
41        .add_systems(Startup, setup)
42        // Adds the plugins for each state
43        .add_plugins((splash::splash_plugin, menu::menu_plugin, game::game_plugin))
44        .run();
45}
46
47fn setup(mut commands: Commands) {
48    commands.spawn(Camera2d);
49}
50
51mod splash {
52    use bevy::prelude::*;
53
54    use super::GameState;
55
56    // This plugin will display a splash screen with Bevy logo for 1 second before switching to the menu
57    pub fn splash_plugin(app: &mut App) {
58        // As this plugin is managing the splash screen, it will focus on the state `GameState::Splash`
59        app
60            // When entering the state, spawn everything needed for this screen
61            .add_systems(OnEnter(GameState::Splash), splash_setup)
62            // While in this state, run the `countdown` system
63            .add_systems(Update, countdown.run_if(in_state(GameState::Splash)));
64    }
65
66    // Tag component used to tag entities added on the splash screen
67    #[derive(Component)]
68    struct OnSplashScreen;
69
70    // Newtype to use a `Timer` for this screen as a resource
71    #[derive(Resource, Deref, DerefMut)]
72    struct SplashTimer(Timer);
73
74    fn splash_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
75        let icon = asset_server.load("branding/icon.png");
76        // Display the logo
77        commands.spawn((
78            // This entity will be despawned when exiting the state
79            DespawnOnExit(GameState::Splash),
80            Node {
81                align_items: AlignItems::Center,
82                justify_content: JustifyContent::Center,
83                width: percent(100),
84                height: percent(100),
85                ..default()
86            },
87            OnSplashScreen,
88            children![(
89                ImageNode::new(icon),
90                Node {
91                    // This will set the logo to be 200px wide, and auto adjust its height
92                    width: px(200),
93                    ..default()
94                },
95            )],
96        ));
97        // Insert the timer as a resource
98        commands.insert_resource(SplashTimer(Timer::from_seconds(1.0, TimerMode::Once)));
99    }
100
101    // Tick the timer, and change state when finished
102    fn countdown(
103        mut game_state: ResMut<NextState<GameState>>,
104        time: Res<Time>,
105        mut timer: ResMut<SplashTimer>,
106    ) {
107        if timer.tick(time.delta()).is_finished() {
108            game_state.set(GameState::Menu);
109        }
110    }
111}
112
113mod game {
114    use bevy::{
115        color::palettes::basic::{BLUE, LIME},
116        prelude::*,
117    };
118
119    use super::{DisplayQuality, GameState, Volume, TEXT_COLOR};
120
121    // This plugin will contain the game. In this case, it's just be a screen that will
122    // display the current settings for 5 seconds before returning to the menu
123    pub fn game_plugin(app: &mut App) {
124        app.add_systems(OnEnter(GameState::Game), game_setup)
125            .add_systems(Update, game.run_if(in_state(GameState::Game)));
126    }
127
128    // Tag component used to tag entities added on the game screen
129    #[derive(Component)]
130    struct OnGameScreen;
131
132    #[derive(Resource, Deref, DerefMut)]
133    struct GameTimer(Timer);
134
135    fn game_setup(
136        mut commands: Commands,
137        display_quality: Res<DisplayQuality>,
138        volume: Res<Volume>,
139    ) {
140        commands.spawn((
141            DespawnOnExit(GameState::Game),
142            Node {
143                width: percent(100),
144                height: percent(100),
145                // center children
146                align_items: AlignItems::Center,
147                justify_content: JustifyContent::Center,
148                ..default()
149            },
150            OnGameScreen,
151            children![(
152                Node {
153                    // This will display its children in a column, from top to bottom
154                    flex_direction: FlexDirection::Column,
155                    // `align_items` will align children on the cross axis. Here the main axis is
156                    // vertical (column), so the cross axis is horizontal. This will center the
157                    // children
158                    align_items: AlignItems::Center,
159                    ..default()
160                },
161                BackgroundColor(Color::BLACK),
162                children![
163                    (
164                        Text::new("Will be back to the menu shortly..."),
165                        TextFont {
166                            font_size: FontSize::Px(67.0),
167                            ..default()
168                        },
169                        TextColor(TEXT_COLOR),
170                        Node {
171                            margin: UiRect::all(px(50)),
172                            ..default()
173                        },
174                    ),
175                    (
176                        Text::default(),
177                        Node {
178                            margin: UiRect::all(px(50)),
179                            ..default()
180                        },
181                        children![
182                            (
183                                TextSpan(format!("quality: {:?}", *display_quality)),
184                                TextFont {
185                                    font_size: FontSize::Px(50.0),
186                                    ..default()
187                                },
188                                TextColor(BLUE.into()),
189                            ),
190                            (
191                                TextSpan::new(" - "),
192                                TextFont {
193                                    font_size: FontSize::Px(50.0),
194                                    ..default()
195                                },
196                                TextColor(TEXT_COLOR),
197                            ),
198                            (
199                                TextSpan(format!("volume: {:?}", *volume)),
200                                TextFont {
201                                    font_size: FontSize::Px(50.0),
202                                    ..default()
203                                },
204                                TextColor(LIME.into()),
205                            ),
206                        ]
207                    ),
208                ]
209            )],
210        ));
211        // Spawn a 5 seconds timer to trigger going back to the menu
212        commands.insert_resource(GameTimer(Timer::from_seconds(5.0, TimerMode::Once)));
213    }
214
215    // Tick the timer, and change state when finished
216    fn game(
217        time: Res<Time>,
218        mut game_state: ResMut<NextState<GameState>>,
219        mut timer: ResMut<GameTimer>,
220    ) {
221        if timer.tick(time.delta()).is_finished() {
222            game_state.set(GameState::Menu);
223        }
224    }
225}
226
227mod menu {
228    use bevy::{
229        app::AppExit,
230        color::palettes::css::CRIMSON,
231        ecs::component::Mutable,
232        ecs::spawn::{SpawnIter, SpawnWith},
233        picking::prelude::*,
234        prelude::*,
235        ui::Checked,
236        ui_widgets::{Activate, ActivateOnPress, Button, RadioButton, RadioGroup, ValueChange},
237    };
238
239    use super::{DisplayQuality, GameState, Setting, Volume, TEXT_COLOR};
240
241    // This plugin manages the menu, with 5 different screens:
242    // - a main menu with "New Game", "Settings", "Quit"
243    // - a settings menu with two submenus and a back button
244    // - two settings screen with a setting that can be set and a back button
245    pub fn menu_plugin(app: &mut App) {
246        app
247            // At start, the menu is not enabled. This will be changed in `menu_setup` when
248            // entering the `GameState::Menu` state.
249            // Current screen in the menu is handled by an independent state from `GameState`
250            .init_state::<MenuState>()
251            .add_systems(OnEnter(GameState::Menu), menu_setup)
252            // Systems to handle the main menu screen
253            .add_systems(OnEnter(MenuState::Main), main_menu_setup)
254            // Systems to handle the settings menu screen
255            .add_systems(OnEnter(MenuState::Settings), settings_menu_setup)
256            // Systems to handle the display settings screen
257            .add_systems(
258                OnEnter(MenuState::SettingsDisplay),
259                display_settings_menu_setup,
260            )
261            .add_observer(
262                on_value_change_setting_radio::<DisplayQuality>
263                    .run_if(in_state(MenuState::SettingsDisplay)),
264            )
265            // Systems to handle the sound settings screen
266            .add_systems(OnEnter(MenuState::SettingsSound), sound_settings_menu_setup)
267            .add_observer(
268                on_value_change_setting_radio::<Volume>.run_if(in_state(MenuState::SettingsSound)),
269            )
270            // Observer that handles navigation within the app upon pressing certain buttons.
271            .add_observer(on_button_activate_update_states)
272            // Observers for button styling (regular buttons and radio buttons)
273            .add_observer(on_button_over_style)
274            .add_observer(on_button_out_style)
275            .add_observer(on_radio_button_press_style)
276            .add_observer(on_radio_button_release_style);
277    }
278
279    // State used for the current menu screen
280    #[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
281    enum MenuState {
282        Main,
283        Settings,
284        SettingsDisplay,
285        SettingsSound,
286        #[default]
287        Disabled,
288    }
289
290    // Tag component used to tag entities added on the main menu screen
291    #[derive(Component)]
292    struct OnMainMenuScreen;
293
294    // Tag component used to tag entities added on the settings menu screen
295    #[derive(Component)]
296    struct OnSettingsMenuScreen;
297
298    // Tag component used to tag entities added on the display settings menu screen
299    #[derive(Component)]
300    struct OnDisplaySettingsMenuScreen;
301
302    // Tag component used to tag entities added on the sound settings menu screen
303    #[derive(Component)]
304    struct OnSoundSettingsMenuScreen;
305
306    const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
307    const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
308    const HOVERED_PRESSED_BUTTON: Color = Color::srgb(0.25, 0.65, 0.25);
309    const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
310
311    // All actions that can be triggered from a button click
312    #[derive(Component)]
313    enum MenuButtonAction {
314        Play,
315        Settings,
316        SettingsDisplay,
317        SettingsSound,
318        BackToMainMenu,
319        BackToSettings,
320        Quit,
321    }
322
323    /// Observer that changes the background color to hovered over buttons / radio buttons to the correct color.
324    fn on_button_over_style(
325        event: On<PointerOver>,
326        mut button_q: Query<&mut BackgroundColor, (With<Button>, Without<RadioButton>)>,
327        mut radio_button_q: Query<
328            (&mut BackgroundColor, Has<Checked>),
329            (With<RadioButton>, Without<Button>),
330        >,
331    ) {
332        if let Ok(mut background_color) = button_q.get_mut(event.entity) {
333            *background_color = HOVERED_BUTTON.into();
334        }
335        if let Ok((mut background_color, is_checked)) = radio_button_q.get_mut(event.entity) {
336            if is_checked {
337                *background_color = HOVERED_PRESSED_BUTTON.into();
338            } else {
339                *background_color = HOVERED_BUTTON.into();
340            }
341        }
342    }
343
344    /// Observer that returns the normal background color to buttons / radio buttons that are hovered out of.
345    fn on_button_out_style(
346        event: On<PointerOut>,
347        mut bg_q: Query<&mut BackgroundColor, (With<Button>, Without<RadioButton>)>,
348        mut radio_button_q: Query<
349            (&mut BackgroundColor, Has<Checked>),
350            (With<RadioButton>, Without<Button>),
351        >,
352    ) {
353        if let Ok(mut background_color) = bg_q.get_mut(event.entity) {
354            *background_color = NORMAL_BUTTON.into();
355        }
356        if let Ok((mut background_color, is_checked)) = radio_button_q.get_mut(event.entity) {
357            if is_checked {
358                *background_color = PRESSED_BUTTON.into();
359            } else {
360                *background_color = NORMAL_BUTTON.into();
361            }
362        }
363    }
364
365    /// Observer that changes the background color of radio buttons that are pressed
366    fn on_radio_button_press_style(
367        event: On<PointerPress>,
368        mut radio_button_q: Query<&mut BackgroundColor, With<RadioButton>>,
369    ) {
370        if let Ok(mut background_color) = radio_button_q.get_mut(event.entity) {
371            *background_color = PRESSED_BUTTON.into();
372        }
373    }
374
375    /// Observer that changes the background color of radio buttons that are released
376    fn on_radio_button_release_style(
377        event: On<PointerRelease>,
378        mut radio_button_q: Query<(&mut BackgroundColor, Has<Checked>), With<RadioButton>>,
379    ) {
380        if let Ok((mut background_color, is_checked)) = radio_button_q.get_mut(event.entity) {
381            if is_checked {
382                *background_color = HOVERED_PRESSED_BUTTON.into();
383            } else {
384                *background_color = HOVERED_BUTTON.into();
385            }
386        }
387    }
388
389    // This system updates the settings when a new value for a setting is selected, and marks
390    // the radio button as the one currently selected
391    fn on_value_change_setting_radio<
392        T: Resource<Mutability = Mutable> + Component + PartialEq + Copy,
393    >(
394        event: On<ValueChange<Entity>>,
395        new_setting_query: Query<(&Setting<T>, Entity), (With<RadioButton>, Without<Checked>)>,
396        previous_query: Single<(Entity, &mut BackgroundColor), (With<RadioButton>, With<Checked>)>,
397        mut commands: Commands,
398        mut setting: ResMut<T>,
399    ) {
400        if let Ok((radio_setting, entity)) = new_setting_query.get(event.value)
401            && *setting != radio_setting.0
402        {
403            let (previous, mut previous_button_color) = previous_query.into_inner();
404            *previous_button_color = NORMAL_BUTTON.into();
405            commands.entity(previous).remove::<Checked>();
406            commands.entity(entity).insert(Checked);
407            *setting = radio_setting.0;
408        }
409    }
410
411    fn menu_setup(mut menu_state: ResMut<NextState<MenuState>>) {
412        menu_state.set(MenuState::Main);
413    }
414
415    fn main_menu_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
416        // Common style for all buttons on the screen
417        let button_node = Node {
418            width: px(300),
419            height: px(65),
420            margin: UiRect::all(px(20)),
421            justify_content: JustifyContent::Center,
422            align_items: AlignItems::Center,
423            ..default()
424        };
425        let button_icon_node = Node {
426            width: px(30),
427            // This takes the icons out of the flexbox flow, to be positioned exactly
428            position_type: PositionType::Absolute,
429            // The icon will be close to the left border of the button
430            left: px(10),
431            ..default()
432        };
433        let button_text_font = TextFont {
434            font_size: FontSize::Px(33.0),
435            ..default()
436        };
437
438        let right_icon = asset_server.load("textures/Game Icons/right.png");
439        let wrench_icon = asset_server.load("textures/Game Icons/wrench.png");
440        let exit_icon = asset_server.load("textures/Game Icons/exitRight.png");
441
442        commands.spawn((
443            DespawnOnExit(MenuState::Main),
444            Node {
445                width: percent(100),
446                height: percent(100),
447                align_items: AlignItems::Center,
448                justify_content: JustifyContent::Center,
449                ..default()
450            },
451            OnMainMenuScreen,
452            children![(
453                Node {
454                    flex_direction: FlexDirection::Column,
455                    align_items: AlignItems::Center,
456                    ..default()
457                },
458                BackgroundColor(CRIMSON.into()),
459                children![
460                    // Display the game name
461                    (
462                        Text::new("Bevy Game Menu UI"),
463                        TextFont {
464                            font_size: FontSize::Px(67.0),
465                            ..default()
466                        },
467                        TextColor(TEXT_COLOR),
468                        Node {
469                            margin: UiRect::all(px(50)),
470                            ..default()
471                        },
472                    ),
473                    // Display three buttons for each action available from the main menu:
474                    // - new game
475                    // - settings
476                    // - quit
477                    (
478                        Button,
479                        ActivateOnPress,
480                        button_node.clone(),
481                        BackgroundColor(NORMAL_BUTTON),
482                        MenuButtonAction::Play,
483                        children![
484                            (ImageNode::new(right_icon), button_icon_node.clone()),
485                            (
486                                Text::new("New Game"),
487                                button_text_font.clone(),
488                                TextColor(TEXT_COLOR),
489                            ),
490                        ]
491                    ),
492                    (
493                        Button,
494                        ActivateOnPress,
495                        button_node.clone(),
496                        BackgroundColor(NORMAL_BUTTON),
497                        MenuButtonAction::Settings,
498                        children![
499                            (ImageNode::new(wrench_icon), button_icon_node.clone()),
500                            (
501                                Text::new("Settings"),
502                                button_text_font.clone(),
503                                TextColor(TEXT_COLOR),
504                            ),
505                        ]
506                    ),
507                    (
508                        Button,
509                        ActivateOnPress,
510                        button_node,
511                        BackgroundColor(NORMAL_BUTTON),
512                        MenuButtonAction::Quit,
513                        children![
514                            (ImageNode::new(exit_icon), button_icon_node),
515                            (Text::new("Quit"), button_text_font, TextColor(TEXT_COLOR),),
516                        ]
517                    ),
518                ]
519            )],
520        ));
521    }
522
523    fn settings_menu_setup(mut commands: Commands) {
524        let button_node = Node {
525            width: px(200),
526            height: px(65),
527            margin: UiRect::all(px(20)),
528            justify_content: JustifyContent::Center,
529            align_items: AlignItems::Center,
530            ..default()
531        };
532
533        let button_text_style = (
534            TextFont {
535                font_size: FontSize::Px(33.0),
536                ..default()
537            },
538            TextColor(TEXT_COLOR),
539        );
540
541        commands.spawn((
542            DespawnOnExit(MenuState::Settings),
543            Node {
544                width: percent(100),
545                height: percent(100),
546                align_items: AlignItems::Center,
547                justify_content: JustifyContent::Center,
548                ..default()
549            },
550            OnSettingsMenuScreen,
551            children![(
552                Node {
553                    flex_direction: FlexDirection::Column,
554                    align_items: AlignItems::Center,
555                    ..default()
556                },
557                BackgroundColor(CRIMSON.into()),
558                Children::spawn(SpawnIter(
559                    [
560                        (MenuButtonAction::SettingsDisplay, "Display"),
561                        (MenuButtonAction::SettingsSound, "Sound"),
562                        (MenuButtonAction::BackToMainMenu, "Back"),
563                    ]
564                    .into_iter()
565                    .map(move |(action, text)| {
566                        (
567                            Button,
568                            ActivateOnPress,
569                            button_node.clone(),
570                            BackgroundColor(NORMAL_BUTTON),
571                            action,
572                            children![(Text::new(text), button_text_style.clone())],
573                        )
574                    })
575                ))
576            )],
577        ));
578    }
579
580    fn display_settings_menu_setup(mut commands: Commands, display_quality: Res<DisplayQuality>) {
581        fn button_node() -> Node {
582            Node {
583                width: px(200),
584                height: px(65),
585                margin: UiRect::all(px(20)),
586                justify_content: JustifyContent::Center,
587                align_items: AlignItems::Center,
588                ..default()
589            }
590        }
591        fn button_text_style() -> impl Bundle {
592            (
593                TextFont {
594                    font_size: FontSize::Px(33.0),
595                    ..default()
596                },
597                TextColor(TEXT_COLOR),
598            )
599        }
600
601        let display_quality = *display_quality;
602        commands.spawn((
603            DespawnOnExit(MenuState::SettingsDisplay),
604            Node {
605                width: percent(100),
606                height: percent(100),
607                align_items: AlignItems::Center,
608                justify_content: JustifyContent::Center,
609                ..default()
610            },
611            OnDisplaySettingsMenuScreen,
612            children![(
613                Node {
614                    flex_direction: FlexDirection::Column,
615                    align_items: AlignItems::Center,
616                    ..default()
617                },
618                BackgroundColor(CRIMSON.into()),
619                children![
620                    // Create a new `Node`, this time not setting its `flex_direction`. It will
621                    // use the default value, `FlexDirection::Row`, from left to right.
622                    // Because the setting can only be one of three values, a RadioGroup
623                    // is used, with each individual value as a RadioButton
624                    (
625                        RadioGroup,
626                        Node {
627                            align_items: AlignItems::Center,
628                            ..default()
629                        },
630                        BackgroundColor(CRIMSON.into()),
631                        Children::spawn((
632                            // Display a label for the current setting
633                            Spawn((Text::new("Display Quality"), button_text_style())),
634                            SpawnWith(move |parent: &mut ChildSpawner| {
635                                for quality_setting in [
636                                    DisplayQuality::Low,
637                                    DisplayQuality::Medium,
638                                    DisplayQuality::High,
639                                ] {
640                                    let mut entity = parent.spawn((
641                                        RadioButton,
642                                        Node {
643                                            width: px(150),
644                                            height: px(65),
645                                            ..button_node()
646                                        },
647                                        BackgroundColor(NORMAL_BUTTON),
648                                        Setting(quality_setting),
649                                        children![(
650                                            Text::new(format!("{quality_setting:?}")),
651                                            button_text_style(),
652                                        )],
653                                    ));
654                                    if display_quality == quality_setting {
655                                        entity.insert((Checked, BackgroundColor(PRESSED_BUTTON)));
656                                    }
657                                }
658                            })
659                        ))
660                    ),
661                    // Display the back button to return to the settings screen
662                    (
663                        Button,
664                        ActivateOnPress,
665                        button_node(),
666                        BackgroundColor(NORMAL_BUTTON),
667                        MenuButtonAction::BackToSettings,
668                        children![(Text::new("Back"), button_text_style())]
669                    )
670                ]
671            )],
672        ));
673    }
674
675    fn sound_settings_menu_setup(mut commands: Commands, volume: Res<Volume>) {
676        let button_node = Node {
677            width: px(200),
678            height: px(65),
679            margin: UiRect::all(px(20)),
680            justify_content: JustifyContent::Center,
681            align_items: AlignItems::Center,
682            ..default()
683        };
684        let button_text_style = (
685            TextFont {
686                font_size: FontSize::Px(33.0),
687                ..default()
688            },
689            TextColor(TEXT_COLOR),
690        );
691
692        let volume = *volume;
693        let button_node_clone = button_node.clone();
694        commands.spawn((
695            DespawnOnExit(MenuState::SettingsSound),
696            Node {
697                width: percent(100),
698                height: percent(100),
699                align_items: AlignItems::Center,
700                justify_content: JustifyContent::Center,
701                ..default()
702            },
703            OnSoundSettingsMenuScreen,
704            children![(
705                Node {
706                    flex_direction: FlexDirection::Column,
707                    align_items: AlignItems::Center,
708                    ..default()
709                },
710                BackgroundColor(CRIMSON.into()),
711                children![
712                    (
713                        RadioGroup,
714                        Node {
715                            align_items: AlignItems::Center,
716                            ..default()
717                        },
718                        BackgroundColor(CRIMSON.into()),
719                        Children::spawn((
720                            Spawn((Text::new("Volume"), button_text_style.clone())),
721                            SpawnWith(move |parent: &mut ChildSpawner| {
722                                for volume_setting in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] {
723                                    let mut entity = parent.spawn((
724                                        RadioButton,
725                                        Node {
726                                            width: px(30),
727                                            height: px(65),
728                                            ..button_node_clone.clone()
729                                        },
730                                        BackgroundColor(NORMAL_BUTTON),
731                                        Setting(Volume(volume_setting)),
732                                    ));
733                                    if volume == Volume(volume_setting) {
734                                        entity.insert((Checked, BackgroundColor(PRESSED_BUTTON)));
735                                    }
736                                }
737                            })
738                        ))
739                    ),
740                    (
741                        Button,
742                        ActivateOnPress,
743                        button_node,
744                        BackgroundColor(NORMAL_BUTTON),
745                        MenuButtonAction::BackToSettings,
746                        children![(Text::new("Back"), button_text_style)]
747                    )
748                ]
749            )],
750        ));
751    }
752
753    /// Observer that changes the menu and game state depending on the `MenuButtonAction`
754    /// attached to the activated Button.
755    fn on_button_activate_update_states(
756        event: On<Activate>,
757        action_q: Query<&MenuButtonAction, With<Button>>,
758        mut app_exit_writer: MessageWriter<AppExit>,
759        mut menu_state: ResMut<NextState<MenuState>>,
760        mut game_state: ResMut<NextState<GameState>>,
761    ) {
762        if let Ok(menu_button_action) = action_q.get(event.entity) {
763            match menu_button_action {
764                MenuButtonAction::Quit => {
765                    app_exit_writer.write(AppExit::Success);
766                }
767                MenuButtonAction::Play => {
768                    game_state.set(GameState::Game);
769                    menu_state.set(MenuState::Disabled);
770                }
771                MenuButtonAction::Settings => menu_state.set(MenuState::Settings),
772                MenuButtonAction::SettingsDisplay => {
773                    menu_state.set(MenuState::SettingsDisplay);
774                }
775                MenuButtonAction::SettingsSound => {
776                    menu_state.set(MenuState::SettingsSound);
777                }
778                MenuButtonAction::BackToMainMenu => menu_state.set(MenuState::Main),
779                MenuButtonAction::BackToSettings => {
780                    menu_state.set(MenuState::Settings);
781                }
782            }
783        }
784    }
785}