# spine2d
Experimental, pure Rust runtime for Spine 4.3 (unofficial).

## Important notice
- This project is **unofficial** and is **not affiliated with Esoteric Software**.
- This is a **pure Rust runtime**, not an FFI binding to the official `spine-c` / `spine-cpp` runtimes. Compatibility and behavioural parity are an explicit goal, but you should treat this as an experimental implementation and manage the integration risk accordingly.
- If you need “official runtime parity + support” today, use the official runtimes (or a binding to them).
## Goals
- Pure Rust implementation (no C/C++ toolchain).
- Works on `wasm32-unknown-unknown` (web) and native targets.
- Core crate is renderer-agnostic; rendering integrations live in separate crates.
- Drive parity via oracles and upstream tests (not “approximate compatibility”).
## Non-goals (for now)
- FFI bindings to the official runtimes (`spine-c`, `spine-cpp`).
- Guaranteeing perfect behavioural parity with official runtimes from day one (we are iterating toward it, with an oracle-based workflow).
## Project layout
- `spine2d`: core runtime + parsing + renderer-agnostic render output.
- `spine2d-wgpu`: wgpu renderer integration built on top of `spine2d`.
- `spine2d-bevy`: Bevy `Material2d` backend built on top of the core runtime.
- `spine2d-web`: Trunk + wgpu web demo/viewer (`wasm32-unknown-unknown`).
## Assets / redistribution
- This repository does **not** ship official Spine example exports by default.
- Local dev scripts can download `spine-runtimes` and import example exports into `assets/` for testing/demos. Make sure you comply with the Spine license terms for your usage and redistribution.
- Import examples locally: `python3 ./scripts/prepare_spine_runtimes_web_assets.py --scope tests`
## Optional upstream smoke tests (local data)
Some smoke tests can parse official exported example JSON files from the upstream `spine-runtimes` repository. These files are not committed here by default.
- Fetch + import: `python3 ./scripts/fetch_spine_runtimes_examples.py --mode json --scope tests`
- Import from an existing checkout: `./scripts/import_spine_runtimes_examples.zsh --mode json --scope tests`
- Run tests: `cargo test -p spine2d --features json,upstream-smoke`
- Run tests (including `.skel` parsing): `cargo test -p spine2d --features json,binary,upstream-smoke`
## Demo
### wgpu demo (native)
- Minimal UV test window: `cargo run -p spine2d-wgpu --example basic --features json`
- Render upstream example skeletons (requires importing `--mode export`): `python3 ./scripts/fetch_spine_runtimes_examples.py --mode export --scope tests`; `cargo run -p spine2d-wgpu --example spine_runtimes --features json -- spineboy run --speed 1.0`
- Note: the demo resets the skeleton to setup pose each frame before `AnimationState::apply` to avoid accumulated constraint drift.
- Tip: the demo has an egui top bar to switch example/animation/skin and adjust speed/margin.
### Web demo (wasm + Trunk)
- Install target: `rustup target add wasm32-unknown-unknown`
- Install Trunk: `cargo install trunk --locked`
- Run dev server: `cd spine2d-web && trunk serve`
- Note: the viewer can load local official exports if `assets/spine-runtimes/web_manifest.json` exists (generated by script; not committed).
- Note: the web demo tracks `devicePixelRatio` and reconfigures the surface on resize.
- Note: the demo auto-fits the initial bounds to the viewport (camera fit is computed once from the first frame).
- Controls: Play/Pause, Restart, Fit, Speed, Animation, Skin.
### Using official example exports locally
- Prepare assets + web manifest: `python3 ./scripts/prepare_spine_runtimes_web_assets.py --scope tests`
- Then run: `cd spine2d-web && trunk serve`
- The demo will auto-detect `assets/spine-runtimes/web_manifest.json` and populate the Example dropdown.
- Optional URL params: `?example=spineboy&anim=run`
### Bevy backend
The Bevy integration is published as the `spine2d-bevy` crate and tracks `bevy 0.19`. Install it with `cargo add spine2d-bevy`.
The Bevy examples can run with bundled demo assets, but the interactive viewer needs local Spine example exports for the full example/animation/skin switcher. Prepare those assets first when you want to test with real upstream data:
```sh
python3 ./scripts/prepare_spine_runtimes_web_assets.py --scope tests
```
The script writes ignored files under `assets/spine-runtimes/`, including `assets/spine-runtimes/web_manifest.json`. Without that manifest, the viewer falls back to the bundled `demo` assets.
Run the built-in Bevy examples:
- `cargo run -p spine2d-bevy --example basic`
- `cargo run -p spine2d-bevy --example metadata`
- `cargo run -p spine2d-bevy --example bounds`
- `cargo run -p spine2d-bevy --example multi_spine`
- `cargo run -p spine2d-bevy --example viewer`
The viewer reads `assets/spine-runtimes/web_manifest.json` and lets you switch examples, animations, skins, playback state, speed, and camera fit from the keyboard.
The Bevy integration is centered on a public `Spine` component plus optional runtime control components:
```rust
use bevy::prelude::*;
use spine2d_bevy::{Spine, Spine2dPlugin};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(Spine2dPlugin)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Spine::new(
asset_server.load("demo.json"),
asset_server.load("demo.atlas"),
).with_animation("spin", true));
}
```
Add `SpineAnimation` or `SpineSkin` only when you want component-driven runtime control after spawn. Use `SpineFlipY(true)` when your project uses a Y-down space and you want the backend to mirror Spine geometry for you. Gameplay systems can also write `SpineAnimationCommand` messages to set, queue, or clear track animations without accessing internal runtime handles. Animation lifecycle and custom Spine timeline events are published as `SpineAnimationEvent` messages.
Use the public `SpineReady` marker or `SpineLifecycleEvent` messages to observe when an entity has an active runtime instance. `SpineLifecycleEvent` also reports when that instance is released because the component was removed, the entity was despawned, or a skeleton/atlas asset reload invalidated the runtime. The backend maintains a public `SpineBounds` component with the current Bevy local-space bounds for camera fitting, hit areas, and gameplay queries.
Coordinate note: `spine2d-bevy` forwards Spine-local positions into Bevy without an implicit Y flip by default. Bevy world space is also Y-up, so the backend stays convention-neutral unless you opt in to `SpineFlipY(true)`. If a project uses Y-down or screen-space coordinates, attach `SpineFlipY(true)` to the entity or apply that conversion in the game's own transform/camera layer.
## License
Dual-licensed under `MIT OR Apache-2.0`. See `LICENSE-MIT` and `LICENSE-APACHE`.
## Spine license notice
Spine is developed by Esoteric Software. This project is not affiliated with them.
Using Spine runtimes and Spine data generally requires a Spine Editor license.
See the official license terms: https://esotericsoftware.com/spine-editor-license