Tainted\\Coders

Bevy Plugins

Bevy version: 0.19Last updated:

Bevy enables us to break up our game into smaller features using plugins.

A plugin is any function that takes an &mut App and modifies it. These functions can do anything in your normal App definition, even adding other plugins.

fn some_plugin_system() {
  println!("Hello from some plugin system!");
}

fn plugin(app: &mut App) {
  app.add_systems(Startup, some_plugin_system);
}

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

Plugins are useful for bundling up all the setup and runtime behavior of a feature into something we can toggle on and off. Plugins encapsulate the complexity of the feature into something we can "plug-into" our app.

By performing the necessary setup such as adding systems, resources and events to your game, each plugin is responsible for injecting its behavior into your game loop.

Plugins are how third party crates add functionality to Bevy. For example, avian has a plugin that adds physics to your games.

The Plugin trait

Usually we prefer using simple functions, but if your plugin has additional life-cycle requirements you can implement the Plugin trait and define the life-cycle methods you are interested in.

struct CameraPlugin;

impl Plugin for CameraPlugin {
  fn cleanup(&self, _app: &mut App) {
    info!("Time to clean up");
  }

  fn build(&self, app: &mut App) {
    app.add_systems(Startup, initialize_camera);
  }
}

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

Plugin life-cycle

Each Plugin will go through a life-cycle of:

  1. We add our plugin to the App with add_plugins
  2. Our plugin has its build method called
  3. Bevy eventually calls ready on our plugin
  4. Bevy internally polls to ensure all plugins succeeded
  5. Finally, Bevy calls cleanup on our plugin.

Plugin ordering and dependencies

Plugins are run in the order they are added to the App. They are also defined during the app's definition phase before the app starts running. They cannot be changed at runtime.

Ideally a plugin would fully encapsulate its dependencies, but that's not always possible. Plugins that have dependencies do not currently have a good way to ensure those dependencies.

Bevy will also panic (by default) if you try and add the same plugin to your app more than once. You can work around this by checking if is_plugin_added before adding it.

fn physics_plugin(app: &mut App) {
  if !app.is_plugin_added::<avian2d::debug_render::PhysicsDebugPlugin>() {
    app.add_plugins(DebugPhysicsPlugin);
  }

  if !app.is_plugin_added::<bevy::input::InputPlugin>() {
    app.add_plugins(bevy::input::InputPlugin);
  }
}

This is a good practice for testing so that the test for your plugin can run without running the entire app.

Plugin configuration

If your plugin becomes complicated enough to demand configuration you can add fields to the struct that implements Plugin.

pub struct CameraPlugin {
  debug: bool,
}

fn initialize_camera(mut commands: Commands) {
  commands.spawn(Camera2d);
}

impl Plugin for CameraPlugin {
  fn build(&self, app: &mut App) {
    app.add_systems(Startup, initialize_camera);

    if self.debug {
      // Do something
    }
  }
}

fn main() {
  App::new().add_plugins(CameraPlugin { debug: true });
}

Plugin groups

Having plugins call other plugins is powerful and leads to a hierarchy of plugin dependencies. This can make it hard to configure.

Collections of plugins with a complicated setup can use the PluginGroup trait which allow us to group related plugins together and then configure them later.

This can be great choice for library authors writing plugins that others can add to their game.

pub struct GamePlugins;

impl PluginGroup for GamePlugins {
  fn build(self) -> PluginGroupBuilder {
    PluginGroupBuilder::start::<Self>()
      .add(CameraPlugin)
      .add(PhysicsPlugin)
  }
}

This will let us (or anyone consuming your plugins) configure exactly how the set of plugins runs in the context of our app:

fn main() {
  App::new()
    .add_plugins(DefaultPlugins)
    .add_plugins(GamePlugins.build().disable::<PhysicsPlugin>())
    .run();
}

The default plugins

Bevy includes DefaultPlugins in the bevy::prelude which gives us all of the obvious things our game would need:

PluginFeatureDescription
DiagnosticsPluginAdds core diagnostics
FrameCountPluginAdds frame counting functionality
InputPluginAdds keyboard and mouse input
PanicHandlerPluginAdds sensible panic handling
TaskPoolPluginSetup of default task pools for multi-threading
TimePluginAdds time functionality
TransformPluginHandles Transform components
AccessibilityPluginbevy_windowAdds non-GUI accessibility functionality
AnimationPluginbevy_animationAdds animation support
AntiAliasPluginbevy_anti_aliasAdds multi-sample anti-aliasing (MSAA)
AssetPluginbevy_assetAdds asset server and resources to load assets
AudioPluginbevy_audioAdds support for using sound assets
CameraPluginbevy_cameraAdds 2D and 3D camera components and systems
CiTestingPluginbevy_ci_testingHelps instrument continuous integration
ClipboardPluginbevy_clipboardAdds clipboard support to a Bevy app
CorePipelinePluginbevy_core_pipelineThe core rendering pipeline
DefaultPickingPluginsbevy_pickingAdds picking functionality
DlssInitPlugindlssInitializes DLSS support if available
GilrsPluginbevy_gilrsAdds support for gamepad inputs
GizmoPluginbevy_gizmosProvides an immediate mode drawing api for visual debugging
GizmoRenderPluginbevy_gizmos_renderSends gizmos to the renderer
GltfPluginbevy_gltfAdds support for loading gltf models
HotPatchPluginhotpatchingEnables hot-patching of assets
ImagePluginbevy_imageAdds the Image asset and prepares them to render on your GPU
InputDispatchPluginbevy_input_focusDispatches bubbling keyboard and gamepad button events to the focused entity
InputFocusPluginbevy_input_focusSets up the core input focus system
LightPluginbevy_lightAdds light components and systems
LogPluginbevy_logAdds logging to apps
MeshPluginbevy_meshAdds the Mesh asset and prepares them to render on the GPU
PbrPluginbevy_pbrAdds physical based rendering with StandardMaterial etc
PipelinedRenderingPluginbevy_renderAdds pipelined rendering
PostProcessPluginbevy_post_processAdds post processing effects
RenderDebugOverlayPluginbevy_dev_tools + bevy_pbrAdds a rendering debug overlay to visualize various renderer buffers
RenderPluginbevy_renderSets up rendering backend powered by wgpu crate
ScenePluginbevy_sceneAdds support for spawning Bevy Scenes
ScheduleRunnerPlugin!bevy_windowConfigures an App to run its Schedule according to a given RunMode (added when windowing is disabled)
SpritePluginbevy_spriteHandling of sprites (images on our entities)
SpriteRenderPluginbevy_sprite_renderAdds support for sending sprites to the renderer
StatesPluginbevy_stateAdds state management for Apps
TerminalCtrlCHandlerPluginstdHandles Ctrl-C signals in terminal applications
TextPluginbevy_textSupports loading fonts and rendering text
UiPluginbevy_uiAdds support for UI layouts (flex, grid, etc)
UiRenderPluginbevy_ui_renderAdds support for sending UI nodes to renderer
UiWidgetsPluginsbevy_ui_widgetsRegisters the observers for all of the widgets in this crate
WebAssetPluginbevy_assetAdds the http and https asset sources to an app
WindowPluginbevy_windowProvides an interface to create and manage Window components
WinitPluginbevy_winitInterface to create operating system windows (to actually display our game)
WorldSerializationPluginbevy_world_serializationLoading and saving collections of entities and components to files
You can toggle on and off features with cargo.

See cargo features for more details.

The minimal plugins

Instead of DefaultPlugins, Bevy also provides MinimalPlugins which only includes the bare essentials to get an app running:

This can be especially useful during tests to setup an app without any rendering concerns.