spine_bevy 0.2.0

Bevy 0.19 plugin for the unofficial spine_runtime port: load, animate, and draw Spine 4.3 skeletons.
Documentation
//! Bevy 0.19 integration for [`spine_runtime`].
//!
//! # Quick start (2D)
//!
//! ```no_run
//! use bevy::prelude::*;
//! use spine_bevy::{SpinePlugin, SpineSkeleton, SpineSkeletonAsset, SpineSkeletonLoaderSettings};
//!
//! fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
//!     commands.spawn(Camera2d);
//!     let skel: Handle<SpineSkeletonAsset> = asset_server.load_with_settings(
//!         "spineboy/export/spineboy-pro.skel",
//!         |s: &mut SpineSkeletonLoaderSettings| {
//!             s.atlas_path = Some("spineboy/export/spineboy-pma.atlas".into());
//!         },
//!     );
//!     commands.spawn(SpineSkeleton::new(skel).with_initial_animation(0, "walk", true));
//! }
//!
//! App::new()
//!     .add_plugins(DefaultPlugins)
//!     .add_plugins(SpinePlugin)
//!     .add_systems(Startup, setup)
//!     .run();
//! ```
//!
//! # Quick start (3D)
//!
//! Opt into the 3D pipeline by spawning a [`SpineRender3d`] marker
//! alongside the [`SpineSkeleton`]; use a `Camera3d` instead of `Camera2d`.
//! Positions emitted by the Spine runtime are 2D — the 3D backend lays
//! them out in the skeleton's local XY plane (z = 0), so rotate the
//! entity's `Transform` to stand the rig upright in a 3D scene. See
//! `examples/spineboy_walk_3d.rs` for a complete example.
//!
//! # How it fits together
//!
//! [`SpinePlugin`] registers asset loaders, both the [`SpineMaterial`]
//! (`Material2d`) and [`SpineMaterial3d`] (`Material`) material plugins,
//! two `Message` types for animation events, and five ordered system
//! stages:
//!
//! 1. [`SpineSet::EnsureMarkers`] — backfills [`SpineRender2d`] on any
//!    skeleton entity that doesn't carry a render-mode marker.
//! 2. [`SpineSet::Init`] — observes assets that finished loading and
//!    builds the per-instance runtime state.
//! 3. [`SpineSet::Tick`] — advances animation time, applies timelines,
//!    re-integrates world transforms, emits the per-frame
//!    `RenderCommand` stream into each skeleton's renderer. Parallel
//!    over skeletons.
//! 4. [`SpineSet::BuildMeshes`] — converts that command stream into
//!    Bevy `Mesh` + `MeshMaterial2d<SpineMaterial>` (or
//!    `MeshMaterial3d<SpineMaterial3d>`) children, depending on which
//!    render-mode marker the parent carries.
//! 5. [`SpineSet::Events`] — drains lifecycle + keyframe events into
//!    [`SpineStateEvent`] / [`SpineKeyframeEvent`] writers.
//!
//! User systems can `.before(SpineSet::Tick)` to mutate `time_scale` or
//! queue animations on the same frame they take effect.
//!
//! # Atlas expectations
//!
//! The shipped materials assume premultiplied-alpha textures. Spine
//! exports usually ship `*-pma.atlas` / `*-pma.png` variants alongside
//! straight-alpha pairs; prefer the PMA variant via
//! [`SpineSkeletonLoaderSettings::atlas_path`].
//!
//! [`spine_runtime`]: https://github.com/dead-money/spine_runtime

use bevy::asset::AssetApp;
use bevy::pbr::MaterialPlugin;
use bevy::prelude::*;
use bevy::sprite_render::Material2dPlugin;

pub mod asset;
pub mod components;
pub mod material;
pub mod mesh;
pub mod systems;

pub use asset::{
    SpineAtlasAsset, SpineAtlasLoader, SpineAtlasLoaderError, SpineSkeletonAsset,
    SpineSkeletonJsonLoader, SpineSkeletonJsonLoaderError, SpineSkeletonJsonLoaderSettings,
    SpineSkeletonLoader, SpineSkeletonLoaderError, SpineSkeletonLoaderSettings,
};
pub use components::{
    PendingAnimation, SpineRender2d, SpineRender3d, SpineSkeleton, SpineSkeletonState,
};
pub use material::{SpineBlendMode, SpineColors, SpineMaterial, SpineMaterial3d, SpineMaterialKey};
pub use mesh::{build_spine_meshes, build_spine_meshes_3d};
pub use systems::{
    SpineInitialized, SpineKeyframeEvent, SpineSet, SpineStateEvent, drain_spine_events,
    ensure_spine_render_marker, initialize_spine_skeletons, tick_spine_skeletons,
};

/// Bevy plugin entry point. Register once during `App` setup; spawns
/// [`SpineSkeleton`] components afterward to bring rigs to life.
#[derive(Default)]
pub struct SpinePlugin;

impl Plugin for SpinePlugin {
    fn build(&self, app: &mut App) {
        material::spine_material::register_spine_shader(app);
        material::spine_material_3d::register_spine_shader_3d(app);

        app.init_asset::<SpineAtlasAsset>()
            .init_asset::<SpineSkeletonAsset>()
            .init_asset_loader::<SpineAtlasLoader>()
            .init_asset_loader::<SpineSkeletonLoader>()
            .init_asset_loader::<SpineSkeletonJsonLoader>()
            .add_plugins(Material2dPlugin::<SpineMaterial>::default())
            .add_plugins(MaterialPlugin::<SpineMaterial3d>::default())
            .add_message::<SpineStateEvent>()
            .add_message::<SpineKeyframeEvent>()
            .configure_sets(
                Update,
                (
                    SpineSet::EnsureMarkers,
                    SpineSet::Init,
                    SpineSet::Tick,
                    SpineSet::BuildMeshes,
                    SpineSet::Events,
                )
                    .chain(),
            )
            .add_systems(
                Update,
                (
                    ensure_spine_render_marker.in_set(SpineSet::EnsureMarkers),
                    initialize_spine_skeletons.in_set(SpineSet::Init),
                    tick_spine_skeletons.in_set(SpineSet::Tick),
                    build_spine_meshes.in_set(SpineSet::BuildMeshes),
                    build_spine_meshes_3d.in_set(SpineSet::BuildMeshes),
                    drain_spine_events.in_set(SpineSet::Events),
                ),
            );
    }
}