Expand description
ยง๐โโ๏ธ Bevy Quickmenu
Every game needs menus. Be it for the settings, for a pause screen, or for the main menu when the user enters the game.
While Bevy UI allows building menus, it is a cumbersome process especially if nested menus are needed. Even worse, though, is the effort required if you want your menu to be accessible with a keyboard, a gamepad, or the mouse.
Bevy Quickmenu offers all that. It is a super lightweight way of building in-game menus that can be controlled by all input devices. It even offers a simple way of having hover
states. Everything can also be customized.
- Super simple menu plugin for bevy, building upon Bevy UI
- Keyboard, Mouse, Gamepad input is processed
- Support mouse hover states in simplified Stylesheet
- Many customizations possible (see examples/custom.rs)
ยงUsage
Add to Cargo.toml
:
[dependencies]
bevy_quickmenu = "0.1.5"
ยงVersion Compatibility
Bevy Version | Crates Version |
---|---|
0.11.0 | 0.2.0 |
0.10.0 | 0.1.6 |
0.9.0 | 0.1.5 |
ยงDemo
ยงQuick Examples
examples/basic.rs
: Basic example to show how it worksexamples/settings.rs
: Full blown user settings including switching game states and showing the menu againexamples/custom.rs
: Showcase customization options
ยงAn explanation of the required components
ยงState
A generic type that hosts the state of your menu (e.g. which items are selected, and so on). Whenever this state changes, the menu is automatically redrawn.
ยงAction
(Conforms to ActionTrait
): This enum defines all the actions your user can take. Such as SoundOn
, SoundOff
etc. When a user performs an action (by selecting the corresponding menu entry), the handle
method is called on your ActionTrait
implementation. ActionTrait
has two generic types: Your State
as well as a Event
which you can define. This allows you to handle your action:
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
enum Actions {
Close,
SoundOn,
SoundOff,
Control(usize, ControlDevice),
}
impl ActionTrait for Actions {
type State = CustomState;
type Event = MyEvent;
fn handle(&self, state: &mut CustomState, event_writer: &mut EventWriter<MyEvent>) {
match self {
Actions::Close => event_writer.send(MyEvent::CloseSettings),
Actions::SoundOn => state.sound_on = true,
Actions::SoundOff => state.sound_on = false,
Actions::Control(p, d) => {
state.controls.insert(*p, *d);
}
}
}
}
ยงScreen
(Conforms to the ScreenTrait
). Each page or screen in your menu is defined by this enum. Note that menu screens are not nested!. Instead the ScreenTrait
has a resolve
function that allows you to return the corresponding menu definition for the given enum:
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
enum Screens {
Root,
Controls,
Sound,
Player(usize),
}
impl ScreenTrait for Screens {
type Action = Actions;
fn resolve(&self, state: &mut CustomState) -> Menu<Screens> {
match self {
Screens::Root => root_menu(state),
Screens::Controls => controls_menu(state),
Screens::Sound => sound_menu(state),
Screens::Player(p) => player_controls_menu(state, *p),
}
}
}
ยงMenu
A menu is just a function that returns a list of MenuItem
to be displayed. Each menu needs to have a distinct id. The example shows how the root
and the sound
menu are defined.
fn root_menu(_state: &mut CustomState) -> Menu<Screens> {
Menu::new(
Id::new("root"),
vec![
MenuItem::headline("Settings"),
MenuItem::action("Back", Actions::Close).with_icon(MenuIcon::Back),
MenuItem::screen("Sound", Screens::Sound).with_icon(MenuIcon::Sound),
MenuItem::screen("Controls", Screens::Controls).with_icon(MenuIcon::Controls),
],
)
}
fn sound_menu(state: &mut CustomState) -> Menu<Screens> {
Menu::new(
Id::new("sound"),
vec![
MenuItem::label("Toggles sound and music"),
MenuItem::action("On", Actions::SoundOn).checked(state.sound_on),
MenuItem::action("Off", Actions::SoundOff).checked(!state.sound_on),
],
)
}
ยงMenuItem
In order to give you some flexibility, the menu item allows you to return five different types:
MenuItem::label
: A small text label that cannot be selectedMenuItem::headline
: A big text label that cannot be selectedMenuItem::action
: A action that is performed when the user selects itMenuItem::screen
: Dive into a screen when the user selects thisMenuItem::image
: A single image (including an optionalStyle
)
In addition, a menu-item can have one of a couple of pre-defined icons or a custom icon
MenuItem::screen("Controls", Screens::Controls).with_icon(MenuIcon::Controls)
MenuItem::screen("Save", Screens::Save).with_icon(MenuIcon::Other(icons.save.clone()))
MenuItem
s can also be checked or unchecked:
MenuItem::action("On", Actions::SoundOn).checked(state.sound_on)
MenuItem::action("Off", Actions::SoundOff).checked(!state.sound_on)
ยงDisplaying a Menu
Hereโs a the annoated setup function from the example:
impl Plugin for SettingsPlugin {
fn build(&self, app: &mut App) {
app
// Register a event that can be called from your action handler
.add_event::<BasicEvent>()
// The plugin
.add_plugins(QuickMenuPlugin::<Screens>::new())
// Some systems
.add_startup_system(setup)
.add_system(event_reader);
}
}
fn setup(mut commands: Commands) {
commands.spawn(Camera3dBundle::default());
commands.insert_resource(MenuState::new(
BasicState::default(),
Screens::Root,
Some(StyleSheet::default()),
))
}
ยงRemoving a Menu
In order to remove a menu, thereโs the bevy_quickmenu::cleanup
function. Usually, it is best
to use it with the event that Bevy Quickmenu allows you to register:
#[derive(Debug)]
enum BasicEvent {
Close,
}
impl ActionTrait for Actions {
fn handle(&self, state: &mut BasicState, event_writer: &mut EventWriter<BasicEvent>) {
match self {
Actions::Close => event_writer.send(BasicEvent::Close),
}
}
}
fn event_reader(mut commands: Commands, mut event_reader: EventReader<BasicEvent>) {
for event in event_reader.iter() {
match event {
BasicEvent::Close => bevy_quickmenu::cleanup(&mut commands),
}
}
}
ยงScreenshot from the customized screen
Modulesยง
- style
- Lightweight abstractions over styles Instead of using the bevy styles with all their properties, these simplified styles are mostly used to define the looks of menus and the different control states of buttons.
Structsยง
- Button
Component - Each Button in the UI can be queried via this component in order to further change the appearance
- Menu
- Create a menu with an identifier and a
Vec
ofMenuItem
entries - Menu
Options - Changing these
MenuOptions
allows overriding the provided images and fonts. Usecrate::QuickMenuPlugin::with_options
to do this. - Menu
State - The primary state resource of the menu
- Navigation
Menu - Primary
Menu - The primary horizontal menu can be queried via this component
- Quick
Menu Plugin - The quickmenu plugin. It requires multiple generic parameters in order to setup. A minimal example. For a full explanation refer to the examples or the README.
- Redraw
Event - Whenever a state change in the
MenuState
is detected, this event is send in order to tell the UI to re-render itself - Rich
Text Entry - Simplified Rich-Text that assumes the default font
- Selections
- This map holds the currently selected items in each screen / menu
- Vertical
Menu Component - Each vertical menu can be queried via this component
Enumsยง
- Menu
Icon - The library comes with some pre-defined icons for several screens.
Custom icons can be used with
MenuIcon::Other
or by overriding the existing ones viaMenuOptions
- Menu
Item - Abstraction over MenuItems in a Screen / Menu
- Menu
Selection - Abstraction over a concrete selection in a screen / menu
- Navigation
Event - GamePad and Cursor navigation generates these navigation events
which are then processed by a system and applied to the menu.
Navigation can be customized by sending these events into a
EventWriter<NavigationEvent>
Traitsยง
- Action
Trait - A type conforming to this trait is used to handle the events that are generated as the user interacts with the menu
- Screen
Trait - Each Menu / Screen uses this trait to define which menu items lead to which other screens
Functionsยง
- cleanup
- Remove the menu