--- name: spine-godot description: spine-godot runtime for Godot 4 — Spine skeleton display, animation playback, bone/slot manipulation, skin mixing, 2D lighting, and runtime asset loading. Use when working with Spine skeletons, animations, SpineSprite nodes, SpineBoneNode, SpineSlotNode, or SpineAnimationTrack in this project. --- # spine-godot Skill The spine-godot runtime provides Godot nodes and APIs to load, display, animate, and manipulate skeletons exported from Spine. It wraps the spine-cpp runtime and exposes it to GDScript and C#. > **Official docs:** https://en.esotericsoftware.com/spine-godot > **Spine Runtimes Guide:** https://en.esotericsoftware.com/spine-runtimes-guide > **Source:** https://github.com/EsotericSoftware/spine-runtimes (under `spine-godot/`) ## Architecture Overview ``` spine-godot Runtime ├── SpineSprite — displays a skeleton from SkeletonDataResource │ ├── SpineSkeleton — skeleton instance (bones, slots, attachments, skins, constraints) │ ├── SpineAnimationState — animation playback, queuing, mixing via tracks │ └── Signals — animation_started, animation_completed, animation_event, etc. ├── SpineBoneNode — follows or drives a bone's transform ├── SpineSlotNode — inserts nodes into drawing order or overrides slot materials └── SpineAnimationTrack — animates a SpineSprite via Godot AnimationPlayer (C++ engine module only) ``` ## Resource Types | Resource | Purpose | |----------|---------| | `SpineSkeletonFileResource` | Imported `.skel` or `.spine-json` skeleton data | | `SpineAtlasResource` | Imported `.atlas` texture atlas data (uses normal map prefix `n_` by default) | | `SpineSkeletonDataResource` | Combines a `SpineSkeletonFileResource` + `SpineAtlasResource` + animation mix times. Shared across SpineSprite instances. | ## Asset Management ### Exporting from Spine Editor Export these files from the Spine Editor: 1. **Skeleton data:** `skeleton-name.spine-json` or `skeleton-name.skel` (binary, smaller/faster — preferred). 2. **Texture atlas:** `skeleton-name.atlas` 3. **Atlas pages:** One or more `.png` image files. > Prefer `.skel` binary exports. If using JSON, use `.spine-json` extension (not `.json`). > Pre-multiplied alpha atlases are **not supported** — Godot's default bleed handles artifacts. ### Importing into Godot 1. Drag `.skel`/`.spine-json`, `.atlas`, and `.png` files into a Godot project folder. 2. The importer creates a `SpineSkeletonFileResource`, a `SpineAtlasResource`, and Godot texture resources. 3. Create a `SpineSkeletonDataResource`: Right-click folder → `New Resource...` → `SpineSkeletonDataResource`. 4. Assign the atlas resource and skeleton file resource in the inspector. Set animation mix times here. > A `SpineSkeletonDataResource` should be **shared** by all `SpineSprite` instances displaying the same skeleton. Do not use inline resources. ### Updating Assets During Development Overwrite the source files re-exported from Spine. Godot auto-detects changes and re-imports. If it doesn't, force re-import in the import settings panel. ### Runtime Loading from Disk (modding) Load skeleton and atlas files from arbitrary paths at runtime: ```gdscript # Load skeleton file var skeleton_file_res = SpineSkeletonFileResource.new() skeleton_file_res.load_from_file("/path/to/skeleton.skel") # Load atlas file var atlas_res = SpineAtlasResource.new() atlas_res.load_from_atlas_file("/path/to/skeleton.atlas") # Create shared skeleton data resource var skeleton_data_res = SpineSkeletonDataResource.new() skeleton_data_res.skeleton_file_res = skeleton_file_res skeleton_data_res.atlas_res = atlas_res # Create sprite from data var sprite = SpineSprite.new() sprite.skeleton_data_res = skeleton_data_res sprite.position = Vector2(200, 200) sprite.get_animation_state().set_animation("animation", true, 0) add_child(sprite) ``` ## Nodes ### SpineSprite Node Displays skeleton data from a `SkeletonDataResource`. Provides access to the animation state and skeleton instance. #### Creating a SpineSprite 1. Add a `SpineSprite` node via the `+` button in the scene panel. 2. Assign a `SkeletonDataResource` in the inspector. 3. Transform the node freely in the editor viewport. #### Editor Features - **Debug view:** Check bones, slots, attachments in the `Debug` property section. Hover over parts in the viewport to see names. - **Animation preview:** Use the `Preview` section to set an animation and scrub through time with the slider. - **Custom materials:** Set per-blend-mode materials in the `Materials` section (applied to all slots). Use `SpineSlotNode` to override per-slot. - **Update Mode:** `Process` (default, every frame), `Physics` (fixed interval, 60fps), `Manual` (call `update_skeleton()` yourself in `_process`/`_physics_process`). #### Animating a SpineSprite ```gdscript extends SpineSprite func _ready(): var animation_state = get_animation_state() # Set an animation on track 0, looping animation_state.set_animation("walk", true, 0) # Queue an animation after a delay (mix duration implied by mix times) animation_state.add_animation("idle", 0.5, true, 0) # Set a reversed animation var track_entry = animation_state.set_animation("walk", true, 0) track_entry.set_reverse(true) # Mix to an empty animation (reset to setup pose) animation_state.set_empty_animation(0, 0.5) animation_state.add_empty_animation(0, 0.5, 0.5) # Clear tracks animation_state.clear_track(0) # clear one track animation_state.clear_tracks() # clear all tracks # Reset skeleton poses get_skeleton().set_to_setup_pose() # bones + slots get_skeleton().set_slots_to_setup_pose() # slots only ``` #### Animation State API | Method | Purpose | |--------|---------| | `set_animation(name, loop, track)` | Play animation. Returns `SpineTrackEntry`. | | `add_animation(name, delay, loop, track)` | Queue animation after delay. Returns `SpineTrackEntry`. | | `set_empty_animation(track, mix_duration)` | Mix out to empty pose immediately. | | `add_empty_animation(track, mix_duration, delay)` | Queue empty animation mix-out. | | `clear_track(track)` | Immediately clear one track. | | `clear_tracks()` | Immediately clear all tracks. | > **Important:** Do not hold `SpineTrackEntry` references across frames. They are reused internally and become invalid once the animation completes. #### SpineTrackEntry Properties | Method | Purpose | |--------|---------| | `set_reverse(true)` | Play animation in reverse. | | `set_mix_duration(seconds)` | Override mix duration. | | `set_alpha(value)` | Mix alpha for blending (0-1). | | `set_time_scale(scale)` | Animation speed multiplier. | | `set_track_time(time)` | Jump to specific time in seconds. | #### SpineSprite Signals **Animation state signals:** - `animation_started(track_entry)` — animation started playing. - `animation_interrupted(track_entry)` — track cleared or new animation set. - `animation_completed(track_entry)` — animation finished one full loop. - `animation_ended(track_entry)` — animation will never be applied again. - `animation_disposed(track_entry)` — track entry disposed. - `animation_event(track_entry, event)` — user-defined Spine event triggered. **Lifecycle signals:** - `before_animation_state_update` — before animation state is updated with delta. - `before_animation_state_apply` — before animation state is applied to skeleton pose. - `before_world_transforms_change` — before bone world transforms update. Use to set bone transforms manually. - `world_transforms_changed` — after bone world transforms update. #### Mix-and-Match Skins Combine multiple skins to create custom avatars: ```gdscript var custom_skin = new_skin("custom-skin") var data = get_skeleton().get_data() custom_skin.add_skin(data.find_skin("skin-base")) custom_skin.add_skin(data.find_skin("nose/short")) custom_skin.add_skin(data.find_skin("eyes/violet")) custom_skin.add_skin(data.find_skin("hair/brown")) custom_skin.add_skin(data.find_skin("clothes/hoodie-orange")) get_skeleton().set_skin(custom_skin) get_skeleton().set_slots_to_setup_pose() ``` #### Getting and Setting Bone Transforms ```gdscript # Get a bone's global transform in Godot canvas space var transform = get_global_bone_transform("bone_name") # Set a bone's transform (must be called before world transforms update) set_global_bone_transform("bone_name", Transform2D(0, Vector2(100, 200))) ``` When manually setting bone transforms, connect to `before_world_transforms_change` signal. Alternatively, use `SpineBoneNode` for simpler bone manipulation. ### SpineBoneNode A child node of `SpineSprite` that either **follows** or **drives** a bone. > Must be a **direct child** of a `SpineSprite`. **Inspector properties:** - `Bone Name` — dropdown of all available bones. - `Bone Mode` — `Follow` (node moves with bone) or `Drive` (node controls bone position). - `Enabled` — toggle following/driving on/off. **Use cases:** - **Drive mode:** Control a bone via mouse position or other input (e.g., aiming). - **Follow mode:** Attach other nodes (CollisionShape, Marker2D) to follow a bone. See examples: `example/05-mouse-following` (drive), `example/06-bone-following` (follow). ### SpineSlotNode A child node of `SpineSprite` that inserts children into the skeleton's drawing order or overrides a slot's materials. > Must be a **direct child** of a `SpineSprite`. **Inspector properties:** - `Slot Name` — dropdown of all available slots. - `Materials` — custom materials that override the slot's default materials. **Use cases:** - Attach particle systems, custom sprites, or other `SpineSprite` nodes on top of a slot. - Override materials per-slot (overrides `SpineSprite`'s global materials). See examples: `example/07-slot-node` (drawing order), `example/09-custom-material` (material override). ### SpineAnimationTrack (C++ Engine Module Only) Animates a `SpineSprite` via Godot's `AnimationPlayer` and animation editor. Ideal for cutscenes. > **Experimental.** Not available in the GDExtension. > Must be a **direct child** of a `SpineSprite`. - Creates a child `AnimationPlayer` automatically. - Key animations on the child `AnimationPlayer`; key animation properties (loop, reverse, etc.) on the `SpineAnimationTrack`. - Multiple `SpineAnimationTrack` nodes on one `SpineSprite` enable multi-track layering. - Each track gets a unique **track index** — higher tracks override lower tracks' skeleton properties. > Mix times from `SpineSkeletonDataResource` cannot be previewed in the editor. See example: `example/08-animation-player`. ## 2D Lighting spine-godot integrates with Godot's 2D lighting system. ### Setup 1. Provide normal map `.png` images alongside atlas page images with prefix `n_` (default): e.g., `raptor.png` → `n_raptor.png`. 2. The normal map prefix is configurable in the atlas import settings. 3. After importing, apply Godot 2D lights to `SpineSprite` nodes. See example: `example/10-2d-lighting`. ## Spine Runtimes API Access Almost all spine-cpp API is mapped 1:1 to GDScript. Objects from `SpineSprite` (via `get_skeleton()`, `get_animation_state()`) mirror the C++ API. ### GDScript Limitations - Returned arrays/maps are **copies** — mutations don't affect internals. - Cannot set per-track-entry listeners — use `SpineSprite` signals instead. - Cannot create/add/remove bones, slots, or other Spine objects directly. - C++ class hierarchies of attachments and timelines are not exposed.