Files
g4borg 325e278af1 Scaffold bevy plugin with bevy-0.19 and bevy-upgrade skills
Add plugin skeleton with versioned skill directories, TODO SKILL.md
scaffolds with goals for skill-creator, and copy over bevy-skill-research
reports from spacecraft into docs/workpad/research/.
2026-07-07 17:52:00 +02:00

3.9 KiB

Bevy Skill Research: Plugin Architecture

Bevy version: 0.18, with 0.19 notes
Date: 2026-07-07

Plugin Trait (bevy::app::Plugin)

Required:

  • fn build(&self, app: &mut App) — runs immediately on add_plugins

Optional (provided defaults):

  • fn ready(&self, _app: &App) -> bool — default true; override for async init
  • fn finish(&self, _app: &mut App) — runs after all plugins report ready(), before first schedule tick; use for setup that depends on other plugins' async work
  • fn cleanup(&self, _app: &mut App) — runs after finish(), before schedule; remove temp resources
  • fn name(&self) -> &str — defaults to type name; used for uniqueness check
  • fn is_unique(&self) -> bool — default true; set false for multi-instance plugins

Trait bounds: Downcast + Any + Send + Sync.

Any fn(&mut App) auto-implements Plugin.

Plugin Dependencies

No formal "depends-on" declaration. Patterns:

  • app.is_plugin_added::<T>() -> bool to guard or panic
  • Sub-plugins: call app.add_plugins(ChildPlugin) inside build(). Bevy deduplicates by name (unique plugins panic on double-add)
  • finish() is the idiomatic place for cross-plugin setup that needs another plugin's resources to exist

Plugin Groups

DefaultPlugins / MinimalPlugins implement PluginGroup.

Customization via PluginGroupBuilder fluent API:

  • .disable::<T>() — skip a plugin
  • .set(plugin) — replace a plugin's config
  • .add_before::<Target>(plugin) / .add_after::<Target>(plugin) — ordering
  • .enable::<T>() — re-enable disabled
DefaultPlugins.disable::<AudioPlugin>().set(WindowPlugin { ... })

App Builder Key Methods

Method Purpose
add_plugins(P) Accepts plugin, plugin group, or tuple
insert_resource(R) Insert (overwrites existing)
init_resource::<R>() Insert via Default/FromWorld if absent
add_systems(schedule, systems) Add to a named schedule
configure_sets(schedule, sets) Configure system set ordering
register_type::<T>() Register in AppTypeRegistry (reflection)
is_plugin_added::<T>() Check if plugin was already added
register_required_components::<T, R>() Component requirements (0.15+)

States (bevy::state)

Three flavors:

  • FreelyMutableState: Changed via NextState<S> resource
  • SubStates: Removed from world when parent state isn't in required variant
  • ComputedStates: Derived automatically via fn compute(sources) -> Option<Self>

Registration:

  • app.init_state::<S>() — register with default initial
  • app.insert_state(value) — register with explicit initial

Transition schedules: OnEnter(S), OnExit(S), OnTransition(S)

Entity scoping: DespawnOnEnter(S), DespawnOnExit(S) (replaced StateScoped in 0.16+)

Run condition: in_state(S) -> impl Condition (in bevy::state::condition)

0.18 change: State::set() now always triggers OnEnter/OnExit even for same-state transitions. Use set_if_neq() for old behavior.

Schedules (Built-in Labels)

Phase Schedules
Startup PreStartup, Startup, PostStartup (run once)
Per-frame First, PreUpdate, Update, PostUpdate, Last
Fixed timestep FixedFirst, FixedPreUpdate, FixedUpdate, FixedPostUpdate, FixedLast (inside FixedMain, driven by RunFixedMainLoop)
Other Main, SpawnScene (new)

Run Conditions (bevy::ecs::schedule::common_conditions)

Key functions:

  • in_state(s) — state matching
  • resource_exists::<R>(), resource_added::<R>(), resource_changed::<R>(), resource_equals(val)
  • any_with_component::<T>(), any_component_removed::<T>()
  • run_once(), not(cond)
  • on_message::<E>() (renamed from on_event in 0.18)
  • condition_changed(cond), condition_changed_to(cond)

Combinators: .and(cond), .or(cond) on any condition.