Expressively define plugins using functions (#11080)
# Objective Plugins are an incredible tool for encapsulating functionality. They are low-key one of Bevy's best features. Combined with rust's module and privacy system, it's a match made in heaven. The one downside is that they can be a little too verbose to define. 90% of all plugin definitions look something like this: ```rust pub struct MyPlugin; impl Plugin for MyPlugin { fn build(&self, app: &mut App) { app.init_resource::<CameraAssets>() .add_event::<SetCamera>() .add_systems(Update, (collect_set_camera_events, drive_camera).chain()); } } ``` Every so often it gets a little spicier: ```rust pub struct MyGenericPlugin<T>(PhantomData<T>); impl<T> Default for MyGenericPlugin<T> { fn default() -> Self { ... } } impl<T> Plugin for MyGenericPlugin<T> { ... } ``` This is an annoying amount of boilerplate. Ideally, plugins should be focused and small in scope, which means any app is going to have a *lot* of them. Writing a plugin should be as easy as possible, and the *only* part of this process that carries any meaning is the body of `fn build`. ## Solution Implement `Plugin` for functions that take `&mut App` as a parameter. The two examples above now look like this: ```rust pub fn my_plugin(app: &mut App) { app.init_resource::<CameraAssets>() .add_event::<SetCamera>() .add_systems(Update, (collect_set_camera_events, drive_camera).chain()); } pub fn my_generic_plugin<T>(app: &mut App) { // No need for PhantomData, it just works. } ``` Almost all plugins can be written this way, which I believe will make bevy code much more attractive. Less boilerplate and less meaningless indentation. More plugins with smaller scopes. --- ## Changelog The `Plugin` trait is now implemented for all functions that take `&mut App` as their only parameter. This is an abbreviated way of defining plugins with less boilerplate than manually implementing the trait. --------- Co-authored-by: Federico Rinaldi <gisquerin@gmail.com>
This commit is contained in:
parent
8f25805b66
commit
d66c868e6f
@ -21,6 +21,40 @@ use std::any::Any;
|
|||||||
/// * once the app started, it will wait for all registered [`Plugin::ready`] to return `true`
|
/// * once the app started, it will wait for all registered [`Plugin::ready`] to return `true`
|
||||||
/// * it will then call all registered [`Plugin::finish`]
|
/// * it will then call all registered [`Plugin::finish`]
|
||||||
/// * and call all registered [`Plugin::cleanup`]
|
/// * and call all registered [`Plugin::cleanup`]
|
||||||
|
///
|
||||||
|
/// ## Defining a plugin.
|
||||||
|
///
|
||||||
|
/// Most plugins are simply functions that add configuration to an [`App`].
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_app::{App, Update};
|
||||||
|
/// App::new().add_plugins(my_plugin).run();
|
||||||
|
///
|
||||||
|
/// // This function implements `Plugin`, along with every other `fn(&mut App)`.
|
||||||
|
/// pub fn my_plugin(app: &mut App) {
|
||||||
|
/// app.add_systems(Update, hello_world);
|
||||||
|
/// }
|
||||||
|
/// # fn hello_world() {}
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// For more advanced use cases, the `Plugin` trait can be implemented manually for a type.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_app::*;
|
||||||
|
/// pub struct AccessibilityPlugin {
|
||||||
|
/// pub flicker_damping: bool,
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl Plugin for AccessibilityPlugin {
|
||||||
|
/// fn build(&self, app: &mut App) {
|
||||||
|
/// if self.flicker_damping {
|
||||||
|
/// app.add_systems(PostUpdate, damp_flickering);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// # fn damp_flickering() {}
|
||||||
|
/// ````
|
||||||
pub trait Plugin: Downcast + Any + Send + Sync {
|
pub trait Plugin: Downcast + Any + Send + Sync {
|
||||||
/// Configures the [`App`] to which this plugin is added.
|
/// Configures the [`App`] to which this plugin is added.
|
||||||
fn build(&self, app: &mut App);
|
fn build(&self, app: &mut App);
|
||||||
@ -60,6 +94,12 @@ pub trait Plugin: Downcast + Any + Send + Sync {
|
|||||||
|
|
||||||
impl_downcast!(Plugin);
|
impl_downcast!(Plugin);
|
||||||
|
|
||||||
|
impl<T: Fn(&mut App) + Send + Sync + 'static> Plugin for T {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
self(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A type representing an unsafe function that returns a mutable pointer to a [`Plugin`].
|
/// A type representing an unsafe function that returns a mutable pointer to a [`Plugin`].
|
||||||
/// It is used for dynamically loading plugins.
|
/// It is used for dynamically loading plugins.
|
||||||
///
|
///
|
||||||
|
@ -37,7 +37,7 @@ fn main() {
|
|||||||
.init_state::<GameState>()
|
.init_state::<GameState>()
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
// Adds the plugins for each state
|
// Adds the plugins for each state
|
||||||
.add_plugins((splash::SplashPlugin, menu::MenuPlugin, game::GamePlugin))
|
.add_plugins((splash::splash_plugin, menu::menu_plugin, game::game_plugin))
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,19 +51,15 @@ mod splash {
|
|||||||
use super::{despawn_screen, GameState};
|
use super::{despawn_screen, GameState};
|
||||||
|
|
||||||
// This plugin will display a splash screen with Bevy logo for 1 second before switching to the menu
|
// This plugin will display a splash screen with Bevy logo for 1 second before switching to the menu
|
||||||
pub struct SplashPlugin;
|
pub fn splash_plugin(app: &mut App) {
|
||||||
|
// As this plugin is managing the splash screen, it will focus on the state `GameState::Splash`
|
||||||
impl Plugin for SplashPlugin {
|
app
|
||||||
fn build(&self, app: &mut App) {
|
// When entering the state, spawn everything needed for this screen
|
||||||
// As this plugin is managing the splash screen, it will focus on the state `GameState::Splash`
|
.add_systems(OnEnter(GameState::Splash), splash_setup)
|
||||||
app
|
// While in this state, run the `countdown` system
|
||||||
// When entering the state, spawn everything needed for this screen
|
.add_systems(Update, countdown.run_if(in_state(GameState::Splash)))
|
||||||
.add_systems(OnEnter(GameState::Splash), splash_setup)
|
// When exiting the state, despawn everything that was spawned for this screen
|
||||||
// While in this state, run the `countdown` system
|
.add_systems(OnExit(GameState::Splash), despawn_screen::<OnSplashScreen>);
|
||||||
.add_systems(Update, countdown.run_if(in_state(GameState::Splash)))
|
|
||||||
// When exiting the state, despawn everything that was spawned for this screen
|
|
||||||
.add_systems(OnExit(GameState::Splash), despawn_screen::<OnSplashScreen>);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag component used to tag entities added on the splash screen
|
// Tag component used to tag entities added on the splash screen
|
||||||
@ -125,14 +121,10 @@ mod game {
|
|||||||
|
|
||||||
// This plugin will contain the game. In this case, it's just be a screen that will
|
// This plugin will contain the game. In this case, it's just be a screen that will
|
||||||
// display the current settings for 5 seconds before returning to the menu
|
// display the current settings for 5 seconds before returning to the menu
|
||||||
pub struct GamePlugin;
|
pub fn game_plugin(app: &mut App) {
|
||||||
|
app.add_systems(OnEnter(GameState::Game), game_setup)
|
||||||
impl Plugin for GamePlugin {
|
.add_systems(Update, game.run_if(in_state(GameState::Game)))
|
||||||
fn build(&self, app: &mut App) {
|
.add_systems(OnExit(GameState::Game), despawn_screen::<OnGameScreen>);
|
||||||
app.add_systems(OnEnter(GameState::Game), game_setup)
|
|
||||||
.add_systems(Update, game.run_if(in_state(GameState::Game)))
|
|
||||||
.add_systems(OnExit(GameState::Game), despawn_screen::<OnGameScreen>);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag component used to tag entities added on the game screen
|
// Tag component used to tag entities added on the game screen
|
||||||
@ -253,57 +245,50 @@ mod menu {
|
|||||||
// - a main menu with "New Game", "Settings", "Quit"
|
// - a main menu with "New Game", "Settings", "Quit"
|
||||||
// - a settings menu with two submenus and a back button
|
// - a settings menu with two submenus and a back button
|
||||||
// - two settings screen with a setting that can be set and a back button
|
// - two settings screen with a setting that can be set and a back button
|
||||||
pub struct MenuPlugin;
|
pub fn menu_plugin(app: &mut App) {
|
||||||
|
app
|
||||||
impl Plugin for MenuPlugin {
|
// At start, the menu is not enabled. This will be changed in `menu_setup` when
|
||||||
fn build(&self, app: &mut App) {
|
// entering the `GameState::Menu` state.
|
||||||
app
|
// Current screen in the menu is handled by an independent state from `GameState`
|
||||||
// At start, the menu is not enabled. This will be changed in `menu_setup` when
|
.init_state::<MenuState>()
|
||||||
// entering the `GameState::Menu` state.
|
.add_systems(OnEnter(GameState::Menu), menu_setup)
|
||||||
// Current screen in the menu is handled by an independent state from `GameState`
|
// Systems to handle the main menu screen
|
||||||
.init_state::<MenuState>()
|
.add_systems(OnEnter(MenuState::Main), main_menu_setup)
|
||||||
.add_systems(OnEnter(GameState::Menu), menu_setup)
|
.add_systems(OnExit(MenuState::Main), despawn_screen::<OnMainMenuScreen>)
|
||||||
// Systems to handle the main menu screen
|
// Systems to handle the settings menu screen
|
||||||
.add_systems(OnEnter(MenuState::Main), main_menu_setup)
|
.add_systems(OnEnter(MenuState::Settings), settings_menu_setup)
|
||||||
.add_systems(OnExit(MenuState::Main), despawn_screen::<OnMainMenuScreen>)
|
.add_systems(
|
||||||
// Systems to handle the settings menu screen
|
OnExit(MenuState::Settings),
|
||||||
.add_systems(OnEnter(MenuState::Settings), settings_menu_setup)
|
despawn_screen::<OnSettingsMenuScreen>,
|
||||||
.add_systems(
|
)
|
||||||
OnExit(MenuState::Settings),
|
// Systems to handle the display settings screen
|
||||||
despawn_screen::<OnSettingsMenuScreen>,
|
.add_systems(
|
||||||
)
|
OnEnter(MenuState::SettingsDisplay),
|
||||||
// Systems to handle the display settings screen
|
display_settings_menu_setup,
|
||||||
.add_systems(
|
)
|
||||||
OnEnter(MenuState::SettingsDisplay),
|
.add_systems(
|
||||||
display_settings_menu_setup,
|
Update,
|
||||||
)
|
(setting_button::<DisplayQuality>.run_if(in_state(MenuState::SettingsDisplay)),),
|
||||||
.add_systems(
|
)
|
||||||
Update,
|
.add_systems(
|
||||||
(
|
OnExit(MenuState::SettingsDisplay),
|
||||||
setting_button::<DisplayQuality>
|
despawn_screen::<OnDisplaySettingsMenuScreen>,
|
||||||
.run_if(in_state(MenuState::SettingsDisplay)),
|
)
|
||||||
),
|
// Systems to handle the sound settings screen
|
||||||
)
|
.add_systems(OnEnter(MenuState::SettingsSound), sound_settings_menu_setup)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
OnExit(MenuState::SettingsDisplay),
|
Update,
|
||||||
despawn_screen::<OnDisplaySettingsMenuScreen>,
|
setting_button::<Volume>.run_if(in_state(MenuState::SettingsSound)),
|
||||||
)
|
)
|
||||||
// Systems to handle the sound settings screen
|
.add_systems(
|
||||||
.add_systems(OnEnter(MenuState::SettingsSound), sound_settings_menu_setup)
|
OnExit(MenuState::SettingsSound),
|
||||||
.add_systems(
|
despawn_screen::<OnSoundSettingsMenuScreen>,
|
||||||
Update,
|
)
|
||||||
setting_button::<Volume>.run_if(in_state(MenuState::SettingsSound)),
|
// Common systems to all screens that handles buttons behavior
|
||||||
)
|
.add_systems(
|
||||||
.add_systems(
|
Update,
|
||||||
OnExit(MenuState::SettingsSound),
|
(menu_action, button_system).run_if(in_state(GameState::Menu)),
|
||||||
despawn_screen::<OnSoundSettingsMenuScreen>,
|
);
|
||||||
)
|
|
||||||
// Common systems to all screens that handles buttons behavior
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
(menu_action, button_system).run_if(in_state(GameState::Menu)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// State used for the current menu screen
|
// State used for the current menu screen
|
||||||
|
Loading…
Reference in New Issue
Block a user