mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-06 15:24:55 +08:00
Remove docs.md and docs-new.md
This commit is contained in:
parent
b9743fde46
commit
ef0c74a8d6
@ -1,595 +0,0 @@
|
||||
# spine-flutter Runtime Documentation
|
||||
|
||||
> **Licensing**
|
||||
>
|
||||
> Please see the [Spine Runtimes License](/spine-runtimes-license) before integrating the Spine Runtimes into your applications.
|
||||
|
||||
# Getting Started
|
||||
The spine-flutter runtime is implemented as a [Flutter FFI plugin](https://docs.flutter.dev/development/packages-and-plugins/developing-packages#plugin-ffi) on top of [spine-cpp](/spine-cpp). It supports all platforms supported by Flutter (desktop, Android, iOS, web), and supports all Spine features except tint black and screen blend mode.
|
||||
|
||||
|
||||
## Installation
|
||||
spine-flutter is supported from Flutter 3.16.0 onwards. To use spine-flutter in your Flutter project, add the following dependency to your project's `pubspec.yaml` file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
...
|
||||
spine_flutter: ^4.3.0
|
||||
```
|
||||
|
||||
See [spine_flutter on pub.dev](https://pub.dev/packages/spine_flutter) for the latest version.
|
||||
|
||||
Ensure that the `major.minor` version of spine-flutter matches the `major.minor` Spine Editor version you are exporting from. See [Spine Versioning](/spine-versioning#Synchronizing-versions) for more information.
|
||||
|
||||
In your `main()` function, add these two lines in the beginning to initialize the spine-flutter runtime:
|
||||
|
||||
```dart
|
||||
void main() async {
|
||||
await initSpineFlutter(enableMemoryDebugging: false);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** the `main()` method must be `async`.
|
||||
|
||||
## Samples
|
||||
The spine-flutter runtime includes several samples that showcase its feature set.
|
||||
|
||||
You can run the example project following these steps:
|
||||
|
||||
1. Install the [Flutter SDK](https://docs.flutter.dev/get-started/install), then run `flutter doctor` which will instruct you what other dependencies to install.
|
||||
2. Clone the spine-runtimes repository: `git clone https://github.com/esotericsoftware/spine-runtimes`
|
||||
3. Run the `setup.sh` script in the `spine-flutter/` folder. On Windows, you can use [Git Bash](https://gitforwindows.org/) included in Git for Window to run the `setup.sh` Bash script.
|
||||
|
||||
You can then open `spine-flutter` in an IDE or editor of your choice that supports Flutter, like [IntelliJ IDEA/Android Studio](https://docs.flutter.dev/get-started/editor?tab=androidstudio) or [Visual Studio Code](https://docs.flutter.dev/get-started/editor?tab=vscode) to inspect and run the example.
|
||||
|
||||
Alternatively, you can run the example from the [command line](https://docs.flutter.dev/get-started/test-drive?tab=terminal).
|
||||
|
||||
The example project contains the following examples:
|
||||
* [`example/lib/simple_animation.dart`](/git/spine-runtimes/spine-flutter/example/lib/simple_animation.dart): demonstrates the basic use of `SpineWidget` and `SpineWidgetController` to load an exported Spine skeleton, display it in the widget, and playback a specific animation.
|
||||
* [`example/lib/pause_play_animation.dart`](/git/spine-runtimes/spine-flutter/example/lib/pause_play_animation.dart): demonstrates how to pause and resume an animation.
|
||||
* [`example/lib/animation_state_events.dart`](/git/spine-runtimes/spine-flutter/example/lib/animation_state_events.dart): demonstrates how set a slot's color, how to queue multiple animations, and how to to listen for animation state events.
|
||||
* [`example/lib/debug_rendering.dart`](/git/spine-runtimes/spine-flutter/example/lib/debug_rendering.dart): shows how to perform custom drawing on top of the rendered skeleton via the `SpineWidgetController` `onAfterPaint` callback.
|
||||
* [`example/lib/dress_up.dart`](/git/spine-runtimes/spine-flutter/example/lib/dress_up.dart): demonstrates Spine's skins feature as well as rendering a skeleton to a thumbnail for use in a character creation UI.
|
||||
* [`example/lib/ik_following.dart`](/git/spine-runtimes/spine-flutter/example/lib/ik_following.dart): demonstrates how to let the user drag one of the skeleton's bones via mouse or touch input.
|
||||
* [`example/lib/physics.dart`](/git/spine-runtimes/spine-flutter/example/lib/physics.dart): demonstrates physics constraints in action.
|
||||
* [`example/lib/animated_login.dart`](/git/spine-runtimes/spine-flutter/example/lib/animated_login.dart): shows how to integrate spine animations into a login form UI.
|
||||
* [`example/lib/flame_example.dart`](/git/spine-runtimes/spine-flutter/example/lib/flame_example.dart): demonstrates how to write a simple [Flame](https://flame-engine.org/) component to use spine-flutter with the Flame game engine.
|
||||
|
||||
## Updating the spine-flutter Runtime
|
||||
Before updating your project's spine-flutter runtime, please consult our [guide on Spine editor and runtime version management](/spine-runtime-architecture#Versioning).
|
||||
|
||||
To update the spine-flutter runtime, simply modify the version string of the `spine_flutter` package in your `pubspec.yaml`.
|
||||
|
||||
> **Note:** If you change the `major.minor` version of the `spine_flutter` package, you have to re-export your Spine skeletons with the same Spine Editor `major.minor` version!
|
||||
|
||||
# Using spine-flutter
|
||||
The spine-flutter runtime is an idiomatic [Dart FFI wrapper](https://dart.dev/guides/libraries/c-interop) around the generic [spine-cpp](/spine-cpp) which supports loading, playback and manipulation of animations created with Spine. The spine-flutter runtime exposes almost all of the spine-cpp API as idiomatic Dart and provides Flutter and [Flame](https://flame-engine.org/) specific classes to easily display and interact with Spine skeletons.
|
||||
|
||||
The spine-flutter runtime supports all Spine features except tint black and screen blend mode.
|
||||
|
||||
## Asset Management
|
||||
### Exporting for spine-flutter
|
||||

|
||||
Please follow the instructions in the Spine User Guide on how to
|
||||
|
||||
1. [Export skeleton & animation data](/spine-export)
|
||||
2. [Export texture atlases containing the images of your skeleton](/spine-texture-packer)
|
||||
|
||||
An export of the skeleton data and texture atlas of your skeleton will yield the following files:
|
||||
|
||||

|
||||
|
||||
1. `skeleton-name.json` or `skeleton-name.skel`, containing your skeleton and animation data.
|
||||
2. `skeleton-name.atlas`, containing information about the texture atlas.
|
||||
3. One or more `.png` files, each representing on page of your texture atlas containing the packed images your skeleton uses.
|
||||
|
||||
> **Note**: You should prefer binary skeleton exports over JSON exports, as they are smaller in size and faster to load.
|
||||
|
||||
The files can be loaded via spine-flutter classes like `AtlasFlutter`, `SkeletonDataFlutter`, `SkeletonDrawableFlutter`, `SpineWidget`.
|
||||
|
||||
> **Note**: The spine-flutter runtime currently does not support atlases exported using pre-multiplied alpha due to technical limitations in Flutter. Flutter's rendering engine ensures that common non-premultiplied alpha artifacts are avoided.
|
||||
|
||||
### Updating Spine Assets
|
||||
During development, you may frequently update your Spine skeleton data and texture atlas files. You can simply overwrite these source files (`.json`, `.skel`, `.atlas`, `.png`) by re-exporting from the Spine Editor and replacing the existing files in your Flutter project.
|
||||
|
||||
Ensure that the `major.minor` version of spine-flutter matches the `major.minor` Spine Editor version you are exporting from. See [Spine Versioning](/spine-versioning#Synchronizing-versions) for more information.
|
||||
|
||||
## Core classes
|
||||
The spine-flutter API is built on top of the generic [spine-cpp](/spine-cpp) runtime, which provides platform independent core classes and algorithms to load, query, modify, and animate Spine skeletons. The core classes are wrapped via Dart FFI and exposed as idiomatic Dart classes.
|
||||
|
||||
Here, we will briefly discuss the most important core classes that you will encounter in your day-to-day use of spine-flutter. Please consult the [Spine Runtimes Guide](/spine-runtimes-guide) for a detailed overview of the Spine Runtimes architecture, core classes, and API usage.
|
||||
|
||||
### spine-dart classes
|
||||
|
||||
The [`Atlas`](/git/spine-runtimes/spine-flutter/lib/generated/atlas.dart) class stores the data loaded from an `.atlas` file. This is the base class used internally by spine-flutter.
|
||||
|
||||
The [`SkeletonData`](/git/spine-runtimes/spine-flutter/lib/generated/skeleton_data.dart) class stores the data loaded from a `.json` or `.skel` skeleton file. The skeleton data contains information about the bone hierarchy, slots, attachments, constraints, skins, and animations. A `SkeletonData` instance is usually loaded by also providing an `Atlas` from which it sources the images to be used by the skeleton it represents. It serves as a blueprint for creating `Skeleton` instances. Multiple skeletons can be instantiated from the same atlas and skeleton data, which then share the loaded data, minimizing both load times and memory consumption at runtime.
|
||||
|
||||
The [`Skeleton`](/git/spine-runtimes/spine-flutter/lib/generated/skeleton.dart) class stores an instance of a skeleton, created from a `SkeletonData` instance. A skeleton stores its current pose, that is the position of bones and the current configuration of slots, attachments, and active skin. The current pose can be computed by either manually modifying the bone hierarchy, or, more commonly, by applying animations via an `AnimationState`.
|
||||
|
||||
The [`AnimationState`](/git/spine-runtimes/spine-flutter/lib/generated/animation_state.dart) class is responsible for keeping track of which animation(s) should be applied to a skeleton, advancing and mixing those animations based on the elapsed time between the last and current rendering frame, and applying the animations to a skeleton instance, thereby setting its current pose. The `AnimationState` queries an [`AnimationStateData`](/git/spine-runtimes/spine-flutter/lib/generated/animation_state_data.dart) instance to retrieve mixing times between animations, or fetches the default mix time if no mixing time is available for a pair of animations.
|
||||
|
||||
### Flutter-specific classes
|
||||
|
||||
spine-flutter provides Flutter-specific wrapper classes that handle texture loading, rendering, and lifecycle management:
|
||||
|
||||
The [`AtlasFlutter`](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L28) class extends the base `Atlas` class and additionally manages Flutter `Image` objects and `Paint` objects for each atlas page and blend mode. It provides static methods to load atlases from assets, files, or URLs:
|
||||
|
||||
```dart
|
||||
final atlas = await AtlasFlutter.fromAsset("assets/skeleton.atlas");
|
||||
```
|
||||
|
||||
The [`SkeletonDataFlutter`](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L122) class extends the base `SkeletonData` class and provides convenient loading methods that work with `AtlasFlutter`:
|
||||
|
||||
```dart
|
||||
final skeletonData = await SkeletonDataFlutter.fromAsset(atlas, "assets/skeleton.skel");
|
||||
```
|
||||
|
||||
The [`SkeletonDrawableFlutter`](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L304) class extends the base `SkeletonDrawable` and provides Flutter-specific rendering capabilities, including the ability to render to `Canvas`, `PictureRecorder`, PNG, or raw image data.
|
||||
|
||||
The spine-flutter runtime builds on top of these core classes.
|
||||
|
||||
## SpineWidget
|
||||

|
||||
|
||||
A [`SpineWidget`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L261) is a [StatefulWidget](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) responsible for loading and displaying a Spine skeleton. At a minimum, the widget needs to know from where to load the skeleton and atlas files, and it must receive a `SpineWidgetController` instance that is responsible for modifying the state of the widget, such as setting an animation, or changing the skin of the skeleton.
|
||||
|
||||
In the simplest case, a `SpineWidget` can be instantiated inside another widget's `build()` method like this:
|
||||
|
||||
```dart
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = SpineWidgetController(onInitialized: (controller) {
|
||||
// Set the walk animation on track 0, let it loop
|
||||
controller.animationState.setAnimation(0, "walk", true);
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Simple Animation')),
|
||||
body: SpineWidget.fromAsset("assets/spineboy.atlas", "assets/spineboy-pro.skel", controller)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Upon instantiation, the `SpineWidget` will asynchronously load the specified files and construct the underlying core class instances from them, namely instances of `AtlasFlutter`, `SkeletonDataFlutter`, `Skeleton`, `AnimationStateData`, and `AnimationState`.
|
||||
|
||||
Once loading is complete, the `SpineWidgetController` is called, allowing it to modify the state of the widget, such as setting one or more animations, manipulating the bone hierarchy, or modifying the skin of the skeleton. See the section on `SpineWidgetController` below.
|
||||
|
||||
The `SpineWidget` class provides multiple static factory methods to load skeleton and atlas files from different sources:
|
||||
|
||||
* `SpineWidget.fromAsset()` loads files from the root bundle, or a provided bundle.
|
||||
* `SpineWidget.fromFile()` loads files from the file system.
|
||||
* `SpineWidget.fromHttp()` loads files from URLs.
|
||||
* `SpineWidget.fromDrawable()` constructs a widget from a `SkeletonDrawableFlutter`. This is useful when the skeleton data should be preloaded, cached, and/or shared between `SpineWidget` instances. See the section "Pre-loading and sharing skeleton data" below.
|
||||
|
||||
All factory methods have optional arguments that let you further define how the Spine skeleton is fitted and aligned inside the widget, and how the widget is sized.
|
||||
|
||||
* `fit`, the [BoxFit](https://api.flutter.dev/flutter/painting/BoxFit.html) to use to fit the skeleton inside the widget.
|
||||
* `alignment`, the [Alignment](https://api.flutter.dev/flutter/painting/Alignment-class.html) to use to align the skeleton inside the widget.
|
||||
* `BoundsProvider`, used to calculate the pixel size of the bounding box to be used for the skeleton when computing the fit and alignment. By default, the skeleton's setup pose bounding box is used. See the class documentation for [`SetupPoseBounds`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L173), [`RawBounds`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L183), and [`SkinAndAnimationBounds`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L196) for additional information.
|
||||
* `sizedByBounds`, defines whether to size the widgets by the bounds computed by the `BoundsProvider`, or have it sized by its parent widget.
|
||||
|
||||
## Pre-loading and sharing skeleton data
|
||||
Pre-loading allows you to share atlas and skeleton data between multiple `SpineWidget` instances, saving both load time and memory. The key is understanding the ownership parameter when creating drawables.
|
||||
|
||||
### Sharing data across multiple widgets
|
||||
When you want multiple widgets to share the same atlas and skeleton data:
|
||||
|
||||
```dart
|
||||
// Pre-load the atlas and skeleton data once
|
||||
final atlas = await AtlasFlutter.fromAsset("assets/test.atlas");
|
||||
final skeletonData = await SkeletonDataFlutter.fromAsset(atlas, "assets/test.skel");
|
||||
|
||||
// Create drawables without taking ownership (pass false)
|
||||
final drawable1 = SkeletonDrawableFlutter(atlas, skeletonData, false);
|
||||
final drawable2 = SkeletonDrawableFlutter(atlas, skeletonData, false);
|
||||
|
||||
// Use in multiple widgets
|
||||
SpineWidget.fromDrawable(drawable1, controller1);
|
||||
SpineWidget.fromDrawable(drawable2, controller2);
|
||||
```
|
||||
|
||||
With `ownsAtlasAndSkeletonData: false`, the drawables will NOT dispose the atlas and skeleton data when they are disposed. You must manually manage their lifecycle:
|
||||
|
||||
```dart
|
||||
// Dispose drawables when done
|
||||
drawable1.dispose();
|
||||
drawable2.dispose();
|
||||
|
||||
// Manually dispose shared data when completely done
|
||||
skeletonData.dispose();
|
||||
atlas.dispose();
|
||||
```
|
||||
|
||||
### Single-use with ownership
|
||||
If you only need one widget and want automatic cleanup:
|
||||
|
||||
```dart
|
||||
final atlas = await AtlasFlutter.fromAsset("assets/test.atlas");
|
||||
final skeletonData = await SkeletonDataFlutter.fromAsset(atlas, "assets/test.skel");
|
||||
|
||||
// Create drawable with ownership (pass true)
|
||||
final drawable = SkeletonDrawableFlutter(atlas, skeletonData, true);
|
||||
SpineWidget.fromDrawable(drawable, controller);
|
||||
|
||||
// When disposed, this will also dispose atlas and skeletonData
|
||||
drawable.dispose();
|
||||
```
|
||||
|
||||
## SpineWidgetController
|
||||
A [`SpineWidgetController`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L64) controls how the skeleton of a `SpineWidget` is animated and rendered. The controller is provided with a set of optional callbacks as constructor arguments, which are called at specific times during the life-time of the `SpineWidget`.
|
||||
|
||||
The controller exposes the skeleton state through getters returning Spine Runtimes API objects such as the `AtlasFlutter`, `SkeletonDataFlutter`, `Skeleton`, and `AnimationState`, through which the state can be manipulated. See the [Spine Runtimes Guide](/spine-runtimes-guide), and the [class documentation](/git/spine-runtimes/spine-flutter/lib/generated/api.dart) for more information.
|
||||
|
||||
Upon initialization of a `SpineWidget`, the controller's `onInitialized()` callback method is invoked once. This method can be used to setup the initial animation(s) to be played back, or set the skin of the skeleton, among other things.
|
||||
|
||||
After initialization is complete, the `SpineWidget` is rendered continuously at the screen refresh rate. Each frame, the `AnimationState` is updated based on the currently queued animations, and applied to the `Skeleton`.
|
||||
|
||||
Next, the optional `onBeforeUpdateWorldTransforms()` callback is invoked, which can modify the skeleton before its current pose is calculated using `Skeleton.updateWorldTransform()`.
|
||||
|
||||
After the current pose has been calculated, the optional `onAfterUpdateWorldTransforms()` callback is invoked, which can further modify the current pose before the skeleton is rendered. This is a good place to manually position bones.
|
||||
|
||||
Before the skeleton is rendered by the `SpineWidget`, the optional `onBeforePaint()` callback is invoked, which allows rendering backgrounds or other objects that should go behind the skeleton on the [`Canvas`](https://api.flutter.dev/flutter/dart-ui/Canvas-class.html).
|
||||
|
||||
After the `SpineWidget` has rendered the current skeleton pose to the `Canvas`, the optional `onAfterPaint()` callback is invoked, which allows rendering additional objects on top of the skeleton.
|
||||
|
||||
By default, the widget updates and renders the skeleton every frame. The `SpineWidgetController.pause()` method can be used to pause updating and rendering the skeleton. The `SpineWidgetController.resume()` method resumes updating and rendering the skeleton. The `SpineWidgetController.isPlaying()` getter reports the current playback state. See the [`example/lib/pause_play_animation.dart`](/git/spine-runtimes/spine-flutter/example/lib/pause_play_animation.dart) example.
|
||||
|
||||
## SkeletonDrawableFlutter
|
||||
A `SkeletonDrawableFlutter` bundles loading, storing, updating, and rendering a `Skeleton` and its associated `AnimationState` into a single, easy to use class. The class can be used as the basis for a custom widget implementation. The `SpineWidget` encapsulates the state of the skeleton it displays via an instance of a `SkeletonDrawableFlutter`.
|
||||
|
||||
Use the static `fromAsset()`, `fromFile()`, or `fromHttp()` methods to construct a `SkeletonDrawableFlutter` from file assets. To share `AtlasFlutter` and `SkeletonDataFlutter` among multiple `SkeletonDrawableFlutter` instances, instantiate the drawables via the constructor, passing the same atlas and skeleton data to each of them.
|
||||
|
||||
The `SkeletonDrawableFlutter` exposes the `atlasFlutter`, `skeletonData`, `skeleton`, `animationStateData`, and `animationState` to query, modify, and animate the skeleton.
|
||||
|
||||
To animate the skeleton, queue animations on one or more tracks via the `AnimationState` API, such as `AnimationState.setAnimation()` or `AnimationState.addAnimation()`.
|
||||
|
||||
To update the animation state, apply it to the skeleton, and update the current skeleton pose, call the `SkeletonDrawableFlutter.update()` method, providing it a delta time in seconds to advance the animations.
|
||||
|
||||
To render the current pose of the skeleton, use the rendering methods `SkeletonDrawableFlutter.renderFlutter()`, `SkeletonDrawableFlutter.renderToCanvas()`, `SkeletonDrawableFlutter.renderToPictureRecorder()`, `SkeletonDrawableFlutter.renderToPng()`, or `SkeletonDrawableFlutter.renderToRawImageData()`.
|
||||
|
||||
The `SkeletonDrawableFlutter` stores objects allocated on the native heap. The native objects need to be manually disposed of via a call to `SkeletonDrawableFlutter.dispose()` if the `SkeletonDrawableFlutter` is no longer needed. Not doing so will result in a native memory leak.
|
||||
|
||||
> **Note:** when using `SpineWidget`, you do not have to manually dispose of the `SkeletonDrawableFlutter` the widget uses. The widget will dispose the `SkeletonDrawableFlutter` when it is disposed itself.
|
||||
|
||||
## Applying Animations
|
||||
Applying animations to a skeleton displayed by a `SpineWidget` is done through the `AnimationState` in the callbacks of a `SpineWidgetController`.
|
||||
|
||||
> **Note:** See [Applying Animations](/spine-applying-animations#AnimationState-API) in the Spine Runtimes Guide for more in-depth information, specifically about animation tracks and animation queueing.
|
||||
|
||||
To set a specific animation on track 0, call `AnimationState.setAnimation()`:
|
||||
|
||||
```dart
|
||||
final controller = SpineWidgetController(onInitialized: (controller) {
|
||||
// Set the walk animation on track 0, let it loop
|
||||
controller.animationState.setAnimation(0, "walk", true);
|
||||
});
|
||||
```
|
||||
|
||||
The first parameter specifies the track, the second parameter is the name of the animation, and the third parameter defines whether to loop the animation.
|
||||
|
||||
You can queue multiple animations:
|
||||
|
||||
```dart
|
||||
controller.animationState.setAnimation(0, "walk", true);
|
||||
controller.animationState.addAnimation(0, "jump", false, 2);
|
||||
controller.animationState.addAnimation(0, "run", true, 0);
|
||||
```
|
||||
|
||||
The first parameter to `addAnimation()` is the track. The second parameter is the name of the animation. The third parameter specifies whether to loop the animation. The final parameter defines the delay in seconds, after which this animation should replace the previous animation on the track.
|
||||
|
||||
In the example above, the `"walk"` animation is played back first. 2 seconds later, the `"jump"` animation is played back once, followed by a transition to the `"run"` animation, which will be looped.
|
||||
|
||||
When transitioning from one animation to another, `AnimationState` will mix the animations for a specificable duration. These mix times are defined in an `AnimationStateData` instance, from which the `AnimationState` retrieves mix times.
|
||||
|
||||
The `AnimationStateData` instance is also available through the controller. You can set the default mix time, or the mix time for a specific pair of animations:
|
||||
|
||||
```dart
|
||||
controller.animationStateData.defaultMix = 0.2;
|
||||
controller.animationStateData.setMix("walk", "jump", 0.1);
|
||||
```
|
||||
|
||||
When setting or adding an animation, a `TrackEntry` object is returned, which allows further modification of that animation's playback. For example, you can set the track entry to reverse the animation playback:
|
||||
|
||||
```dart
|
||||
final entry = controller.animationState.setAnimation(0, "walk", true);
|
||||
entry.reverse = true;
|
||||
```
|
||||
|
||||
See the [`TrackEntry` class documentation](/git/spine-runtimes/spine-flutter/lib/generated/track_entry.dart) for more options.
|
||||
|
||||
> **Note:** Do not hold on to `TrackEntry` instances outside the function you are using them in. Track entries are re-used internally and will thus become invalid once the animation it represents has been completed.
|
||||
|
||||
You can set or queue empty animations on an animation track to smoothly reset the skeleton back to its setup pose:
|
||||
|
||||
```dart
|
||||
controller.animationState.setEmptyAnimation(0, 0.5);
|
||||
controller.animationState.addEmptyAnimation(0, 0.5, 0.5);
|
||||
```
|
||||
|
||||
The first parameter to `setEmptyAnimation()` specifies the track. The second parameter specifies the mix duration in seconds used to mix out the previous animation and mix in the "empty" animation.
|
||||
|
||||
The first parameter to `addEmptyAnimation()` specifies the track. The second parameter is the mix duration. The third parameter specifies the delay in seconds, after which the empty animation should replace the previous animation on the track via mixing.
|
||||
|
||||
All animations on a track can be cleared immediately via `AnimationState.clearTrack()`. To clear all tracks at once, `AnimationState.clearTracks()` can be used. This will leave the skeleton in the last pose it was in.
|
||||
|
||||
To reset the pose of a skeleton to the setup pose, use `Skeleton.setupPose()`:
|
||||
|
||||
```dart
|
||||
controller.skeleton.setupPose();
|
||||
```
|
||||
|
||||
This will reset both the bones and slots to their setup pose configuration. Use `Skeleton.setupPoseSlots()` to only reset the slots to their setup pose configuration.
|
||||
|
||||
## AnimationState Events
|
||||
An `AnimationState` emits events during the life-cycle of an animation that is being played back. You can listen for this events to react as needed. The Spine Runtimes API defines the following [event types](/git/spine-runtimes/spine-flutter/lib/generated/event_type.dart):
|
||||
|
||||
* `start`: emitted when an animation is started.
|
||||
* `interrupt`: emitted when an animation's track was cleared, or a new animation was set.
|
||||
* `complete`: emitted when an animation completes a loop.
|
||||
* `end`: emitted when an animation will never be applied again.
|
||||
* `dispose`: emitted when the animation's track entry is disposed.
|
||||
* `event`: emitted when a user defined [event](/spine-events#Events) happened.
|
||||
|
||||
To receive events, you can register an [`AnimationStateListener`](/git/spine-runtimes/spine-flutter/lib/spine_dart.dart#L229) callback with either the `AnimationState` to receive events across all animations, or with the `TrackEntry` of a specific animation queued for playback:
|
||||
|
||||
```dart
|
||||
final entry = controller.animationState.setAnimation(0, "walk", true);
|
||||
entry.setListener((type, trackEntry, event) {
|
||||
if (type == EventType.event) {
|
||||
print("User defined event: ${event?.data.name}");
|
||||
}
|
||||
});
|
||||
|
||||
controller.animationState.setListener((type, trackEntry, event) {
|
||||
print("Animation state event $type");
|
||||
});
|
||||
```
|
||||
|
||||
See the [`example/lib/animation_state_events.dart`](/git/spine-runtimes/spine-flutter/example/lib/animation_state_events.dart) example.
|
||||
|
||||
## Skins
|
||||

|
||||
|
||||
Many applications and games allow users to create custom avatars out of many individual items, such as hair, eyes, pants, or accessories like earrings or bags. With Spine, this can be achieved by [mixing and matching skins](/spine-examples-mix-and-match).
|
||||
|
||||
You can create custom skins from other skins like this:
|
||||
|
||||
```dart
|
||||
final data = controller.skeletonData;
|
||||
final skeleton = controller.skeleton;
|
||||
final customSkin = Skin("custom-skin");
|
||||
customSkin.addSkin(data.findSkin("skin-base")!);
|
||||
customSkin.addSkin(data.findSkin("nose/short")!);
|
||||
customSkin.addSkin(data.findSkin("eyelids/girly")!);
|
||||
customSkin.addSkin(data.findSkin("eyes/violet")!);
|
||||
customSkin.addSkin(data.findSkin("hair/brown")!);
|
||||
customSkin.addSkin(data.findSkin("clothes/hoodie-orange")!);
|
||||
customSkin.addSkin(data.findSkin("legs/pants-jeans")!);
|
||||
customSkin.addSkin(data.findSkin("accessories/bag")!);
|
||||
customSkin.addSkin(data.findSkin("accessories/hat-red-yellow")!);
|
||||
skeleton.setSkin2(customSkin);
|
||||
skeleton.setupPoseSlots();
|
||||
```
|
||||
|
||||
Create a custom skin with the `Skin()` constructor.
|
||||
|
||||
Next, fetch the `SkeletonData` from the controller. It is used to look up skins by name via `SkeletonData.findSkin()`.
|
||||
|
||||
Add all the skins you want to combine into the new custom skin via `Skin.addSkin()`.
|
||||
|
||||
Finally, set the new skin on the `Skeleton` using `Skeleton.setSkin2()` and call `Skeleton.setupPoseSlots()` to ensure no attachments from previous skins and/or animations are left over.
|
||||
|
||||
> **Note:** A `Skin` wraps an underlying C++ object. It needs to be manually disposed via a call to `Skin.dispose()` when it is no longer in use.
|
||||
|
||||
See the [`example/lib/dress_up.dart`](/git/spine-runtimes/spine-flutter/example/lib/dress_up.dart) example, which also demonstrate how to render thumbnail previews of skins using `SkeletonDrawableFlutter`.
|
||||
|
||||
## Setting Bone Transforms
|
||||

|
||||
|
||||
When authoring a skeleton in the Spine Editor, the skeleton is defined in what is called the skeleton coordinate system. This coordinate system may not align with the coordinate system of the `SpineWidget` the skeleton is rendered by. Touch coordinates relative to the `SpineWidget` need thus be converted to the skeleton coordinate system, e.g. if a user should be able to move a bone by touch.
|
||||
|
||||
The `SpineWidgetController` offers the method `toSkeletonCoordinates()` which takes an [`Offset`](https://api.flutter.dev/flutter/dart-ui/Offset-class.html) relative to the `SpineWidget` it is associated with, and converts it to the skeleton's coordinate system.
|
||||
|
||||
See the [`example/lib/ik_following.dart`](/git/spine-runtimes/spine-flutter/example/lib/ik_following.dart) example.
|
||||
|
||||
## Flame Integration
|
||||

|
||||
|
||||
spine-flutter includes an example that shows how to load and renderer Spine skeletons in [Flame Engine](https://flame-engine.org/). See the [`example/lib/flame_example.dart`](/git/spine-runtimes/spine-flutter/example/lib/flame_example.dart) source file.
|
||||
|
||||
The example features a simple `SpineComponent` that extends Flame's `PositionComponent`. The `SpineComponent` can be instantiated through the static `SpineComponent.fromAsset()` method, or through the constructor.
|
||||
|
||||
The static method can be used as a quick, one-off loading mechanism when the skeleton and atlas data doesn't have to be shared with other components. The example contains a `FlameGame` implementation called `SimpleFlameExample` which demonstrates this simple way of getting a Spine skeleton on screen as part of a Flame game.
|
||||
|
||||
Creating a `SpineComponent` via the constructor allows more fine-grained management of the data loading and sharing by taking a `SkeletonDrawableFlutter`. E.g. you can pre-load the skeleton data and atlas, then share it across multiple `SpineComponent` instances. This will both improve memory usage and rendering performance, as data is shared, and rendering can be batched. See the `FlameGame` implementation called `PreloadAndShareSpineDataExample` for an example.
|
||||
|
||||
By design, Flame can not know when a component has reached its end of life. However, a `SpineComponent` handles native resources that need to be released at the end of its life. It is thus your responsibility to either call `SpineComponent.dispose()` if a `SpineComponent` is no longer in use. If the `SpineComponent` was constructed from a `SkeletonDrawableFlutter`, you may also have to manually dispose the `SkeletonDataFlutter` and `AtlasFlutter` from which it was constructed, like in the `PreloadAndShareSpineDataExample` example.
|
||||
|
||||
# Spine Runtimes API access
|
||||
spine-flutter maps almost all of the Spine Runtime API to Dart. Objects returned by `SpineWidgetController` or `SkeletonDrawableFlutter`, like `Skeleton` or `AnimationState` are 1:1 translations of the spine-cpp API to Dart. You can thus apply almost all of the materials in the generic [Spine Runtimes Guide](/spine-runtimes-guide) to your Dart code.
|
||||
|
||||
Due to the nature of the spine-cpp to Dart FFI bridge, there are some considerations:
|
||||
|
||||
* Arrays returned by the API (like `ArrayFloat`, `ArrayInt`) are direct wrappers around native memory. They provide List-like access to the underlying C++ data and modifications through the array's methods will affect the native data.
|
||||
* You can create bones and slots using their factory constructors (e.g., `Bone(boneData, parent)`, `Slot(slotData, skeleton)`). However, you are responsible for disposing any manually created objects.
|
||||
* The C++ class hierarchy is fully translated to Dart, including all timeline and constraint classes with proper inheritance relationships and the same nullability patterns as the Java reference implementation.
|
||||
|
||||
## Development
|
||||
|
||||
This section details the development workflow and architecture of spine-flutter, including code generation, building, and testing.
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
spine-flutter is built on a multi-layer architecture:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ spine-cpp │
|
||||
│ Core C++ Spine runtime implementation │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ spine-c │
|
||||
│ C wrapper API around spine-cpp │
|
||||
│ (Auto-generated + manual extensions) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ FFI Bindings Layer │
|
||||
│ Low-level Dart FFI bindings to spine-c │
|
||||
│ (Generated by Dart's ffigen) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Dart Wrapper Classes │
|
||||
│ Idiomatic, type-safe Dart API │
|
||||
│ (Generated by custom codegen) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Flutter Integration Layer │
|
||||
│ Flutter-specific classes (SpineWidget, rendering) │
|
||||
│ (Hand-written) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Binding Generation
|
||||
|
||||
The binding generation process is automated through the `generate-bindings.sh` script, which orchestrates several steps:
|
||||
|
||||
#### 1. Generate spine-c Bindings
|
||||
First, the spine-c bindings are generated from spine-cpp headers:
|
||||
```bash
|
||||
cd ../spine-c && ./build.sh codegen
|
||||
```
|
||||
|
||||
This uses Clang's AST to parse C++ headers and generate:
|
||||
- C wrapper functions for all spine-cpp classes
|
||||
- Type information in JSON format
|
||||
- Header and implementation files
|
||||
|
||||
#### 2. Copy Source Files
|
||||
The `setup.sh` script copies necessary source files:
|
||||
- spine-cpp sources to `src/spine-cpp/`
|
||||
- spine-c sources to `src/spine-c/`
|
||||
- Platform-specific setup for iOS/macOS CocoaPods
|
||||
|
||||
#### 3. Generate Dart Bindings
|
||||
The custom TypeScript-based code generator creates idiomatic Dart wrappers:
|
||||
```bash
|
||||
npx tsx codegen/src/index.ts
|
||||
```
|
||||
|
||||
This generates:
|
||||
- Dart wrapper classes for all Spine types (`lib/generated/*.dart`)
|
||||
- FFI bindings configuration (`ffigen.yaml`)
|
||||
- Low-level FFI bindings (`spine_dart_bindings_generated.dart`)
|
||||
|
||||
See the [codegen README](codegen/README.md) for detailed information about the code generation architecture.
|
||||
|
||||
#### 4. Build Test Library
|
||||
For headless testing, a native shared library is built:
|
||||
```bash
|
||||
cd test && ./build.sh
|
||||
```
|
||||
|
||||
### WASM Compilation
|
||||
|
||||
Web platform support requires compiling spine-cpp to WebAssembly:
|
||||
|
||||
```bash
|
||||
./compile-wasm.sh
|
||||
```
|
||||
|
||||
This script:
|
||||
1. Creates the assets directory structure
|
||||
2. Compiles all spine-cpp and spine-c sources using Emscripten
|
||||
3. Generates `libspine_flutter.js` and `libspine_flutter.wasm`
|
||||
4. Places them in `lib/assets/` for web platform loading
|
||||
|
||||
Key Emscripten flags:
|
||||
- `-O2`: Optimization level that preserves function names
|
||||
- `--closure 1`: Closure compiler for size optimization
|
||||
- `-s MODULARIZE=1`: Creates a module-based output
|
||||
- `-s EXPORT_ALL=1`: Exports all functions for FFI access
|
||||
- `-s ALLOW_MEMORY_GROWTH=1`: Dynamic memory allocation
|
||||
|
||||
### Testing
|
||||
|
||||
spine-flutter includes both Flutter widget tests and headless Dart tests:
|
||||
|
||||
#### Headless Testing
|
||||
Pure Dart tests that don't require Flutter:
|
||||
```bash
|
||||
cd test
|
||||
dart headless_test.dart
|
||||
```
|
||||
|
||||
These tests verify:
|
||||
- Atlas and skeleton data loading
|
||||
- Core API functionality
|
||||
- Memory management
|
||||
- Extension functions
|
||||
|
||||
#### Flutter Testing
|
||||
Widget and integration tests:
|
||||
```bash
|
||||
flutter test
|
||||
```
|
||||
|
||||
#### Running Examples
|
||||
The example app showcases all features:
|
||||
```bash
|
||||
cd example
|
||||
flutter run
|
||||
```
|
||||
|
||||
### Build Scripts
|
||||
|
||||
Several utility scripts facilitate development:
|
||||
|
||||
- **`setup.sh`**: Prepares the project structure
|
||||
- Copies spine-cpp sources for native builds
|
||||
- Sets up iOS/macOS specific files
|
||||
- Creates necessary directories
|
||||
|
||||
- **`clean.sh`**: Cleans all build artifacts
|
||||
- Removes CocoaPods installations
|
||||
- Cleans Flutter dependencies
|
||||
- Deletes generated files
|
||||
- Useful for resolving build issues
|
||||
|
||||
- **`generate-bindings.sh`**: Complete binding generation
|
||||
- Runs spine-c code generation
|
||||
- Executes Dart code generation
|
||||
- Builds test libraries
|
||||
|
||||
- **`compile-wasm.sh`**: Web platform compilation
|
||||
- Compiles C++ to WebAssembly
|
||||
- Generates JavaScript module
|
||||
|
||||
> **Note:** On Windows, use [Git Bash](https://gitforwindows.org/) to run these bash scripts.
|
||||
|
||||
### Code Generation Details
|
||||
|
||||
The code generation system is central to spine-flutter's maintainability. For detailed information about:
|
||||
- How C++ types are transformed to Dart
|
||||
- Nullability handling
|
||||
- RTTI-based type instantiation
|
||||
- Method overloading resolution
|
||||
- Extension system architecture
|
||||
|
||||
Please refer to the comprehensive [codegen README](codegen/README.md).
|
||||
|
||||
### Contributing
|
||||
|
||||
When contributing to spine-flutter:
|
||||
|
||||
1. **Core API changes**: Modify spine-cpp, then regenerate bindings
|
||||
2. **Extension functions**: Add to spine-c `extensions.h/.cpp`, then wrap in `spine_dart.dart`
|
||||
3. **Flutter integration**: Modify Flutter-specific classes in `lib/`
|
||||
4. **Examples**: Add to the example app to showcase new features
|
||||
|
||||
Always run the full test suite and ensure examples work on all platforms before submitting changes.
|
||||
@ -1,370 +0,0 @@
|
||||
# spine-flutter Runtime Documentation
|
||||
|
||||
> **Licensing**
|
||||
>
|
||||
> Please see the [Spine Runtimes License](/spine-runtimes-license) before integrating the Spine Runtimes into your applications.
|
||||
|
||||
# Getting Started
|
||||
The spine-flutter runtime is implemented as a [Flutter FFI plugin](https://docs.flutter.dev/development/packages-and-plugins/developing-packages#plugin-ffi) on top of [spine-cpp](/spine-cpp). It supports all platforms supported by Flutter (desktop, Android, iOS, web), and supports all Spine features except tint black and screen blend mode.
|
||||
|
||||
|
||||
## Installation
|
||||
spine-flutter is supported from Flutter 3.10.5 onwards. To use spine-flutter in your Flutter project, add the following dependency to your project's `pubspec.yaml` file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
...
|
||||
spine_flutter: ^4.2.36
|
||||
```
|
||||
|
||||
See [spine_flutter on pub.dev](https://pub.dev/packages/spine_flutter) for the latest version.
|
||||
|
||||
Ensure that the `major.minor` version of spine-flutter matches the `major.minor` Spine Editor version you are exporting from. See [Spine Versioning](/spine-versioning#Synchronizing-versions) for more information.
|
||||
|
||||
In your `main()` function, add these two lines in the beginning to initialize the spine-flutter runtime:
|
||||
|
||||
```dart
|
||||
void main() async {
|
||||
await initSpineFlutter(enableMemoryDebugging: false);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** the `main()` method must be `async`.
|
||||
|
||||
## Samples
|
||||
The spine-flutter runtime includes several samples that showcase its feature set.
|
||||
|
||||
You can run the example project following these steps:
|
||||
|
||||
1. Install the [Flutter SDK](https://docs.flutter.dev/get-started/install), then run `flutter doctor` which will instruct you what other dependencies to install.
|
||||
2. Clone the spine-runtimes repository: `git clone https://github.com/esotericsoftware/spine-runtimes`
|
||||
3. Run the `setup.sh` script in the `spine-flutter/` folder. On Windows, you can use [Git Bash](https://gitforwindows.org/) included in Git for Window to run the `setup.sh` Bash script.
|
||||
|
||||
You can then open `spine-flutter` in an IDE or editor of your choice that supports Flutter, like [IntelliJ IDEA/Android Studio](https://docs.flutter.dev/get-started/editor?tab=androidstudio) or [Visual Studio Code](https://docs.flutter.dev/get-started/editor?tab=vscode) to inspect and run the example.
|
||||
|
||||
Alternatively, you can run the example from the [command line](https://docs.flutter.dev/get-started/test-drive?tab=terminal).
|
||||
|
||||
The example project contains the following examples:
|
||||
* [`example/lib/simple_animation.dart`](/git/spine-runtimes/spine-flutter/example/lib/simple_animation.dart): demonstrates the basic use of `SpineWidget` and `SpineWidgetController` to load an exported Spine skeleton, display it in the widget, and playback a specific animation.
|
||||
* [`example/lib/pause_play_animation.dart`](/git/spine-runtimes/spine-flutter/example/lib/pause_play_animation.dart): demonstrates how to pause and resume an animation.
|
||||
* [`example/lib/animation_state_events`](/git/spine-runtimes/spine-flutter/example/lib/animation_state_events.dart): demonstrates how set a slot's color, how to queue multiple animations, and how to to listen for animation state events.
|
||||
* [`example/lib/debug_rendering.dart`](/git/spine-runtimes/spine-flutter/example/lib/debug_rendering.dart): shows how to perform custom drawing on top of the rendered skeleton via the `SpineWidgetController` `onAfterPaint` callback.
|
||||
* [`example/lib/dress_up.dart`](/git/spine-runtimes/spine-flutter/example/lib/dress_up.dart): demonstrates Spine's skins feature as well as rendering a skeleton to a thumbnail for use in a character creation UI.
|
||||
* [`example/lib/ik_following.dart`](/git/spine-runtimes/spine-flutter/example/lib/ik_following.dart): demonstrates how to let the user drag one of the skeleton's bones via mouse or touch input.
|
||||
* [`example/lib/flame_example.dart`](/git/spine-runtimes/spine-flutter/example/lib/flame_example.dart): demonstrates how to write a simple [Flame](https://flame-engine.org/) component to use spine-flutter with the Flame game engine.
|
||||
|
||||
## Updating the spine-flutter Runtime
|
||||
Before updating your project's spine-flutter runtime, please consult our [guide on Spine editor and runtime version management](/spine-runtime-architecture#Versioning).
|
||||
|
||||
To update the spine-flutter runtime, simply modify the version string of the `spine_flutter` package in your `pubspec.yaml`.
|
||||
|
||||
> **Note:** If you change the `major.minor` version of the `spine_flutter` package, you have to re-export your Spine skeletons with the same Spine Editor `major.minor` version!
|
||||
|
||||
# Using spine-flutter
|
||||
The spine-flutter runtime is an idiomatic [Dart FFI wrapper](https://dart.dev/guides/libraries/c-interop) around the generic [spine-cpp](/spine-cpp) which supports loading, playback and manipulation of animations created with Spine. The spine-flutter runtime exposes almost all of the spine-cpp API as idiomatic Dart and provides Flutter and [Flame](https://flame-engine.org/) specific classes to easily display and interact with Spine skeletons.
|
||||
|
||||
The spine-flutter runtime supports all Spine features except tint black and screen blend mode.
|
||||
|
||||
## Asset Management
|
||||
### Exporting for spine-flutter
|
||||

|
||||
Please follow the instructions in the Spine User Guide on how to
|
||||
|
||||
1. [Export skeleton & animation data](/spine-export)
|
||||
2. [Export texture atlases containing the images of your skeleton](/spine-texture-packer)
|
||||
|
||||
An export of the skeleton data and texture atlas of your skeleton will yield the following files:
|
||||
|
||||

|
||||
|
||||
1. `skeleton-name.json` or `skeleton-name.skel`, containing your skeleton and animation data.
|
||||
2. `skeleton-name.atlas`, containing information about the texture atlas.
|
||||
3. One or more `.png` files, each representing on page of your texture atlas containing the packed images your skeleton uses.
|
||||
|
||||
> **Note**: You should prefer binary skeleton exports over JSON exports, as they are smaller in size and faster to load.
|
||||
|
||||
The files can be loaded via spine-flutter classes like `Atlas`, `SkeletonData`, `SkeletonDrawable`, `SpineWidget`.
|
||||
|
||||
> **Note**: The spine-flutter runtime currently does not support atlases exported using pre-multiplied alpha due to technical limitations in Flutter. Flutter's rendering engine ensures that common non-premultiplied alpha artifacts are avoided.
|
||||
|
||||
### Updating Spine Assets
|
||||
During development, you may frequently update your Spine skeleton data and texture atlas files. You can simply overwrite these source files (`.json`, `.skel`, `.atlas`, `.png`) by re-exporting from the Spine Editor and replacing the existing files in your Flutter project.
|
||||
|
||||
Ensure that the `major.minor` version of spine-flutter matches the `major.minor` Spine Editor version you are exporting from. See [Spine Versioning](/spine-versioning#Synchronizing-versions) for more information.
|
||||
|
||||
## Core classes
|
||||
The spine-flutter API is built on top of the generic [spine-cpp](/spine-cpp) runtime, which provides platform independent core classes and algorithms to load, query, modify, and animate Spine skeletons. The core classes are wrapped via Dart FFI and exposed as idiomatic Dart classes.
|
||||
|
||||
Here, we will briefly discuss the most important core classes that you will encounter in your day-to-day use of spine-flutter. Please consult the [Spine Runtimes Guide](/spine-runtimes-guide)
|
||||
for a detailed overview of the Spine Runtimes architecture, core classes, and API usage.
|
||||
|
||||
The [`Atlas`](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L102) class stores the data loaded from an `.atlas` file and its corresponding `.png` image files.
|
||||
|
||||
The [`SkeletonData`](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L199) class stores the data loaded from a `.json` or `.skel` skeleton file. The skeleton data contains information about the bone hierarchy, slots, attachments, constraints, skins, and animations. A `SkeletonData` instance is usually loaded by also providing an `Atlas` from which it sources the images to be used by the skeleton it represents. It serves as a blueprint for creating `Skeleton` instances. Multiple skeletons can be instantiated from the same atlas and skeleton data, which then share the loaded data, minimizing both load times and memory consumption at runtime.
|
||||
|
||||
The [`Skeleton`](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L2734) class stores an instance of a skeleton, created from a `SkeletonData` instance. A skeleton stores its current pose, that is the position of bones and the current configuration of slots, attachments, and active skin. The current pose can be computed by either manually modifying the bone hierarchy, or, more commonly, by applying animations via an `AnimationState`.
|
||||
|
||||
The [`AnimationState`](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L3663) class is responsible for keeping track of which animation(s) should be applied to a skeleton, advancing and mixing those animations based on the elapsed time between the last and current rendering frame, and applying the animations to a skeleton instance, thereby setting its current pose. The `AnimationState` queries an [`AnimationStateData`](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L3600) instance to retrieve mixing times between animations, or fetches the default mix time if no mixing time is available for a pair of animations.
|
||||
|
||||
The spine-flutter runtime builds on top of these core classes.
|
||||
|
||||
## SpineWidget
|
||||

|
||||
|
||||
A [`SpineWidget`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L261) is a [StatefulWidget](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) responsible for loading and displaying a Spine skeleton. At a minimum, the widget needs to know from where to load the skeleton and atlas files, and it must receive a `SpineWidgetController` instance that is responsible for modifying the state of the widget, such as setting an animation, or changing the skin of the skeleton.
|
||||
|
||||
In the simplest case, a `SpineWidget` can be instantiated inside another widget's `build()` method like this:
|
||||
|
||||
```dart
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = SpineWidgetController(onInitialized: (controller) {
|
||||
// Set the walk animation on track 0, let it loop
|
||||
controller.animationState.setAnimationByName(0, "walk", true);
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Simple Animation')),
|
||||
body: SpineWidget.fromAsset("assets/spineboy.atlas", "assets/spineboy-pro.skel", controller)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Upon instantiation, the `SpineWidget` will asynchronously load the specified files and construct the underlying core class instances from them, namely instances of `Atlas`, `SkeletonData`, `Skeleton`, `AnimationStateData`, and `AnimationState`.
|
||||
|
||||
Once loading is complete, the `SpineWidgetController` is called, allowing it to modify the state of the widget, such as setting one or more animations, manipulating the bone hierarchy, or modifying the skin of the skeleton. See the section on `SpineWidgetController` below.
|
||||
|
||||
The `SpineWidget` class provides multiple static factory methods to load skeleton and atlas files from different sources:
|
||||
|
||||
* `SpineWidget.fromAsset()` loads files from the root bundle, or a provided bundle.
|
||||
* `SpineWidget.fromFile()` loads files from the file system.
|
||||
* `SpineWidget.fromHttp()` loads files from URLs.
|
||||
* `SpineWidget.fromDrawable()` constructs a widget from a `SkeletonDrawable`. This is useful when the skeleton data should be preloaded, cached, and/or shared between `SpineWidget` instances. See the section "Pre-loading and sharing skeleton data" below.
|
||||
|
||||
All factory methods have optional arguments that let you further define how the Spine skeleton is fitted and aligned inside the widget, and how the widget is sized.
|
||||
|
||||
* `fit`, the [BoxFit](https://api.flutter.dev/flutter/painting/BoxFit.html) to use to fit the skeleton inside the widget.
|
||||
* `alignment`, the [Alignment](https://api.flutter.dev/flutter/painting/Alignment-class.html) to use to align the skeleton inside the widget.
|
||||
* `BoundsProvider`, used to calculate the pixel size of the bounding box to be used for the skeleton when computing the fit and alignment. By default, the skeleton's setup pose bounding box is used. See the class documentation for [`SetupPoseBounds`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L173), [`RawBounds`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L183), and [`SkinAndAnimationBounds`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L196) for additional information.
|
||||
* `sizedByBounds`, defines whether to size the widgets by the bounds computed by the `BoundsProvider`, or have it sized by its parent widget.
|
||||
|
||||
## Pre-loading and sharing skeleton data
|
||||
If you want to share the atlas and skeleton data between multiple `SpineWidget` instances, you can manually pre-load the assets:
|
||||
|
||||
```
|
||||
final atlas = await Atlas.fromAsset("assets/test.atlas");
|
||||
final skeletonData = await SkeletonData.fromAsset("assets/test.json", atlas);
|
||||
```
|
||||
|
||||
You can then instantiate one or more `SpineWidget` instances from the same data, saving on load time and memory:
|
||||
|
||||
```
|
||||
SpineWidget.fromDrawable(SkeletonDrawable(skeletonData, atlas));
|
||||
```
|
||||
|
||||
You are responsible for disposing of the atlas and skeleton data one no `SpineWidget` (or `SkeletonDrawable`) refercing them exists anymore.
|
||||
|
||||
```
|
||||
skeletonData.dispose();
|
||||
atlas.dispose();
|
||||
```
|
||||
|
||||
## SpineWidgetController
|
||||
A [`SpineWidgetController`](/git/spine-runtimes/spine-flutter/lib/spine_widget.dart#L64) controls how the skeleton of a `SpineWidget` is animated and rendered. The controller is provided with a set of optional callbacks as constructor arguments, which are called at specific times during the life-time of the `SpineWidget`.
|
||||
|
||||
The controller exposes the skeleton state through getters returning Spine Runtimes API objects such as the `Atlas`, `SkeletonData`, `Skeleton`, and `AnimationState`, through which the state can be manipulated. See the [Spine Runtimes Guide](/spine-runtimes-guide), and the [class documentation](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart) for more information.
|
||||
|
||||
Upon initialization of a `SpineWidget`, the controller's `onInitialized()` callback method is invoked once. This method can be used to setup the initial animation(s) to be played back, or set the skin of the skeleton, among other things.
|
||||
|
||||
After initialization is complete, the `SpineWidget` is rendered continuously at the screen refresh rate. Each frame, the `AnimationState` is updated based on the currently queued animations, and applied to the `Skeleton`.
|
||||
|
||||
Next, the optional `onBeforeUpdateWorldTransforms()` callback is invoked, which can modify the skeleton before its current pose is calculated using `Skeleton.updateWorldTransform()`.
|
||||
|
||||
After the current pose has been calculated, the optional `onAfterUpdateWorldTransforms()` callback is invoked, which can further modify the current pose before the skeleton is rendered. This is a good place to manually position bones.
|
||||
|
||||
Before the skeleton is rendered by the `SpineWidget`, the optional `onBeforePaint()` callback is invoked, which allows rendering backgrounds or other objects that should go behind the skeleton on the [`Canvas`](https://api.flutter.dev/flutter/dart-ui/Canvas-class.html).
|
||||
|
||||
After the `SpineWidget` has rendered the current skeleton pose to the `Canvas`, the optional `onAfterPaint()` callback is invoked, which allows rendering additional objects on top of the skeleton.
|
||||
|
||||
By default, the widget updates and renders the skeleton every frame. The `SpineWidgetController.pause()` method can be used to pause updating and rendering the skeleton. The `SpineWidgetController.resume()` method resumes updating and rendering the skeleton. The `SpineWidgetController.isPlaying()` getter reports the current playback state. See the [`example/lib/animation_state_events.dart`](/git/spine-runtimes/spine-flutter/example/lib/animation_state_events.dart) example.
|
||||
|
||||
## SkeletonDrawable
|
||||
A `SkeletonDrawable` bundles loading, storing, updating, and rendering a `Skeleton` and its associated `AnimationState` into a single, easy to use class. The class can be used as the basis for a custom widget implementation. The `SpineWidget` encapsulates the state of the skeleton it displays via an instance of a `SkeletonDrawable`.
|
||||
|
||||
Use the `fromAsset()`, `fromFile()`, or `fromHttp()` methods to construct a `SkeletonDrawable` from file assets. To share `Atlas` and `SkeletonData` among multiple `SkeletonDrawable` instances, instantiate the drawables via the constructor, passing the same atlas and skeleton data to each of them.
|
||||
|
||||
The `SkeletonDrawable` exposes the `Skeleton` and `AnimationState` to query, modify, and animate the skeleton. It also exposes the `Atlas` and `SkeletonData` from which the skeleton and animation state have been constructed.
|
||||
|
||||
To animate the skeleton, queue animations on one or more tracks via the `AnimationState` API, such as `AnimationState.setAnimation()` or `AnimationState.addAnimation()`.
|
||||
|
||||
To update the animation state, apply it to the skeleton, and update the current skeleton pose, call the `SkeletonDrawable.update()` method, providing it a delta time in seconds to advance the animations.
|
||||
|
||||
To render the current pose of the skeleton, use the rendering methods `SkeletonDrawable.render()`, `SkeletonDrawable.renderToCanvas()`, `SkeletonDrawable.renderToPictureRecorder()`, `SkeletonDrawable.renderToPng()`, or `SkeletonDrawable.renderToRawImageData()`.
|
||||
|
||||
The `SkeletonDrawable` stores objects allocated on the native heap. The native objects need to be manually disposed of via a call to `SkeletonDrawable.dispose()` if the `SkeletonDrawable` is no longer needed. Not doing so will result in a native memory leak.
|
||||
|
||||
> **Note:** when using `SpineWidget`, you do not have to manually dispose of the `SkeletonDrawable` the widget uses. The widget will dispose the `SkeletonDrawable` when it is disposed itself.
|
||||
|
||||
## Applying Animations
|
||||
Applying animations to a skeleton displayed by a `SpineWidget` is done through the `AnimationState` in the callbacks of a `SpineWidgetController`.
|
||||
|
||||
> **Note:** See [Applying Animations](/spine-applying-animations#AnimationState-API) in the Spine Runtimes Guide for more in-depth information, specifically about animation tracks and animation queueing.
|
||||
|
||||
To set a specific animation on track 0, call `AnimationState.setAnimation()`:
|
||||
|
||||
```dart
|
||||
final controller = SpineWidgetController(onInitialized: (controller) {
|
||||
// Set the walk animation on track 0, let it loop
|
||||
controller.animationState.setAnimationByName(0, "walk", true);
|
||||
});
|
||||
```
|
||||
|
||||
The first parameter specifies the track, the second parameter is the name of the animation, and the third parameter defines whether to loop the animation.
|
||||
|
||||
You can queue multiple animations:
|
||||
|
||||
```dart
|
||||
controller.animationState.setAnimationByName(0, "walk", true);
|
||||
controller.animationState.addAnimationByName(0, "jump", false, 2);
|
||||
controller.animationState.addAnimationByName(0, "run", true, 0);
|
||||
```
|
||||
|
||||
The first parameter to `addAnimationByName()` is the track. The second parameter is the name of the animation. The third parameter specifies the delay in seconds, after which this animation should replace the previous animation on the track. The final parameter defines whether to loop the animation.
|
||||
|
||||
In the example above, the `"walk"` animation is played back first. 2 seconds later, the `"jump"` animation is played back once, followed by a transition to the `"run"` animation, which will be looped.
|
||||
|
||||
When transitioning from one animation to another, `AnimationState` will mix the animations for a specificable duration. These mix times are defined in an `AnimationStateData` instance, from which the `AnimationState` retrieves mix times.
|
||||
|
||||
The `AnimationStateData` instance is also available through the controller. You can set the default mix time, or the mix time for a specific pair of animations:
|
||||
|
||||
```dart
|
||||
controller.animationStateData.setDefaultMix(0.2);
|
||||
controller.animationStateData.setMixByName("walk", "jump", 0.1);
|
||||
```
|
||||
|
||||
When setting or adding an animation, a `TrackEntry` object is returned, which allows further modification of that animation's playback. For example, you can set the track entry to reverse the animation playback:
|
||||
|
||||
```dart
|
||||
final entry = controller.animationState.setAnimationByName(0, "walk", true);
|
||||
entry.setReverse(true);
|
||||
```
|
||||
|
||||
See the [`TrackEntry` class documentation](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L3100) for more options.
|
||||
|
||||
> **Note:** Do not hold on to `TrackEntry` instances outside the function you are using them in. Track entries are re-used internally and will thus become invalid once the animation it represents has been completed.
|
||||
|
||||
You can set or queue empty animations on an animation track to smoothly reset the skeleton back to its setup pose:
|
||||
|
||||
```dart
|
||||
controller.animationState.setEmptyAnimation(0, 0.5);
|
||||
controller.animationState.addEmptyAnimation(0, 0.5, 0.5);
|
||||
```
|
||||
|
||||
The first parameter to `setEmptyAnimation()` specifies the track. The second parameter specifies the mix duration in seconds used to mix out the previous animation and mix in the "empty" animation.
|
||||
|
||||
The first parameter to `addEmptyAnimation()` specifies the track. The second parameter specifies the mix duration. The third parameter is the delay in seconds, after which the empty animation should replace the previous animation on the track via mixing.
|
||||
|
||||
All animations on a track can be cleared immediately via `AnimationState.clearTrack()`. To clear all tracks at once, `AnimationState.clearTracks()` can be used. This will leave the skeleton in the last pose it was in.
|
||||
|
||||
To reset the pose of a skeleton to the setup pose, use `Skeleton.setToSetupPose()`:
|
||||
|
||||
```dart
|
||||
controller.skeleton.setToSetupPose();
|
||||
```
|
||||
|
||||
This will reset both the bones and slots to their setup pose configuration. Use `Skeleton.setSlotsToSetupPose()` to only reset the slots to their setup pose configuration.
|
||||
|
||||
## AnimationState Events
|
||||
An `AnimationState` emits events during the life-cycle of an animation that is being played back. You can listen for this events to react as needed. The Spine Runtimes API defines the following [event types](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L3429):
|
||||
|
||||
* `Start`: emitted when an animation is started.
|
||||
* `Interrupted`: emitted when an animation's track was cleared, or a new animation was set.
|
||||
* `Completed`: emitted when an animation completes a loop.
|
||||
* `Ended`: emitted when an animation will never be applied again.
|
||||
* `Disposed`: emitted when the animation's track entry is disposed.
|
||||
* `Event`: emitted when a user defined [event](/spine-events#Events) happened.
|
||||
|
||||
To receive events, you can register an [`AnimationStateListener`](/git/spine-runtimes/spine-flutter/lib/spine_flutter.dart#L3597) callback with either the `AnimationState` to receive events across all animations, or with the `TrackEntry` of a specific animation queued for playback:
|
||||
|
||||
```dart
|
||||
final entry = controller.animationState.setAnimationByName(0, "walk", true);
|
||||
entry.setListener((type, trackEntry, event) {
|
||||
if (type == EventType.event) {
|
||||
print("User defined event: ${event?.getData().getName()}");
|
||||
}
|
||||
});
|
||||
|
||||
controller.animationState.setListener((type, trackEntry, event) {
|
||||
print("Animation state event $type");
|
||||
});
|
||||
```
|
||||
|
||||
See the [`example/lib/animation_state_events.dart`](/git/spine-runtimes/spine-flutter/example/lib/animation_state_events.dart) example.
|
||||
|
||||
## Skins
|
||||

|
||||
|
||||
Many applications and games allow users to create custom avatars out of many individual items, such as hair, eyes, pants, or accessories like earrings or bags. With Spine, this can be achieved by [mixing and matching skins](/spine-examples-mix-and-match).
|
||||
|
||||
You can create custom skins from other skins like this:
|
||||
|
||||
```dart
|
||||
final data = controller.skeletonData;
|
||||
final skeleton = controller.skeleton;
|
||||
final customSkin = Skin("custom-skin");
|
||||
customSkin.addSkin(data.findSkin("skin-base")!);
|
||||
customSkin.addSkin(data.findSkin("nose/short")!);
|
||||
customSkin.addSkin(data.findSkin("eyelids/girly")!);
|
||||
customSkin.addSkin(data.findSkin("eyes/violet")!);
|
||||
customSkin.addSkin(data.findSkin("hair/brown")!);
|
||||
customSkin.addSkin(data.findSkin("clothes/hoodie-orange")!);
|
||||
customSkin.addSkin(data.findSkin("legs/pants-jeans")!);
|
||||
customSkin.addSkin(data.findSkin("accessories/bag")!);
|
||||
customSkin.addSkin(data.findSkin("accessories/hat-red-yellow")!);
|
||||
skeleton.setSkin(customSkin);
|
||||
skeleton.setSlotsToSetupPose();
|
||||
```
|
||||
|
||||
Create a custom skin with the `Skin()` constructor.
|
||||
|
||||
Next, fetch the `SkeletonData` from the controller. It is used to look up skins by name via `SkeletonData.findSkin()`.
|
||||
|
||||
Add all the skins you want to combine into the new custom skin via `Skin.addSkin()`.
|
||||
|
||||
Finally, set the new skin on the `Skeleton` and call `Skeleton.setSlotsToSetupPose()` to ensure no attachments from previous skins and/or animations are left over.
|
||||
|
||||
> **Note:** A `Skin` wraps an underlying C++ object. It needs to be manually disposed via a call to `Skin.dispose()` when it is no longer in use.
|
||||
|
||||
See the [`example/lib/dress_up.dart`](/git/spine-runtimes/spine-flutter/example/lib/dress_up.dart) example, which also demonstrate how to render thumbnail previews of skins using `SkeletonDrawable`.
|
||||
|
||||
## Setting Bone Transforms
|
||||

|
||||
|
||||
When authoring a skeleton in the Spine Editor, the skeleton is defined in what is called the skeleton coordinate system. This coordinate system may not align with the coordinate system of the `SpineWidget` the skeleton is rendered by. Touch coordinates relative to the `SpineWidget` need thus be converted to the skeleton coordinate system, e.g. if a user should be able to move a bone by touch.
|
||||
|
||||
The `SpineWidgetController` offers the method `toSkeletonCoordinates()` which takes an [`Offset`](https://api.flutter.dev/flutter/dart-ui/Offset-class.html) relative to the `SpineWidget` it is associated with, and converts it to the skeleton's coordinate system.
|
||||
|
||||
See the [`example/lib/ik_following.dart`](/git/spine-runtimes/spine-flutter/example/lib/ik_following.dart) example.
|
||||
|
||||
## Flame Integration
|
||||

|
||||
|
||||
spine-flutter includes an example that shows how to load and renderer Spine skeletons in [Flame Engine](https://flame-engine.org/). See the [`example/lib/flame_example.dart`](/git/spine-runtimes/spine-flutter/example/lib/flame_example.dart) source file.
|
||||
|
||||
The example features a simple `SpineComponent` that extends Flame's `PositionComponent`. The `SpineComponent` can be instantiated through the static `SpineComponent.fromAsset()` method, or through the constructor.
|
||||
|
||||
The static method can be used as a quick, one-off loading mechanism when the skeleton and atlas data doesn't have to be shared with other components. The example contains a `FlameGame` implementation called `SimpleFlameExample` which demonstrates this simple way of getting a Spine skeleton on screen as part of a Flame game.
|
||||
|
||||
Creating a `SpineComponent` via the constructor allows more fine-grained management of the data loading and sharing by taking a `SkeletonDrawable`. E.g. you can pre-load the skeleton data and atlas, then share it across multiple `SpineComponent` instances. This will both improve memory usage and rendering performance, as data is shared, and rendering can be batched. See the `FlameGame` implementation called `PreloadAndShareSpineDataExample` for an example.
|
||||
|
||||
By design, Flame can not know when a component has reached its end of life. However, a `SpineComponent` handles native resources that need to be released at the end of its life. It is thus your responsibility to either call `SpineComponent.dispose()` if a `SpineComponent` is no longer in use. If the `SpineComponent` was constructed from a `SkeletonDrawable`, you may also have to manually dispose the `SkeletonData` and `Atlas` from which it was constructed, like in the `PreloadAndShareSpineDataExample` example.
|
||||
|
||||
# Spine Runtimes API access
|
||||
spine-flutter maps almost all of the Spine Runtime API to Dart. Objects returned by `SpineWidgetController` or `SkeletonDrawable`, like `Skeleton` or `AnimationState` are 1:1 translations of the spine-cpp API to Dart. You can thus apply almost all of the materials in the generic [Spine Runtimes Guide](/spine-runtimes-guide) to your Dart code.
|
||||
|
||||
Due to the nature of the spine-cpp to Dart FFI bridge, there are however a few limitations:
|
||||
|
||||
* Any returned array or map is a copy of the internal array. Modification will not have an effect. However, returned `Float32List` or `Int32List` instances are wrappers around the underlying native memory, and can thus be used to modify the native data.
|
||||
* You can not create, add or remove bones, slots, and other Spine objects directly.
|
||||
* The C++ class hierarchies of timelines are not exposed in Dart.
|
||||
@ -1,368 +0,0 @@
|
||||
# spine-glfw Runtime Documentation
|
||||
|
||||
> **Licensing**
|
||||
>
|
||||
> Please see the [Spine Runtimes License](/spine-runtimes-license) before integrating the Spine Runtimes into your applications.
|
||||
|
||||
# Getting Started
|
||||
spine-glfw is a C++ based runtime to load, manipulate and render Spine skeletons with [GLFW](https://www.glfw.org/) and OpenGL.
|
||||
|
||||
spine-glfw requires GLFW 3.0+ and OpenGL 3.3+ and supports all Spine features.
|
||||
|
||||
## Installation
|
||||
|
||||
The spine-glfw runtime is available as a C++ API based on the generic [spine-cpp](/spine-cpp) runtime and also supports [spine-c](/spine-c) API. Note that spine-c depends on spine-cpp.
|
||||
|
||||
### Integration with CMake (Recommended)
|
||||
|
||||
The easiest way to integrate spine-glfw into your project is via CMake FetchContent:
|
||||
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(MySpineProject)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
spine-glfw
|
||||
GIT_REPOSITORY https://github.com/esotericsoftware/spine-runtimes.git
|
||||
GIT_TAG 4.3
|
||||
SOURCE_SUBDIR spine-glfw
|
||||
)
|
||||
FetchContent_MakeAvailable(spine-glfw)
|
||||
|
||||
# Create your executable
|
||||
add_executable(MyApp main.cpp)
|
||||
|
||||
# Link against spine-glfw (includes spine-cpp, spine-c, GLFW, and glbinding)
|
||||
target_link_libraries(MyApp spine-glfw)
|
||||
```
|
||||
|
||||
This will automatically fetch and build spine-glfw along with all its dependencies (spine-c, spine-cpp, GLFW, and glbinding).
|
||||
|
||||
### Manual Integration
|
||||
|
||||
If you prefer manual integration:
|
||||
|
||||
1. Download the Spine Runtimes source using git (`git clone https://github.com/esotericsoftware/spine-runtimes`) or download as a zip.
|
||||
2. Add the required source files to your project:
|
||||
- Add sources from `spine-cpp/src`, `spine-c/src`, and `spine-glfw/src/spine-glfw.cpp`
|
||||
3. Add the include directories: `spine-cpp/include`, `spine-c/include`, and `spine-glfw/src`
|
||||
4. Link against GLFW, OpenGL, and glbinding libraries
|
||||
|
||||
In your C++ code, include the following header file to get access to the `spine-glfw` API:
|
||||
|
||||
```cpp
|
||||
#include <spine-glfw.h>
|
||||
```
|
||||
|
||||
> *Note:* spine-glfw requires OpenGL 3.3 Core Profile or higher. The runtime uses modern OpenGL features including vertex array objects, vertex buffer objects, and GLSL shaders.
|
||||
|
||||
## Samples
|
||||
The spine-glfw example works on Windows, Linux and Mac OS X. For a [spine-cpp](/spine-cpp) based example, see [example/main.cpp](/git/spine-runtimes/tree/spine-glfw/example/main.cpp), for a spine-c example see [example/main-c.cpp](/git/spine-runtimes/tree/spine-glfw/example/main-c.cpp).
|
||||
|
||||
### Windows
|
||||
|
||||
1. Install [Visual Studio Community](https://visualstudio.microsoft.com/downloads/). Make sure you install support for C++ and CMake.
|
||||
2. Download the Spine Runtimes repository using git (`git clone https://github.com/esotericsoftware/spine-runtimes`) or download it as a zip.
|
||||
3. Open Visual Studio Community, then open `spine-glfw/` via the **Open a local folder** button in the Visual Studio Community launcher.
|
||||
4. Wait for CMake to finish, then select either `spine-glfw-example.exe` or `spine-glfw-example-c.exe` as the start-up project and start debugging.
|
||||
|
||||
### Linux
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
sudo apt-get install cmake ninja-build libgl1-mesa-dev libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev # Ubuntu/Debian
|
||||
# or equivalent for your distribution
|
||||
```
|
||||
2. Clone the repository: `git clone https://github.com/esotericsoftware/spine-runtimes`
|
||||
3. Build and run:
|
||||
```bash
|
||||
cd spine-runtimes/spine-glfw
|
||||
./build.sh
|
||||
./build/debug/spine-glfw-example-c # Run C example
|
||||
./build/debug/spine-glfw-example # Run C++ example
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
1. Install [Xcode](https://developer.apple.com/xcode/)
|
||||
2. Install [Homebrew](http://brew.sh/)
|
||||
3. Install dependencies:
|
||||
```bash
|
||||
brew install cmake ninja
|
||||
```
|
||||
4. Clone the repository: `git clone https://github.com/esotericsoftware/spine-runtimes`
|
||||
5. Build and run:
|
||||
```bash
|
||||
cd spine-runtimes/spine-glfw
|
||||
./build.sh
|
||||
./build/debug/spine-glfw-example-c # Run C example
|
||||
./build/debug/spine-glfw-example # Run C++ example
|
||||
```
|
||||
|
||||
## Using spine-glfw
|
||||
The spine-glfw runtime supports playback and manipulation of animations created with Spine using [GLFW](https://www.glfw.org/) and OpenGL. The spine-glfw runtime is implemented in C++ and is based on the generic [spine-cpp](/spine-cpp) runtime. It adds loading and rendering implementations based on OpenGL APIs.
|
||||
|
||||
Please consult the [Spine Runtimes Guide](/spine-runtimes) for a detailed overview of the Spine Runtime architecture, and the [spine-cpp](/spine-cpp) documentation for information on the core APIs used to playback and manipulate animations created with Spine with C++.
|
||||
|
||||
### Exporting for GLFW
|
||||

|
||||
Please follow the instructions in the Spine User Guide on how to
|
||||
|
||||
1. [Export skeleton & animation data](/spine-export)
|
||||
2. [Export texture atlases containing the images of your skeleton](/spine-texture-packer)
|
||||
|
||||
An export of the skeleton data and texture atlas of your skeleton will yield the following files:
|
||||
|
||||

|
||||
|
||||
1. `skeleton-name.json` or `skeleton-name.skel`, containing your skeleton and animation data.
|
||||
2. `skeleton-name.atlas`, containing information about the texture atlas.
|
||||
3. One or more `.png` files, each representing on page of your texture atlas containing the packed images your skeleton uses.
|
||||
|
||||
### Loading Spine skeletons
|
||||
The spine-glfw runtime uses OpenGL for rendering skeletons. Before a skeleton can be loaded from exported files, a GLFW window and OpenGL context must be created:
|
||||
|
||||
```cpp
|
||||
// Initialize GLFW
|
||||
if (!glfwInit()) {
|
||||
// Handle error
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set OpenGL version to 3.3 Core Profile
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
// Create window
|
||||
GLFWwindow* window = glfwCreateWindow(800, 600, "Spine GLFW", NULL, NULL);
|
||||
glfwMakeContextCurrent(window);
|
||||
|
||||
// Initialize OpenGL function loading (e.g., with glbinding)
|
||||
glbinding::initialize(glfwGetProcAddress);
|
||||
```
|
||||
|
||||
Next, the texture atlas can be loaded using the `GlTextureLoader`:
|
||||
|
||||
```cpp
|
||||
// C++ API
|
||||
spine::GlTextureLoader textureLoader;
|
||||
spine::Atlas *atlas = new spine::Atlas("data/spineboy-pma.atlas", &textureLoader);
|
||||
```
|
||||
|
||||
With the atlas loaded, the `.json` or `.skel` file can be loaded:
|
||||
|
||||
```cpp
|
||||
// C++ API
|
||||
spine::SkeletonBinary binary(*atlas);
|
||||
spine::SkeletonData *skeletonData = binary.readSkeletonDataFile("data/spineboy-pro.skel");
|
||||
```
|
||||
|
||||
For JSON format:
|
||||
|
||||
```cpp
|
||||
// C++ API
|
||||
spine::SkeletonJson json(*atlas);
|
||||
spine::SkeletonData *skeletonData = json.readSkeletonDataFile("data/spineboy-pro.json");
|
||||
```
|
||||
|
||||
The `spine::Atlas` and `spine::SkeletonData` instances can then be used to create `spine::Skeleton` instances for rendering.
|
||||
|
||||
> *Note:* the loaded skeleton data and atlas can and should be shared across `spine::Skeleton` instances to reduce memory consumption and enable batched rendering of skeletons that share the same atlas data.
|
||||
|
||||
### Renderer
|
||||
The main addition of spine-glfw on top of [spine-cpp](/spine-cpp) is the renderer system. The renderer handles the OpenGL rendering pipeline including shaders, meshes, and textures. Unlike other runtimes that provide a drawable class, spine-glfw uses a more modular approach with separate renderer and mesh components.
|
||||
|
||||
You can create a renderer like this:
|
||||
|
||||
```cpp
|
||||
// Create the renderer and set viewport size
|
||||
renderer_t *renderer = renderer_create();
|
||||
renderer_set_viewport_size(renderer, windowWidth, windowHeight);
|
||||
```
|
||||
|
||||
The renderer automatically creates and manages OpenGL shaders optimized for Spine skeleton rendering.
|
||||
|
||||
### Creating and animating skeletons
|
||||
With the skeleton data loaded, you can create a skeleton instance:
|
||||
|
||||
```cpp
|
||||
// Set coordinate system (spine-glfw uses y-down by default)
|
||||
spine::Bone::setYDown(true);
|
||||
|
||||
// Create a skeleton from the data
|
||||
spine::Skeleton skeleton(*skeletonData);
|
||||
skeleton.setPosition(400, 500);
|
||||
skeleton.setScaleX(0.5f);
|
||||
skeleton.setScaleY(0.5f);
|
||||
```
|
||||
|
||||
For animation, create an animation state:
|
||||
|
||||
```cpp
|
||||
// Create animation state
|
||||
spine::AnimationStateData animationStateData(*skeletonData);
|
||||
animationStateData.setDefaultMix(0.2f);
|
||||
spine::AnimationState animationState(animationStateData);
|
||||
|
||||
// Set animations
|
||||
animationState.setAnimation(0, "portal", true);
|
||||
animationState.addAnimation(0, "run", true, 0);
|
||||
```
|
||||
|
||||
Please refer to the [spine-cpp](/spine-cpp) documentation for more information on the APIs to manipulate skeletons and animation states.
|
||||
|
||||
### Updating and rendering
|
||||
In your main loop, update the animation state and skeleton, then render:
|
||||
|
||||
```cpp
|
||||
double lastTime = glfwGetTime();
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
double currTime = glfwGetTime();
|
||||
float delta = currTime - lastTime;
|
||||
lastTime = currTime;
|
||||
|
||||
// Update animation state
|
||||
animationState.update(delta);
|
||||
animationState.apply(skeleton);
|
||||
|
||||
// Update skeleton
|
||||
skeleton.update(delta);
|
||||
skeleton.updateWorldTransform(spine::Physics_Update);
|
||||
|
||||
// Clear screen
|
||||
gl::glClear(gl::GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Render skeleton
|
||||
renderer_draw(renderer, &skeleton, true); // true for premultiplied alpha
|
||||
|
||||
// Present
|
||||
glfwSwapBuffers(window);
|
||||
glfwPollEvents();
|
||||
}
|
||||
```
|
||||
|
||||
### Using spine-c
|
||||
spine-glfw also supports the [spine-c](/spine-c) API for applications that need a C interface or are written in programming languages that cannot interface with C++ code directly. The key differences when using spine-c with spine-glfw are:
|
||||
|
||||
#### Key Differences from spine-cpp:
|
||||
|
||||
1. **C API instead of C++**: All functions use C-style naming (e.g., `spine_skeleton_set_position` vs `skeleton.setPosition`)
|
||||
2. **Manual file loading**: You must manually load atlas and skeleton files into memory
|
||||
3. **Callback-based texture loading**: Textures are loaded via user-provided callback functions
|
||||
4. **Skeleton drawable wrapper**: Uses `spine_skeleton_drawable` which wraps skeleton and animation state
|
||||
5. **Different renderer function**: Uses `renderer_draw_c()` instead of `renderer_draw()`
|
||||
|
||||
#### Texture Loading with Callbacks
|
||||
First, you need to provide texture loading callbacks that bridge spine-c to spine-glfw's texture system:
|
||||
|
||||
```cpp
|
||||
// Callback function to load textures
|
||||
void *load_texture(const char *path) {
|
||||
return (void *)(uintptr_t)texture_load(path);
|
||||
}
|
||||
|
||||
// Callback function to unload textures
|
||||
void unload_texture(void *texture) {
|
||||
texture_dispose((texture_t)(uintptr_t)texture);
|
||||
}
|
||||
```
|
||||
|
||||
#### Loading Atlas and Skeleton Data
|
||||
Unlike spine-cpp which can load files directly, spine-c requires manual file reading:
|
||||
|
||||
```cpp
|
||||
// Read atlas file into memory
|
||||
int atlas_length = 0;
|
||||
uint8_t *atlas_bytes = read_file("data/spineboy-pma.atlas", &atlas_length);
|
||||
spine_atlas_result result = spine_atlas_load_callback(
|
||||
(const char*)atlas_bytes, "data/", load_texture, unload_texture);
|
||||
spine_atlas atlas = spine_atlas_result_get_atlas(result);
|
||||
|
||||
// Read skeleton file into memory
|
||||
int skeleton_length = 0;
|
||||
uint8_t *skeleton_bytes = read_file("data/spineboy-pro.skel", &skeleton_length);
|
||||
spine_skeleton_data_result result2 = spine_skeleton_data_load_binary(
|
||||
atlas, skeleton_bytes, skeleton_length, "data/");
|
||||
spine_skeleton_data skeleton_data = spine_skeleton_data_result_get_data(result2);
|
||||
```
|
||||
|
||||
#### Creating and Manipulating Skeletons
|
||||
spine-c uses a drawable wrapper and C-style function calls:
|
||||
|
||||
```cpp
|
||||
// Create skeleton drawable (combines skeleton + animation state)
|
||||
spine_skeleton_drawable drawable = spine_skeleton_drawable_create(skeleton_data);
|
||||
spine_skeleton skeleton = spine_skeleton_drawable_get_skeleton(drawable);
|
||||
|
||||
// Set skeleton properties using C functions
|
||||
spine_skeleton_set_position(skeleton, width / 2, height - 100);
|
||||
spine_skeleton_set_scale(skeleton, 0.3f, 0.3f);
|
||||
|
||||
// Get animation state from drawable
|
||||
spine_animation_state animation_state = spine_skeleton_drawable_get_animation_state(drawable);
|
||||
spine_animation_state_data animation_state_data = spine_animation_state_get_data(animation_state);
|
||||
spine_animation_state_data_set_default_mix(animation_state_data, 0.2f);
|
||||
|
||||
// Set animations using C functions
|
||||
spine_animation_state_set_animation_1(animation_state, 0, "portal", true);
|
||||
spine_animation_state_add_animation_1(animation_state, 0, "run", true, 0);
|
||||
```
|
||||
|
||||
#### Updating and Rendering
|
||||
The update loop uses C-style function calls and a different renderer function:
|
||||
|
||||
```cpp
|
||||
// Update animation state and skeleton (full sequence required)
|
||||
spine_animation_state_update(animation_state, delta);
|
||||
spine_animation_state_apply(animation_state, skeleton);
|
||||
spine_skeleton_update(skeleton, delta);
|
||||
spine_skeleton_update_world_transform(skeleton, SPINE_PHYSICS_UPDATE);
|
||||
|
||||
// Render using the C-specific function
|
||||
renderer_draw_c(renderer, skeleton, true);
|
||||
```
|
||||
|
||||
The `renderer_draw_c()` function is specifically designed to work with spine-c's `spine_skeleton` opaque type, while `renderer_draw()` works with spine-cpp's `spine::Skeleton` class.
|
||||
|
||||
### Cleanup
|
||||
|
||||
#### Cleanup for spine-cpp
|
||||
When using the spine-cpp API, use C++ delete operators:
|
||||
|
||||
```cpp
|
||||
// Dispose renderer
|
||||
renderer_dispose(renderer);
|
||||
|
||||
// Dispose skeleton data and atlas (C++ API)
|
||||
delete skeletonData;
|
||||
delete atlas;
|
||||
|
||||
// Cleanup GLFW
|
||||
glfwTerminate();
|
||||
```
|
||||
|
||||
#### Cleanup for spine-c
|
||||
When using the spine-c API, use the C-style dispose functions:
|
||||
|
||||
```cpp
|
||||
// Dispose renderer
|
||||
renderer_dispose(renderer);
|
||||
|
||||
// Dispose skeleton drawable and data (C API)
|
||||
spine_skeleton_drawable_dispose(drawable);
|
||||
spine_skeleton_data_dispose(skeleton_data);
|
||||
spine_atlas_dispose(atlas);
|
||||
spine_skeleton_data_result_dispose(result2);
|
||||
spine_atlas_result_dispose(result);
|
||||
|
||||
// Free manually allocated file data
|
||||
free(atlas_bytes);
|
||||
free(skeleton_bytes);
|
||||
|
||||
// Cleanup GLFW
|
||||
glfwTerminate();
|
||||
```
|
||||
|
||||
> *Note:* freeing skeleton data and atlas instances will automatically dispose of any associated OpenGL textures through the texture loader. With spine-c, you must also free any memory you allocated for file data using `malloc()`/`read_file()`.
|
||||
@ -1,333 +0,0 @@
|
||||
# spine-glfw Runtime Documentation
|
||||
|
||||
> **Licensing**
|
||||
>
|
||||
> Please see the [Spine Runtimes License](/spine-runtimes-license) before integrating the Spine Runtimes into your applications.
|
||||
|
||||
# Getting Started
|
||||
spine-glfw is a C++ based runtime to load, manipulate and render Spine skeletons with [GLFW](https://www.glfw.org/) and OpenGL.
|
||||
|
||||
spine-glfw requires GLFW 3.0+ and OpenGL 3.3+ and supports all Spine features including [two-color tinting](https://en.esotericsoftware.com/spine-slots#Tint-black).
|
||||
|
||||
## Installation
|
||||
|
||||
The spine-glfw runtime is available as a C++ API based on the generic [spine-cpp](/spine-cpp) runtime and also supports [spine-c](/spine-c) API. To integrate spine-glfw into your project:
|
||||
|
||||
1. Create a new GLFW project. See the [GLFW documentation](https://www.glfw.org/docs/latest/) or have a look at the example in [spine-runtimes repository](/git/spine-runtimes/tree/spine-glfw), which uses [CMake](https://cmake.org) as the build system.
|
||||
2. Download the Spine Runtimes source using git (`git clone https://github.com/esotericsoftware/spine-runtimes`).
|
||||
3. Add the sources from `spine-cpp/spine-cpp/src/spine` and the files `spine-glfw/src/spine-glfw.cpp` and `spine-glfw/src/spine-glfw.h` to your project.
|
||||
4. Add the folders `spine-cpp/spine-cpp/include` and `spine-glfw/src` to your header search path.
|
||||
5. Link against GLFW, OpenGL, and optionally glbinding for modern OpenGL function loading.
|
||||
|
||||
In your C++ code, include the following header file to get access to the `spine-glfw` API:
|
||||
|
||||
```cpp
|
||||
#include <spine-glfw.h>
|
||||
```
|
||||
|
||||
> *Note:* spine-glfw requires OpenGL 3.3 Core Profile or higher. The runtime uses modern OpenGL features including vertex array objects, vertex buffer objects, and GLSL shaders.
|
||||
|
||||
## Samples
|
||||
The spine-glfw example works on Windows, Linux and Mac OS X. For a [spine-cpp](/spine-cpp) based example, see [example/main.cpp](/git/spine-runtimes/tree/spine-glfw/example/main.cpp), for a spine-c example see [example/main-c.cpp](/git/spine-runtimes/tree/spine-glfw/example/main-c.cpp).
|
||||
|
||||
### Windows
|
||||
|
||||
1. Install [Visual Studio Community](https://visualstudio.microsoft.com/downloads/). Make sure you install support for C++ and CMake.
|
||||
2. Download the Spine Runtimes repository using git (`git clone https://github.com/esotericsoftware/spine-runtimes`) or download it as a zip.
|
||||
3. Open Visual Studio Community, then open `spine-glfw/` via the **Open a local folder** button in the Visual Studio Community launcher.
|
||||
4. Wait for CMake to finish, then select either `spine-glfw-example.exe` or `spine-glfw-example-c.exe` as the start-up project and start debugging.
|
||||
|
||||
### Linux
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
sudo apt-get install cmake ninja-build libgl1-mesa-dev libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev # Ubuntu/Debian
|
||||
# or equivalent for your distribution
|
||||
```
|
||||
2. Clone the repository: `git clone https://github.com/esotericsoftware/spine-runtimes`
|
||||
3. Build and run:
|
||||
```bash
|
||||
cd spine-runtimes/spine-glfw
|
||||
./build.sh
|
||||
./build/debug/spine-glfw-example-c # Run C example
|
||||
./build/debug/spine-glfw-example # Run C++ example
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
1. Install [Xcode](https://developer.apple.com/xcode/)
|
||||
2. Install [Homebrew](http://brew.sh/)
|
||||
3. Install dependencies:
|
||||
```bash
|
||||
brew install cmake ninja
|
||||
```
|
||||
4. Clone the repository: `git clone https://github.com/esotericsoftware/spine-runtimes`
|
||||
5. Build and run:
|
||||
```bash
|
||||
cd spine-runtimes/spine-glfw
|
||||
./build.sh
|
||||
./build/debug/spine-glfw-example-c # Run C example
|
||||
./build/debug/spine-glfw-example # Run C++ example
|
||||
```
|
||||
|
||||
## Using spine-glfw
|
||||
The spine-glfw runtime supports playback and manipulation of animations created with Spine using [GLFW](https://www.glfw.org/) and OpenGL. The spine-glfw runtime is implemented in C++ and is based on the generic [spine-cpp](/spine-cpp) runtime. It adds loading and rendering implementations based on OpenGL APIs.
|
||||
|
||||
Please consult the [Spine Runtimes Guide](/spine-runtimes) for a detailed overview of the Spine Runtime architecture, and the [spine-cpp](/spine-cpp) documentation for information on the core APIs used to playback and manipulate animations created with Spine with C++.
|
||||
|
||||
### Exporting for GLFW
|
||||

|
||||
Please follow the instructions in the Spine User Guide on how to
|
||||
|
||||
1. [Export skeleton & animation data](/spine-export)
|
||||
2. [Export texture atlases containing the images of your skeleton](/spine-texture-packer)
|
||||
|
||||
An export of the skeleton data and texture atlas of your skeleton will yield the following files:
|
||||
|
||||

|
||||
|
||||
1. `skeleton-name.json` or `skeleton-name.skel`, containing your skeleton and animation data.
|
||||
2. `skeleton-name.atlas`, containing information about the texture atlas.
|
||||
3. One or more `.png` files, each representing on page of your texture atlas containing the packed images your skeleton uses.
|
||||
|
||||
> **Note:** The spine-glfw runtime does not support the screen blend mode available in the Spine editor.
|
||||
|
||||
### Loading Spine skeletons
|
||||
The spine-glfw runtime uses OpenGL for rendering skeletons. Before a skeleton can be loaded from exported files, a GLFW window and OpenGL context must be created:
|
||||
|
||||
```cpp
|
||||
// Initialize GLFW
|
||||
if (!glfwInit()) {
|
||||
// Handle error
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set OpenGL version to 3.3 Core Profile
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
// Create window
|
||||
GLFWwindow* window = glfwCreateWindow(800, 600, "Spine GLFW", NULL, NULL);
|
||||
glfwMakeContextCurrent(window);
|
||||
|
||||
// Initialize OpenGL function loading (e.g., with glbinding)
|
||||
glbinding::initialize(glfwGetProcAddress);
|
||||
```
|
||||
|
||||
Next, the texture atlas can be loaded using the `GlTextureLoader`:
|
||||
|
||||
```cpp
|
||||
// C++ API
|
||||
spine::GlTextureLoader textureLoader;
|
||||
spine::Atlas *atlas = new spine::Atlas("data/spineboy-pma.atlas", &textureLoader);
|
||||
```
|
||||
|
||||
With the atlas loaded, the `.json` or `.skel` file can be loaded:
|
||||
|
||||
```cpp
|
||||
// C++ API
|
||||
spine::SkeletonBinary binary(atlas);
|
||||
spine::SkeletonData *skeletonData = binary.readSkeletonDataFile("data/spineboy-pro.skel");
|
||||
```
|
||||
|
||||
For JSON format:
|
||||
|
||||
```cpp
|
||||
// C++ API
|
||||
spine::SkeletonJson json(atlas);
|
||||
spine::SkeletonData *skeletonData = json.readSkeletonDataFile("data/spineboy-pro.json");
|
||||
```
|
||||
|
||||
The `spine::Atlas` and `spine::SkeletonData` instances can then be used to create `spine::Skeleton` instances for rendering.
|
||||
|
||||
> *Note:* the loaded skeleton data and atlas can and should be shared across `spine::Skeleton` instances to reduce memory consumption and enable batched rendering of skeletons that share the same atlas data.
|
||||
|
||||
### Renderer
|
||||
The main addition of spine-glfw on top of [spine-cpp](/spine-cpp) is the renderer system. The renderer handles the OpenGL rendering pipeline including shaders, meshes, and textures. Unlike other runtimes that provide a drawable class, spine-glfw uses a more modular approach with separate renderer and mesh components.
|
||||
|
||||
You can create a renderer like this:
|
||||
|
||||
```cpp
|
||||
// Create the renderer and set viewport size
|
||||
renderer_t *renderer = renderer_create();
|
||||
renderer_set_viewport_size(renderer, windowWidth, windowHeight);
|
||||
```
|
||||
|
||||
The renderer automatically creates and manages OpenGL shaders optimized for Spine skeleton rendering.
|
||||
|
||||
### Creating and animating skeletons
|
||||
With the skeleton data loaded, you can create a skeleton instance:
|
||||
|
||||
```cpp
|
||||
// Set coordinate system (spine-glfw uses y-down by default)
|
||||
spine::Bone::setYDown(true);
|
||||
|
||||
// Create a skeleton from the data
|
||||
spine::Skeleton skeleton(skeletonData);
|
||||
skeleton.setPosition(400, 500);
|
||||
skeleton.setScaleX(0.5f);
|
||||
skeleton.setScaleY(0.5f);
|
||||
```
|
||||
|
||||
For animation, create an animation state:
|
||||
|
||||
```cpp
|
||||
// Create animation state
|
||||
spine::AnimationStateData animationStateData(skeletonData);
|
||||
animationStateData.setDefaultMix(0.2f);
|
||||
spine::AnimationState animationState(&animationStateData);
|
||||
|
||||
// Set animations
|
||||
animationState.setAnimation(0, "portal", true);
|
||||
animationState.addAnimation(0, "run", true, 0);
|
||||
```
|
||||
|
||||
Please refer to the [spine-cpp](/spine-cpp) documentation for more information on the APIs to manipulate skeletons and animation states.
|
||||
|
||||
### Updating and rendering
|
||||
In your main loop, update the animation state and skeleton, then render:
|
||||
|
||||
```cpp
|
||||
double lastTime = glfwGetTime();
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
double currTime = glfwGetTime();
|
||||
float delta = currTime - lastTime;
|
||||
lastTime = currTime;
|
||||
|
||||
// Update animation state
|
||||
animationState.update(delta);
|
||||
animationState.apply(skeleton);
|
||||
|
||||
// Update skeleton
|
||||
skeleton.update(delta);
|
||||
skeleton.updateWorldTransform(spine::Physics_Update);
|
||||
|
||||
// Clear screen
|
||||
gl::glClear(gl::GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Render skeleton
|
||||
renderer_draw(renderer, &skeleton, true); // true for premultiplied alpha
|
||||
|
||||
// Present
|
||||
glfwSwapBuffers(window);
|
||||
glfwPollEvents();
|
||||
}
|
||||
```
|
||||
|
||||
### Using spine-c
|
||||
spine-glfw also supports the [spine-c](/spine-cpp-lite) API for applications that need a C interface or are written in programming languages that cannot interface with C++ code directly. The key differences when using spine-c with spine-glfw are:
|
||||
|
||||
#### Key Differences from spine-cpp:
|
||||
|
||||
1. **C API instead of C++**: All functions use C-style naming (e.g., `spine_skeleton_set_position` vs `skeleton.setPosition`)
|
||||
2. **Manual file loading**: You must manually load atlas and skeleton files into memory
|
||||
3. **Callback-based texture loading**: Textures are loaded via user-provided callback functions
|
||||
4. **Skeleton drawable wrapper**: Uses `spine_skeleton_drawable` which wraps skeleton and animation state
|
||||
5. **Different renderer function**: Uses `renderer_draw_c()` instead of `renderer_draw()`
|
||||
|
||||
#### Texture Loading with Callbacks
|
||||
First, you need to provide texture loading callbacks that bridge spine-cpp-lite to spine-glfw's texture system:
|
||||
|
||||
```cpp
|
||||
// Callback function to load textures
|
||||
void *load_texture(const char *path) {
|
||||
return (void *)(uintptr_t)texture_load(path);
|
||||
}
|
||||
|
||||
// Callback function to unload textures
|
||||
void unload_texture(void *texture) {
|
||||
texture_dispose((texture_t)(uintptr_t)texture);
|
||||
}
|
||||
```
|
||||
|
||||
#### Loading Atlas and Skeleton Data
|
||||
Unlike spine-cpp which can load files directly, spine-c requires manual file reading:
|
||||
|
||||
```cpp
|
||||
// Read atlas file into memory
|
||||
int atlas_length = 0;
|
||||
uint8_t *atlas_bytes = read_file("data/spineboy-pma.atlas", &atlas_length);
|
||||
spine_atlas atlas = spine_atlas_load_callback(
|
||||
(utf8*)atlas_bytes, "data/", load_texture, unload_texture);
|
||||
|
||||
// Read skeleton file into memory
|
||||
int skeleton_length = 0;
|
||||
uint8_t *skeleton_bytes = read_file("data/spineboy-pro.skel", &skeleton_length);
|
||||
spine_skeleton_data_result result = spine_skeleton_data_load_binary(atlas, skeleton_bytes, skeleton_length);
|
||||
spine_skeleton_data skeleton_data = spine_skeleton_data_result_get_data(result);
|
||||
```
|
||||
|
||||
#### Creating and Manipulating Skeletons
|
||||
spine-c uses a drawable wrapper and C-style function calls:
|
||||
|
||||
```cpp
|
||||
// Create skeleton drawable (combines skeleton + animation state)
|
||||
spine_skeleton_drawable drawable = spine_skeleton_drawable_create(skeleton_data);
|
||||
spine_skeleton skeleton = spine_skeleton_drawable_get_skeleton(drawable);
|
||||
|
||||
// Set skeleton properties using C functions
|
||||
spine_skeleton_set_position(skeleton, width / 2, height - 100);
|
||||
spine_skeleton_set_scale(skeleton, 0.3f, 0.3f);
|
||||
|
||||
// Get animation state from drawable
|
||||
spine_animation_state animation_state = spine_skeleton_drawable_get_animation_state(drawable);
|
||||
spine_animation_state_data animation_state_data = spine_animation_state_get_data(animation_state);
|
||||
spine_animation_state_data_set_default_mix(animation_state_data, 0.2f);
|
||||
|
||||
// Set animations using C functions
|
||||
spine_animation_state_set_animation_1(animation_state, 0, "portal", true);
|
||||
spine_animation_state_add_animation_1(animation_state, 0, "run", true, 0);
|
||||
```
|
||||
|
||||
#### Updating and Rendering
|
||||
The update loop uses C-style function calls and a different renderer function:
|
||||
|
||||
```cpp
|
||||
// Update animation state and skeleton
|
||||
spine_skeleton_drawable_update(drawable, deltaTimeInSeconds)
|
||||
|
||||
// Render using the C-specific function
|
||||
renderer_draw_c(renderer, skeleton, true);
|
||||
```
|
||||
|
||||
The `renderer_draw_c()` function is specifically designed to work with spine-c's `spine_skeleton` opaque type, while `renderer_draw()` works with spine-cpp's `spine::Skeleton` class.
|
||||
|
||||
### Cleanup
|
||||
|
||||
#### Cleanup for spine-cpp
|
||||
When using the spine-cpp API, use C++ delete operators:
|
||||
|
||||
```cpp
|
||||
// Dispose renderer
|
||||
renderer_dispose(renderer);
|
||||
|
||||
// Dispose skeleton data and atlas (C++ API)
|
||||
delete skeletonData;
|
||||
delete atlas;
|
||||
|
||||
// Cleanup GLFW
|
||||
glfwTerminate();
|
||||
```
|
||||
|
||||
#### Cleanup for spine-c
|
||||
When using the spine-c API, use the C-style dispose functions:
|
||||
|
||||
```cpp
|
||||
// Dispose renderer
|
||||
renderer_dispose(renderer);
|
||||
|
||||
// Dispose skeleton data and atlas (C API)
|
||||
spine_skeleton_drawable_dispose(drawable);
|
||||
spine_skeleton_data_dispose(skeleton_data);
|
||||
spine_atlas_dispose(atlas);
|
||||
|
||||
// Free manually allocated file data
|
||||
free(atlas_bytes);
|
||||
free(skeleton_bytes);
|
||||
|
||||
// Cleanup GLFW
|
||||
glfwTerminate();
|
||||
```
|
||||
|
||||
> *Note:* freeing skeleton data and atlas instances will automatically dispose of any associated OpenGL textures through the texture loader. With spine-c, you must also free any memory you allocated for file data using `malloc()`/`read_file()`.
|
||||
@ -1,291 +0,0 @@
|
||||
# spine-sdl Runtime Documentation
|
||||
|
||||
> **Licensing**
|
||||
>
|
||||
> Please see the [Spine Runtimes License](/spine-runtimes-license) before integrating the Spine Runtimes into your applications.
|
||||
|
||||
# Getting Started
|
||||
spine-sdl is a C and C++ based runtime to load, manipulate and render Spine skeletons with [SDL](https://www.libsdl.org/).
|
||||
|
||||
spine-sdl requires SDL 2.0.18+ and supports all Spine features except two-color tinting.
|
||||
|
||||
## Installation
|
||||
|
||||
The spine-sdl runtime is available as a C and C++ API. Both APIs are based on [spine-c](/spine-c) and [spine-cpp](/spine-cpp) runtimes. Note that spine-c depends on spine-cpp, so both are required regardless of which API you choose to use.
|
||||
|
||||
### Integration with CMake (Recommended)
|
||||
|
||||
The easiest way to integrate spine-sdl into your project is via CMake FetchContent:
|
||||
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(MySpineProject)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
spine-sdl
|
||||
GIT_REPOSITORY https://github.com/esotericsoftware/spine-runtimes.git
|
||||
GIT_TAG 4.3
|
||||
SOURCE_SUBDIR spine-sdl
|
||||
)
|
||||
FetchContent_MakeAvailable(spine-sdl)
|
||||
|
||||
# Create your executable
|
||||
add_executable(MyApp main.cpp)
|
||||
|
||||
# For C API
|
||||
target_link_libraries(MyApp spine-sdl-c)
|
||||
|
||||
# For C++ API
|
||||
target_link_libraries(MyApp spine-sdl-cpp)
|
||||
```
|
||||
|
||||
This will automatically fetch and build spine-sdl along with all its dependencies (spine-c, spine-cpp, and SDL).
|
||||
|
||||
### Manual Integration
|
||||
|
||||
If you prefer manual integration:
|
||||
|
||||
1. Download the Spine Runtimes source using git (`git clone https://github.com/esotericsoftware/spine-runtimes`) or download as a zip.
|
||||
2. Add the required source files to your project:
|
||||
- For C API: Add sources from `spine-cpp/src`, `spine-c/src`, and `spine-sdl/src/spine-sdl-c.c`
|
||||
- For C++ API: Add sources from `spine-cpp/src`, `spine-c/src`, and `spine-sdl/src/spine-sdl-cpp.cpp`
|
||||
3. Add the include directories: `spine-cpp/include`, `spine-c/include`, and `spine-sdl/src`
|
||||
4. Link against SDL2
|
||||
|
||||
In your C or C++ code, include either of the following header files to get access to the `spine-sdl` API:
|
||||
|
||||
```cpp
|
||||
// C API
|
||||
#include <spine-sdl-c.h>
|
||||
|
||||
// C++ API
|
||||
#include <spine-sdl-cpp.h>
|
||||
```
|
||||
|
||||
> *Note:* spine-sdl requires the [`SDL_RenderGeometry`](https://wiki.libsdl.org/SDL_RenderGeometry) API which is available since SDL 2.0.18. Earlier versions of SDL are not compatible with spine-sdl.
|
||||
|
||||
## Samples
|
||||
The spine-sdl example works on Windows, Linux and Mac OS X. For a [spine-c](/spine-c) based example, see [example/main.c](/git/spine-runtimes/tree/spine-sdl/example/main.c), for a spine-cpp example see [example/main.cpp](/git/spine-runtimes/tree/spine-sdl/example/main.cpp).
|
||||
|
||||
### Windows
|
||||
|
||||
1. Install [Visual Studio Community](https://visualstudio.microsoft.com/downloads/). Make sure you install support for C++ and CMake.
|
||||
2. Download the Spine Runtimes repository using git (`git clone https://github.com/esotericsoftware/spine-runtimes`) or download it as a zip.
|
||||
3. Open Visual Studio Community, then open `spine-sdl/` via the **Open a local folder** button in the Visual Studio Community launcher.
|
||||
4. Wait for CMake to finish, then select either `spine-sdl-c-example.exe` or `spine-sdl-cpp-example.exe` as the start-up project and start debugging.
|
||||
|
||||
### Linux
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
sudo apt-get install cmake ninja-build # Ubuntu/Debian
|
||||
# or equivalent for your distribution
|
||||
```
|
||||
2. Clone the repository: `git clone https://github.com/esotericsoftware/spine-runtimes`
|
||||
3. Build and run:
|
||||
```bash
|
||||
cd spine-runtimes/spine-sdl
|
||||
./build.sh
|
||||
./build/debug/spine-sdl-c-example # Run C example
|
||||
./build/debug/spine-sdl-cpp-example # Run C++ example
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
1. Install [Xcode](https://developer.apple.com/xcode/)
|
||||
2. Install [Homebrew](http://brew.sh/)
|
||||
3. Install dependencies:
|
||||
```bash
|
||||
brew install cmake ninja
|
||||
```
|
||||
4. Clone the repository: `git clone https://github.com/esotericsoftware/spine-runtimes`
|
||||
5. Build and run:
|
||||
```bash
|
||||
cd spine-runtimes/spine-sdl
|
||||
./build.sh
|
||||
./build/debug/spine-sdl-c-example # Run C example
|
||||
./build/debug/spine-sdl-cpp-example # Run C++ example
|
||||
```
|
||||
|
||||
## Using spine-sdl
|
||||
The spine-sdl runtime supports playback and manipulation of animations created with Spine with [SDL](https://www.libsdl.org). The spine-sdl runtime comes as both a C and C++ implementation, based on the generic [spine-c](/spine-c) and [spine-cpp](/spine-cpp) runtimes. It adds loading and rendering implementations based on the SDL APIs.
|
||||
|
||||
Please consult the [Spine Runtimes Guide](/spine-runtimes) for a detailed overview of the Spine Runtime architecture, and the [spine-c](/spine-c) and [spine-cpp](/spine-cpp) documentation for information on the core APIs used to playback and manipulate animations created with Spine with C and C++.
|
||||
|
||||
### Exporting for SDL
|
||||

|
||||
Please follow the instructions in the Spine User Guide on how to
|
||||
|
||||
1. [Export skeleton & animation data](/spine-export)
|
||||
2. [Export texture atlases containing the images of your skeleton](/spine-texture-packer)
|
||||
|
||||
An export of the skeleton data and texture atlas of your skeleton will yield the following files:
|
||||
|
||||

|
||||
|
||||
1. `skeleton-name.json` or `skeleton-name.skel`, containing your skeleton and animation data.
|
||||
2. `skeleton-name.atlas`, containing information about the texture atlas.
|
||||
3. One or more `.png` files, each representing on page of your texture atlas containing the packed images your skeleton uses.
|
||||
|
||||
> **Note:** The spine-sdl runtime does not support two-color tinting.
|
||||
|
||||
### Loading Spine skeletons
|
||||
The spine-sdl runtime uses the [`SDL_Renderer`](https://wiki.libsdl.org/SDL_Renderer) API to display skeletons. Before a skeleton can be loaded from exported files, an `SDL_Renderer` must be created:
|
||||
|
||||
```cpp
|
||||
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
```
|
||||
|
||||
#### Loading with C API
|
||||
|
||||
With the new spine-c API, loading requires texture loading callbacks and manual file reading:
|
||||
|
||||
```cpp
|
||||
// Global renderer for texture loading callbacks
|
||||
static SDL_Renderer *g_renderer;
|
||||
|
||||
// Texture loading callbacks
|
||||
void *load_texture_callback(const char *path) {
|
||||
extern void *load_texture(const char *path, SDL_Renderer *renderer);
|
||||
return load_texture(path, g_renderer);
|
||||
}
|
||||
|
||||
void unload_texture_callback(void *texture) {
|
||||
SDL_DestroyTexture((SDL_Texture*)texture);
|
||||
}
|
||||
|
||||
// Set the global renderer
|
||||
g_renderer = renderer;
|
||||
|
||||
// Read atlas file into memory
|
||||
int atlas_length;
|
||||
char *atlas_data = read_file("data/spineboy-pma.atlas", &atlas_length);
|
||||
|
||||
// Load atlas with callbacks
|
||||
spine_atlas_result atlas_result = spine_atlas_load_callback(
|
||||
atlas_data, "data/", load_texture_callback, unload_texture_callback);
|
||||
spine_atlas atlas = spine_atlas_result_get_atlas(atlas_result);
|
||||
|
||||
// Read skeleton file into memory
|
||||
int skeleton_length;
|
||||
char *skeleton_data = read_file("data/spineboy-pro.json", &skeleton_length);
|
||||
|
||||
// Load skeleton data
|
||||
spine_skeleton_data_result skeleton_result = spine_skeleton_data_load_json(
|
||||
atlas, skeleton_data, "data/");
|
||||
spine_skeleton_data skeleton_data_handle = spine_skeleton_data_result_get_data(skeleton_result);
|
||||
|
||||
// Free file data
|
||||
free(atlas_data);
|
||||
free(skeleton_data);
|
||||
```
|
||||
|
||||
#### Loading with C++ API
|
||||
|
||||
For the C++ API, a `SDLTextureLoader` is needed:
|
||||
|
||||
```cpp
|
||||
// C++ API
|
||||
spine::SDLTextureLoader textureLoader(renderer);
|
||||
spine::Atlas atlas("data/spineboy-pma.atlas", &textureLoader);
|
||||
spine::SkeletonJson json(atlas);
|
||||
spine::SkeletonData *skeletonData = json.readSkeletonDataFile("data/spineboy-pro.json");
|
||||
```
|
||||
|
||||
The loaded skeleton data and atlas can and should be shared across skeleton instances to reduce memory consumption and enable batched rendering of skeletons that share the same atlas data.
|
||||
|
||||
### Rendering Skeletons
|
||||
|
||||
spine-sdl provides simple rendering functions that work directly with skeletons and skeleton drawables.
|
||||
|
||||
#### C API
|
||||
|
||||
```cpp
|
||||
// Create skeleton drawable (combines skeleton + animation state)
|
||||
spine_skeleton_drawable drawable = spine_skeleton_drawable_create(skeleton_data_handle);
|
||||
spine_skeleton skeleton = spine_skeleton_drawable_get_skeleton(drawable);
|
||||
spine_animation_state animation_state = spine_skeleton_drawable_get_animation_state(drawable);
|
||||
|
||||
// Setup skeleton
|
||||
spine_skeleton_set_position(skeleton, 400, 500);
|
||||
spine_skeleton_set_scale(skeleton, 0.5f, 0.5f);
|
||||
spine_skeleton_setup_pose(skeleton);
|
||||
|
||||
// Setup animation
|
||||
spine_animation_state_set_animation_1(animation_state, 0, "portal", false);
|
||||
spine_animation_state_add_animation_1(animation_state, 0, "run", true, 0);
|
||||
|
||||
// Update and render (in your main loop)
|
||||
spine_skeleton_drawable_update(drawable, deltaTime);
|
||||
spine_sdl_draw(drawable, renderer, true); // true for premultiplied alpha
|
||||
|
||||
// Or draw skeleton directly without drawable
|
||||
spine_sdl_draw_skeleton(skeleton, renderer, true);
|
||||
|
||||
// Cleanup
|
||||
spine_skeleton_drawable_dispose(drawable);
|
||||
spine_skeleton_data_dispose(skeleton_data_handle);
|
||||
spine_atlas_dispose(atlas);
|
||||
spine_skeleton_data_result_dispose(skeleton_result);
|
||||
spine_atlas_result_dispose(atlas_result);
|
||||
```
|
||||
|
||||
#### C++ API
|
||||
|
||||
```cpp
|
||||
// Create skeleton and animation state
|
||||
spine::Skeleton skeleton(*skeletonData);
|
||||
spine::AnimationStateData animationStateData(*skeletonData);
|
||||
spine::AnimationState animationState(animationStateData);
|
||||
|
||||
// Setup skeleton
|
||||
skeleton.setPosition(400, 500);
|
||||
skeleton.setupPose();
|
||||
|
||||
// Setup animation
|
||||
animationState.setAnimation(0, "portal", false);
|
||||
animationState.addAnimation(0, "run", true, 0);
|
||||
|
||||
// Update and render (in your main loop)
|
||||
animationState.update(deltaTime);
|
||||
animationState.apply(skeleton);
|
||||
skeleton.update(deltaTime);
|
||||
skeleton.updateWorldTransform(spine::Physics_Update);
|
||||
|
||||
// Draw using the simple SDL_draw function
|
||||
spine::SDL_draw(skeleton, renderer, true); // true for premultiplied alpha
|
||||
|
||||
// Cleanup
|
||||
delete skeletonData;
|
||||
delete &atlas;
|
||||
```
|
||||
|
||||
Please refer to the [spine-c](/spine-c) and [spine-cpp](/spine-cpp) documentation for more information on the APIs to manipulate skeletons and animation states.
|
||||
|
||||
### Cleanup
|
||||
|
||||
When you no longer need the skeleton and atlas data, free their memory:
|
||||
|
||||
#### C API
|
||||
```cpp
|
||||
// Dispose skeleton drawable and data
|
||||
spine_skeleton_drawable_dispose(drawable);
|
||||
spine_skeleton_data_dispose(skeleton_data_handle);
|
||||
spine_atlas_dispose(atlas);
|
||||
spine_skeleton_data_result_dispose(skeleton_result);
|
||||
spine_atlas_result_dispose(atlas_result);
|
||||
|
||||
// Free manually allocated file data
|
||||
free(atlas_data);
|
||||
free(skeleton_data);
|
||||
```
|
||||
|
||||
#### C++ API
|
||||
```cpp
|
||||
// C++ API cleanup
|
||||
delete skeletonData;
|
||||
delete &atlas; // If allocated with new
|
||||
```
|
||||
|
||||
> *Note:* Freeing skeleton data and atlas instances will automatically dispose of any associated SDL textures through the texture loader.
|
||||
@ -1,213 +0,0 @@
|
||||
# spine-sdl Runtime Documentation
|
||||
|
||||
> **Licensing**
|
||||
>
|
||||
> Please see the [Spine Runtimes License](/spine-runtimes-license) before integrating the Spine Runtimes into your applications.
|
||||
|
||||
# Getting Started
|
||||
spine-sdl is a C and C++ based runtime to load, manipulate and render Spine skeletons with [SDL](https://www.libsdl.org/).
|
||||
|
||||
spine-sdl requires SDL +2.0 and supports all Spine features except [two-color tinting](https://en.esotericsoftware.com/spine-slots#Tint-black)
|
||||
|
||||
## Installation
|
||||
|
||||
The spine-sdl runtime is available as a C and C++ API. The C API is based on the generic [spine-c](/spine-c) runtime, the C++ API is based on generic [spine-cpp](/spine-cpp) runtime. To integrate spine-sdl into your project:
|
||||
|
||||
1. Create a new SDL project. See the [SDL documentation](https://wiki.libsdl.org/FrontPage) or have a look at the example in [spine-runtimes repository](/git/spine-runtimes/tree/spine-sdl), which uses [CMake](https://cmake.org) as the build system.
|
||||
2. Download the Spine Runtimes source using git (`git clone https://github.com/esotericsoftware/spine-runtimes`).
|
||||
3. If you are using C:
|
||||
* Add the sources from `spine-c/spine-c/src/spine` and the file `spine-sdl/src/spine-sdl-c.c` to your project.
|
||||
* Add the folders `spine-c/spine-c/include` and `spine-sdl/src/` to your header search path.
|
||||
3. If you are using C++:
|
||||
* Add the sources from `spine-cpp/spine-cpp/src/spine` and the file `spine-sdl/src/spine-sdl-cpp.cpp` to your project.
|
||||
* Add the folders `spine-cpp/spine-cpp/include` and `spine-sdl/src` to your header search path.
|
||||
|
||||
In your C or C++ code, include either of the following header files to get access to the `spine-sdl` API:
|
||||
|
||||
```
|
||||
// C API
|
||||
#include <spine-sdl-c.h>
|
||||
|
||||
// C++ API
|
||||
#include <spine-sdl-cpp.h>
|
||||
```
|
||||
|
||||
> *Note:* spine-sdl requires the [`SDL_RenderGeometry`](https://wiki.libsdl.org/SDL_RenderGeometry) API which is available since SDL 2.0.18. Earlier versions of SDL are not compatible with spine-sdl.
|
||||
|
||||
## Samples
|
||||
The spine-sdl example works on Windows, Linux and Mac OS X. For a [spine-c](/spine-c) based example, see [example/main.c](/git/spine-runtimes/tree/spine-sdl/example/main.c), for a spine-cpp example see [example/main.cpp](/git/spine-runtimes/tree/spine-sdl/example/main.cpp).
|
||||
|
||||
### Windows
|
||||
1. Install [Visual Studio Community](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx). Make sure you install support for C++ and CMake.
|
||||
1. Download the Spine Runtimes repository using git (`git clone https://github.com/esotericsoftware/spine-runtimes`) or download it as a zip via the download button above.
|
||||
1. Open Visual Studio Community, then open `spine-sdl/` via the `Open a local folder` button in the Visual Studio Community launcher
|
||||
1. Wait for CMake to finish, then select either `spine-sdl-c-example.exe` or `spine-sdl-cpp-example.exe` as the start-up project and start debugging.
|
||||
|
||||
The entire example code is contained in [main.cpp](/git/spine-runtimes/tree/spine-sdl/example/main.cpp#L61).
|
||||
|
||||
### Linux
|
||||
1. Install the [SDL build dependencies](https://github.com/libsdl-org/SDL/blob/main/docs/README-linux.md).
|
||||
3. Download the Spine Runtimes repository using git (`git clone https://github.com/esotericsoftware/spine-runtimes`) or download it as a zip via the download button above.
|
||||
4. Open a terminal, and `cd` into the `spine-runtimes/spine-sdl` folder.
|
||||
5. Type `mkdir build && cd build && cmake ..` to generate Make files.
|
||||
6. Type `make -j` to compile the example.
|
||||
7. Run the example by `./spine-sdl-c-example` (C) or by `./spine-sdl-cpp-example` (C++).
|
||||
|
||||
### Mac OS X
|
||||
1. Install [Xcode](https://developer.apple.com/xcode/).
|
||||
2. Install [Homebrew](http://brew.sh/).
|
||||
3. Open a terminal and install CMake via `brew install cmake`.
|
||||
3. Download the Spine Runtimes repository using git (`git clone https://github.com/esotericsoftware/spine-runtimes`) or download it as a zip via the download button above.
|
||||
4. Open a terminal, and `cd` into the `spine-runtimes/spine-sdl` folder.
|
||||
5. Type `mkdir build && cd build && cmake -G Xcode ..` to generate Make files.
|
||||
6. Type `open spine-sdl.xcodeproj` to open the Xcode project.
|
||||
7. Run the example `spine-sdl-c-example` (C) or spine-sdl-cpp-example` (C++).
|
||||
|
||||
## Using spine-sdl
|
||||
The spine-sdl runtime supports playback and manipulation of animations created with Spine with [SDL](https://www.libsdl.org). The spine-sdl runtime comes as both a C and C++ implementation, based on the generic [spine-c](/spine-c) and [spine-cpp](/spine-cpp) runtimes. It adds loading and rendering implementations based on the SDL APIs.
|
||||
|
||||
Please consult the [Spine Runtimes Guide](/spine-runtimes) for a detailed overview of the Spine Runtime architecture, and the [spine-c](/spine-c) and [spine-cpp](/spine-cpp) documentation for information on the core APIs used to playback and manipulate animations created with Spine with C and C++.
|
||||
|
||||
### Exporting for SDL
|
||||

|
||||
Please follow the instructions in the Spine User Guide on how to
|
||||
|
||||
1. [Export skeleton & animation data](/spine-export)
|
||||
2. [Export texture atlases containing the images of your skeleton](/spine-texture-packer)
|
||||
|
||||
An export of the skeleton data and texture atlas of your skeleton will yield the following files:
|
||||
|
||||

|
||||
|
||||
1. `skeleton-name.json` or `skeleton-name.skel`, containing your skeleton and animation data.
|
||||
2. `skeleton-name.atlas`, containing information about the texture atlas.
|
||||
3. One or more `.png` files, each representing on page of your texture atlas containing the packed images your skeleton uses.
|
||||
|
||||
> **Note:** The spine-sdl runtime does also not support two color tinting and the screen blend mode available in the Spine editor.
|
||||
|
||||
### Loading Spine skeletons
|
||||
The spine-sdl runtime uses the [`SDL_Renderer`](https://wiki.libsdl.org/SDL_Renderer) API to display skeletons. Before a skeleton can be loaded from exported files, an `SDL_Renderer` must be created:
|
||||
|
||||
```
|
||||
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
```
|
||||
|
||||
Next, the texture atlas can be loaded like this using the C API:
|
||||
|
||||
```
|
||||
// C API
|
||||
spAtlas *atlas = spAtlas_createFromFile("data/spineboy.atlas", renderer);
|
||||
|
||||
```
|
||||
|
||||
For the C++ API, a `SDLTextureLoader` is needed:
|
||||
|
||||
```
|
||||
// C++ API
|
||||
spine::SDLTextureLoader textureLoader(renderer);
|
||||
spine::Atlas atlas("data/spineboy.atlas", &textureLoader);
|
||||
|
||||
```
|
||||
|
||||
With the atlas loaded, the `.json` or `.skel` file can be loaded using the C-API like this:
|
||||
|
||||
```
|
||||
// C API
|
||||
spSkeletonJson *json = spSkeletonJson_create(atlas);
|
||||
spSkeletonData *skeletonData = spSkeletonJson_readSkeletonDataFile(json, "data/spineboy-pro.json");
|
||||
spSkeletonJson_dispose(json);
|
||||
```
|
||||
|
||||
For the C++ API, loading of the skeleton file is achieved like this:
|
||||
|
||||
```
|
||||
// C++ API
|
||||
spine::AtlasAttachmentLoader attachmentLoader(&atlas);
|
||||
spine::SkeletonJson json(&attachmentLoader);
|
||||
spine::SkeletonData *skeletonData = json.readSkeletonDataFile("data/spineboy-pro.json");
|
||||
```
|
||||
|
||||
The `spAtlas`/`spine::Atlas` and `spSkeletonData`/`spine::SkeletonData` instances can then be used to create `spSkeletonDrawable`/`spine::SkeletonDrawable` instances to render the skeleton data.
|
||||
|
||||
> *Note:* the loaded skeleton data and atlas can and should be shared across `spSkeletonDrawable`/`spine::SkeletonDrawable` instances to reduce memory consumption and enable batched rendering of skeletons that share the same atlas data.
|
||||
|
||||
### Skeleton drawable
|
||||
The main addition of spine-sdl on top of [spine-c](/spine-c) and [spine-cpp](/spine-cpp) is the skeleton drawable. It holds a `spSkeleton` (C API) or `spine::Skeleton` (C++ API), which stores the skeletons bones, slots, attachments, constraints and so on, and a `spAnimationState` (C API) or `spine::AnimationState`, which manages animating the skeleton. The skeleton drawable provides methods to update the animation state, apply it to the skeleton, update the skeleton and draw the skeleton using `SDL_Renderer`.
|
||||
|
||||
You can creating a skeleton drawable using the C API like this:
|
||||
|
||||
```
|
||||
// C API
|
||||
spAnimationStateData *animationStateData = spAnimationStateData_create(skeletonData);
|
||||
spSkeletonDrawable *drawable = spSkeletonDrawable_create(skeletonData, animationStateData);
|
||||
```
|
||||
|
||||
The `spAnimationStateData` stores mix times between animations and is required to construct the internal `spAnimationState`. Please refer to the [spine-c](/spine-c) documentation for more information.
|
||||
|
||||
For the C++ API, creating a skeleton drawable works like this:
|
||||
|
||||
```
|
||||
// C++ API
|
||||
spine::SkeletonDrawable drawable(skeletonData);
|
||||
```
|
||||
|
||||
You can optionally pass a `spine::AnimationStateData` to the `spine::SkeletonDrawable` constructor if you want to share mix times between drawables. Please refer to the [spine-cpp](/spine-cpp) documentation for more information.
|
||||
|
||||
With the skeleton drawable created, you can access the contained skeleton and animation state instances to manipulate them.
|
||||
|
||||
```
|
||||
// C API
|
||||
drawable->skeleton->x = 400;
|
||||
drawable->skeleton->y = 500;
|
||||
spSkeleton_setToSetupPose(drawable->skeleton);
|
||||
|
||||
spAnimationState_setAnimationByName(drawable->animationState, 0, "portal", 0);
|
||||
spAnimationState_addAnimationByName(drawable->animationState, 0, "run", -1, 0);
|
||||
```
|
||||
|
||||
Or in C++:
|
||||
|
||||
```
|
||||
// C++ API
|
||||
drawable.skeleton->setPosition(400, 500);
|
||||
drawable.skeleton->setToSetupPose();
|
||||
|
||||
drawable.animationState->setAnimation(0, "portal", true);
|
||||
drawable.animationState->addAnimation(0, "run", true, 0);
|
||||
```
|
||||
|
||||
Please refer to the [spine-c](/spine-c) and [spine-cpp](/spine-cpp) documentation for more information on the APIs to manipulate skeletons and animation states.
|
||||
|
||||
The skeleton drawable also provides a method to conveniently update the skeleton and animation state it contains:
|
||||
|
||||
```
|
||||
// C API
|
||||
spSkeletonDrawable_update(drawable, deltaTimeInSeconds);
|
||||
|
||||
// C++ API
|
||||
drawable.update(deltaTimeInSeconds);
|
||||
```
|
||||
|
||||
The update method takes the delta time between the last and current frame in seconds and then updates the animation state, applies the animation state to the skeleton, and finally udpates the world transforms of the skeleton.
|
||||
|
||||
After the animation state and skeleton have been updated, it can be drawn:
|
||||
|
||||
```
|
||||
// C API
|
||||
spSkeletonDrawable_draw(drawable, renderer);
|
||||
|
||||
// C++ API
|
||||
drawable.draw(renderer);
|
||||
```
|
||||
|
||||
Once you no longer need the skeleton drawable, you can free its memory via:
|
||||
|
||||
```
|
||||
// C API
|
||||
spSkeletonDrawable_dipose(drawable);
|
||||
|
||||
// C++ API, if the drawable was allocated on the heap via new
|
||||
delete drawable;
|
||||
```
|
||||
|
||||
> *Note:* freeing a skeleton drawable does not free the skeleton data and atlas it was created from. The skeleton data and atlas need to be freed separately through the respective API (`spSkeletonData_dipose(skeletonData)`/`spAtlas_dispose(atlas)` in C, or `delete` in C++).
|
||||
@ -1,7 +1,16 @@
|
||||
- 4.3 release work
|
||||
- update docs
|
||||
- [ ] spine-c
|
||||
- [ ] spine-cpp
|
||||
- [x] spine-flutter
|
||||
- [x] spine-ios
|
||||
- [x] spine-sdl
|
||||
- [x] spine-sfml
|
||||
- [x] spine-glfw
|
||||
|
||||
- Port C++ SkeletonRenderer and RenderCommands to all runtimes
|
||||
- Will be used to snapshottesting via HeadlessTest, see also tests/
|
||||
- Can go into main package in all core runtimes, except for spine-libgdx, where it must go next to SkeletonSerializer in spine-libgdx-tests
|
||||
- Fix Dart NativeArray wrt to resize/add/remove. Current impl is wonky. Either make it read-only or support full mutabiliy (prefer latter)
|
||||
- Generate Godot wrappers from C++ types and/or spine-c generate() (unlike dart-writer.ts)?
|
||||
- headless-test improvements
|
||||
- should take cli args for ad-hoc testing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user