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/.
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 onadd_plugins
Optional (provided defaults):
fn ready(&self, _app: &App) -> bool— defaulttrue; override for async initfn finish(&self, _app: &mut App)— runs after all plugins reportready(), before first schedule tick; use for setup that depends on other plugins' async workfn cleanup(&self, _app: &mut App)— runs afterfinish(), before schedule; remove temp resourcesfn name(&self) -> &str— defaults to type name; used for uniqueness checkfn is_unique(&self) -> bool— defaulttrue; setfalsefor 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>() -> boolto guard or panic- Sub-plugins: call
app.add_plugins(ChildPlugin)insidebuild(). 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 initialapp.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 matchingresource_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 fromon_eventin 0.18)condition_changed(cond),condition_changed_to(cond)
Combinators: .and(cond), .or(cond) on any condition.