Plugins

One of Bevy's core principles is modularity. All Bevy engine features are implemented as plugins---collections of code that modify an App. This includes internal features like the renderer, but games themselves are also implemented as plugins! This empowers developers to pick and choose which features they want. Don't need a UI? Don't register the UiPlugin. Want to build a headless server? Don't register the RenderPlugin.

This also means you are free to replace any components you don't like. If you feel the need, you are welcome to build your own UiPlugin, but consider contributing it back to Bevy if you think it would be useful!

Those not contributed back into Bevy and instead released separately are third-party plugins. These are useful and easy to use additions created by fellow developers that can help you avoid re-inventing the wheel. To use them all you have to do is:

  1. Find a third party Bevy plugin (like those at the Assets page).
  2. Add it to your Cargo.toml as a crate under [dependencies].
  3. Import the code definitions (like use third_party::prelude::*;) from the crate to add the items to your workspace.
  4. Add the plugin to your app (like app.add_plugins(third_party_plugin)).

However, most developers don't need a custom experience and just want the "full engine" experience with no hassle. For this, Bevy provides a set of DefaultPlugins.

Bevy's Default Plugins #

Let's make our app more interesting by adding Bevy's DefaultPlugins which are a PluginGroup containing core engine features. (For those needing minimal features, MinimalPlugins exists). add_plugins(DefaultPlugins) adds the features most people expect from an engine, such as a 2D / 3D renderer, asset loading, a UI system, windows, and input.

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, add_people)
        .add_systems(Update, (hello_world, (update_people, greet_people).chain()))
        .run();
}

Once again run cargo run.

You should hopefully notice two things:

  • A window should pop up. This is because we now have WindowPlugin, which defines the window interface (but doesn't actually know how to make windows), and [WinitPlugin] which uses the winit library to create a window using your OS's native window API.
  • Your console is now full of "hello" messages: This is because DefaultPlugins adds an "event loop" to our application. Our App's ECS Schedule now runs in a loop once per "frame". We will resolve the console spam in a moment.

Creating your first plugin #

For better organization, let's move all of our "hello" logic to a plugin. To create a plugin we just need to implement the Plugin interface. Add the following code to your main.rs file:

pub struct HelloPlugin;

impl Plugin for HelloPlugin {
    fn build(&self, app: &mut App) {
        // add things to your app here
    }
}

Then register the plugin in your App like this:

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(HelloPlugin)
        .add_systems(Startup, add_people)
        .add_systems(Update, (hello_world, (update_people, greet_people).chain()))
        .run();
}

Note add_plugins can add any number of plugins (or plugin groups like DefaultPlugins) by passing in a tuple of them. Now all that's left is to move our systems into HelloPlugin, which is just a matter of cut and paste. The app variable in our plugin's build() function is the same builder type we use in our main() function:

impl Plugin for HelloPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Startup, add_people);
        app.add_systems(Update, (hello_world, (update_people, greet_people).chain()));
    }
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(HelloPlugin)
        .run();
}

Try running the app again. It should do exactly what it did before. In the next section, we'll fix the "hello" spam using Resources.