diff --git a/spine-flutter/example/lib/main.dart b/spine-flutter/example/lib/main.dart index 6f6b96228..59a188a09 100644 --- a/spine-flutter/example/lib/main.dart +++ b/spine-flutter/example/lib/main.dart @@ -35,9 +35,13 @@ class Spineboy extends StatelessWidget { @override Widget build(BuildContext context) { + final controller = SpineWidgetController((controller) { + controller.animationState?.setAnimation(0, "walk", true); + }); + return Scaffold( appBar: AppBar(title: const Text('Spineboy')), - body: const SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas"), + body: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", controller), // body: const SpineWidget.file("/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy-pro.skel", "/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy.atlas"), // body: const SpineWidget.http("https://marioslab.io/dump/spineboy/spineboy-pro.json", "https://marioslab.io/dump/spineboy/spineboy.atlas"), ); diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index f0abf8336..477c50461 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -16,15 +16,15 @@ int majorVersion() => _bindings.spine_major_version(); int minorVersion() => _bindings.spine_minor_version(); void reportLeaks() => _bindings.spine_report_leaks(); -class SpineAtlas { - Pointer _atlas; - List atlasPages; - List atlasPagePaints; +class Atlas { + final Pointer _atlas; + final List atlasPages; + final List atlasPagePaints; bool _disposed; - SpineAtlas(this._atlas, this.atlasPages, this.atlasPagePaints): _disposed = false; + Atlas(this._atlas, this.atlasPages, this.atlasPagePaints): _disposed = false; - static Future _load(String atlasFileName, Future Function(String name) loadFile) async { + static Future _load(String atlasFileName, Future Function(String name) loadFile) async { final atlasBytes = await loadFile(atlasFileName); final atlasData = convert.utf8.decode(atlasBytes); final atlasDataNative = atlasData.toNativeUtf8(); @@ -54,18 +54,18 @@ class SpineAtlas { ); } - return SpineAtlas(atlas, atlasPages, atlasPagePaints); + return Atlas(atlas, atlasPages, atlasPagePaints); } - static Future fromAsset(AssetBundle assetBundle, String atlasFileName) async { + static Future fromAsset(AssetBundle assetBundle, String atlasFileName) async { return _load(atlasFileName, (file) async => (await assetBundle.load(file)).buffer.asUint8List()); } - static Future fromFile(String atlasFileName) async { + static Future fromFile(String atlasFileName) async { return _load(atlasFileName, (file) => File(file).readAsBytes()); } - static Future fromUrl(String atlasFileName) async { + static Future fromUrl(String atlasFileName) async { return _load(atlasFileName, (file) async { return (await http.get(Uri.parse(file))).bodyBytes; }); @@ -79,13 +79,13 @@ class SpineAtlas { } } -class SpineSkeletonData { - Pointer _skeletonData; +class SkeletonData { + final Pointer _skeletonData; bool _disposed; - SpineSkeletonData(this._skeletonData): _disposed = false; + SkeletonData(this._skeletonData): _disposed = false; - static SpineSkeletonData fromJson(SpineAtlas atlas, String json) { + static SkeletonData fromJson(Atlas atlas, String json) { final jsonNative = json.toNativeUtf8(); final skeletonData = _bindings.spine_skeleton_data_load_json(atlas._atlas, jsonNative.cast()); if (skeletonData.ref.error.address != nullptr.address) { @@ -94,10 +94,10 @@ class SpineSkeletonData { _bindings.spine_skeleton_data_dispose(skeletonData); throw Exception("Couldn't load skeleton data: " + message); } - return SpineSkeletonData(skeletonData); + return SkeletonData(skeletonData); } - static SpineSkeletonData fromBinary(SpineAtlas atlas, Uint8List binary) { + static SkeletonData fromBinary(Atlas atlas, Uint8List binary) { final Pointer binaryNative = malloc.allocate(binary.lengthInBytes); binaryNative.asTypedList(binary.lengthInBytes).setAll(0, binary); final skeletonData = _bindings.spine_skeleton_data_load_binary(atlas._atlas, binaryNative.cast(), binary.lengthInBytes); @@ -108,7 +108,7 @@ class SpineSkeletonData { _bindings.spine_skeleton_data_dispose(skeletonData); throw Exception("Couldn't load skeleton data: " + message); } - return SpineSkeletonData(skeletonData); + return SkeletonData(skeletonData); } void dispose() { @@ -118,14 +118,127 @@ class SpineSkeletonData { } } -class SpineSkeletonDrawable { - SpineAtlas atlas; - SpineSkeletonData skeletonData; - late Pointer _drawable; +class Skeleton { + final spine_skeleton _skeleton; + + Skeleton(this._skeleton); +} + +class TrackEntry { + final spine_track_entry _entry; + + TrackEntry(this._entry); +} + +class AnimationState { + final spine_animation_state _state; + + AnimationState(this._state); + + /// Increments the track entry times, setting queued animations as current if needed + /// @param delta delta time + void update(double delta) { + _bindings.spine_animation_state_update(_state, delta); + } + + /// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the + /// animation state can be applied to multiple skeletons to pose them identically. + void apply(Skeleton skeleton) { + _bindings.spine_animation_state_apply(_state, skeleton._skeleton); + } + + /// Removes all animations from all tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTracks() { + _bindings.spine_animation_state_clear_tracks(_state); + } + + /// Removes all animations from the tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTrack(int trackIndex) { + _bindings.spine_animation_state_clear_track(_state, trackIndex); + } + + /// Sets the current animation for a track, discarding any queued animations. + /// @param loop If true, the animation will repeat. + /// If false, it will not, instead its last frame is applied if played beyond its duration. + /// In either case TrackEntry.TrackEnd determines when the track is cleared. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose. + TrackEntry setAnimation(int trackIndex, String animationName, bool loop) { + final animation = animationName.toNativeUtf8(); + final entry = _bindings.spine_animation_state_set_animation(_state, trackIndex, animation.cast(), loop ? -1 : 0); + calloc.free(animation); + if (entry.address == nullptr.address) throw Exception("Couldn't set animation $animationName"); + return TrackEntry(entry); + } + + /// Adds an animation to be played delay seconds after the current or last queued animation + /// for a track. If the track is empty, it is equivalent to calling setAnimation. + /// @param delay + /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + /// + /// @return A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose + TrackEntry addAnimation(int trackIndex, String animationName, bool loop, double delay) { + final animation = animationName.toNativeUtf8(); + final entry = _bindings.spine_animation_state_add_animation(_state, trackIndex, animation.cast(), loop ? -1 : 0, delay); + calloc.free(animation); + if (entry.address == nullptr.address) throw Exception("Couldn't add animation $animationName"); + return TrackEntry(entry); + } + + /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + TrackEntry setEmptyAnimation(int trackIndex, double mixDuration) { + final entry = _bindings.spine_animation_state_set_empty_animation(_state, trackIndex, mixDuration); + return TrackEntry(entry); + } + + /// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the + /// specified mix duration. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after AnimationState.Dispose. + /// + /// @param trackIndex Track number. + /// @param mixDuration Mix duration. + /// @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + TrackEntry addEmptyAnimation(int trackIndex, double mixDuration, double delay) { + final entry = _bindings.spine_animation_state_add_empty_animation(_state, trackIndex, mixDuration, delay); + return TrackEntry(entry); + } + + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + void setEmptyAnimations(double mixDuration) { + _bindings.spine_animation_state_set_empty_animations(_state, mixDuration); + } + + double getTimeScale() { + return _bindings.spine_animation_state_get_time_scale(_state); + } + + void setTimeScale(double timeScale) { + _bindings.spine_animation_state_set_time_scale(_state, timeScale); + } +} + +class SkeletonDrawable { + final Atlas atlas; + final SkeletonData skeletonData; + late final Pointer _drawable; + late final Skeleton skeleton; + late final AnimationState animationState; + final bool _ownsData; bool _disposed; - SpineSkeletonDrawable(this.atlas, this.skeletonData): _disposed = false { + SkeletonDrawable(this.atlas, this.skeletonData, this._ownsData): _disposed = false { _drawable = _bindings.spine_skeleton_drawable_create(skeletonData._skeletonData); + skeleton = Skeleton(_drawable.ref.skeleton); + animationState = AnimationState(_drawable.ref.animationState); } void update(double delta) { @@ -133,13 +246,13 @@ class SpineSkeletonDrawable { _bindings.spine_skeleton_drawable_update(_drawable, delta); } - List render() { + List render() { if (_disposed) return []; Pointer nativeCmd = _bindings.spine_skeleton_drawable_render(_drawable); - List commands = []; + List commands = []; while(nativeCmd.address != nullptr.address) { final atlasPage = atlas.atlasPages[nativeCmd.ref.atlasPage]; - commands.add(SpineRenderCommand(nativeCmd, atlasPage.width.toDouble(), atlasPage.height.toDouble())); + commands.add(RenderCommand(nativeCmd, atlasPage.width.toDouble(), atlasPage.height.toDouble())); nativeCmd = nativeCmd.ref.next; } return commands; @@ -148,17 +261,19 @@ class SpineSkeletonDrawable { void dispose() { if (_disposed) return; _disposed = true; - atlas.dispose(); - skeletonData.dispose(); + if (_ownsData) { + atlas.dispose(); + skeletonData.dispose(); + } _bindings.spine_skeleton_drawable_dispose(_drawable); } } -class SpineRenderCommand { - late Vertices vertices; - late int atlasPageIndex; +class RenderCommand { + late final Vertices vertices; + late final int atlasPageIndex; - SpineRenderCommand(Pointer nativeCmd, double pageWidth, double pageHeight) { + RenderCommand(Pointer nativeCmd, double pageWidth, double pageHeight) { atlasPageIndex = nativeCmd.ref.atlasPage; int numVertices = nativeCmd.ref.numVertices; int numIndices = nativeCmd.ref.numIndices; diff --git a/spine-flutter/lib/spine_flutter_bindings_generated.dart b/spine-flutter/lib/spine_flutter_bindings_generated.dart index 285053f2f..c852f49cf 100644 --- a/spine-flutter/lib/spine_flutter_bindings_generated.dart +++ b/spine-flutter/lib/spine_flutter_bindings_generated.dart @@ -208,6 +208,225 @@ class SpineFlutterBindings { late final _spine_skeleton_drawable_dispose = _spine_skeleton_drawable_disposePtr .asFunction)>(); + + void spine_animation_state_update( + spine_animation_state state, + double delta, + ) { + return _spine_animation_state_update( + state, + delta, + ); + } + + late final _spine_animation_state_updatePtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(spine_animation_state, + ffi.Float)>>('spine_animation_state_update'); + late final _spine_animation_state_update = _spine_animation_state_updatePtr + .asFunction(); + + void spine_animation_state_apply( + spine_animation_state state, + spine_skeleton skeleton, + ) { + return _spine_animation_state_apply( + state, + skeleton, + ); + } + + late final _spine_animation_state_applyPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(spine_animation_state, + spine_skeleton)>>('spine_animation_state_apply'); + late final _spine_animation_state_apply = _spine_animation_state_applyPtr + .asFunction(); + + void spine_animation_state_clear_tracks( + spine_animation_state state, + ) { + return _spine_animation_state_clear_tracks( + state, + ); + } + + late final _spine_animation_state_clear_tracksPtr = + _lookup>( + 'spine_animation_state_clear_tracks'); + late final _spine_animation_state_clear_tracks = + _spine_animation_state_clear_tracksPtr + .asFunction(); + + void spine_animation_state_clear_track( + spine_animation_state state, + int trackIndex, + ) { + return _spine_animation_state_clear_track( + state, + trackIndex, + ); + } + + late final _spine_animation_state_clear_trackPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(spine_animation_state, + ffi.Int32)>>('spine_animation_state_clear_track'); + late final _spine_animation_state_clear_track = + _spine_animation_state_clear_trackPtr + .asFunction(); + + spine_track_entry spine_animation_state_set_animation( + spine_animation_state state, + int trackIndex, + ffi.Pointer animationName, + int loop, + ) { + return _spine_animation_state_set_animation( + state, + trackIndex, + animationName, + loop, + ); + } + + late final _spine_animation_state_set_animationPtr = _lookup< + ffi.NativeFunction< + spine_track_entry Function( + spine_animation_state, + ffi.Int32, + ffi.Pointer, + ffi.Int32)>>('spine_animation_state_set_animation'); + late final _spine_animation_state_set_animation = + _spine_animation_state_set_animationPtr.asFunction< + spine_track_entry Function( + spine_animation_state, int, ffi.Pointer, int)>(); + + spine_track_entry spine_animation_state_add_animation( + spine_animation_state state, + int trackIndex, + ffi.Pointer animationName, + int loop, + double delay, + ) { + return _spine_animation_state_add_animation( + state, + trackIndex, + animationName, + loop, + delay, + ); + } + + late final _spine_animation_state_add_animationPtr = _lookup< + ffi.NativeFunction< + spine_track_entry Function( + spine_animation_state, + ffi.Int32, + ffi.Pointer, + ffi.Int32, + ffi.Float)>>('spine_animation_state_add_animation'); + late final _spine_animation_state_add_animation = + _spine_animation_state_add_animationPtr.asFunction< + spine_track_entry Function(spine_animation_state, int, + ffi.Pointer, int, double)>(); + + spine_track_entry spine_animation_state_set_empty_animation( + spine_animation_state state, + int trackIndex, + double mixDuration, + ) { + return _spine_animation_state_set_empty_animation( + state, + trackIndex, + mixDuration, + ); + } + + late final _spine_animation_state_set_empty_animationPtr = _lookup< + ffi.NativeFunction< + spine_track_entry Function(spine_animation_state, ffi.Int32, + ffi.Float)>>('spine_animation_state_set_empty_animation'); + late final _spine_animation_state_set_empty_animation = + _spine_animation_state_set_empty_animationPtr.asFunction< + spine_track_entry Function(spine_animation_state, int, double)>(); + + spine_track_entry spine_animation_state_add_empty_animation( + spine_animation_state state, + int trackIndex, + double mixDuration, + double delay, + ) { + return _spine_animation_state_add_empty_animation( + state, + trackIndex, + mixDuration, + delay, + ); + } + + late final _spine_animation_state_add_empty_animationPtr = _lookup< + ffi.NativeFunction< + spine_track_entry Function( + spine_animation_state, + ffi.Int32, + ffi.Float, + ffi.Float)>>('spine_animation_state_add_empty_animation'); + late final _spine_animation_state_add_empty_animation = + _spine_animation_state_add_empty_animationPtr.asFunction< + spine_track_entry Function( + spine_animation_state, int, double, double)>(); + + void spine_animation_state_set_empty_animations( + spine_animation_state state, + double mixDuration, + ) { + return _spine_animation_state_set_empty_animations( + state, + mixDuration, + ); + } + + late final _spine_animation_state_set_empty_animationsPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(spine_animation_state, + ffi.Float)>>('spine_animation_state_set_empty_animations'); + late final _spine_animation_state_set_empty_animations = + _spine_animation_state_set_empty_animationsPtr + .asFunction(); + + double spine_animation_state_get_time_scale( + spine_animation_state state, + ) { + return _spine_animation_state_get_time_scale( + state, + ); + } + + late final _spine_animation_state_get_time_scalePtr = + _lookup>( + 'spine_animation_state_get_time_scale'); + late final _spine_animation_state_get_time_scale = + _spine_animation_state_get_time_scalePtr + .asFunction(); + + void spine_animation_state_set_time_scale( + spine_animation_state state, + double timeScale, + ) { + return _spine_animation_state_set_time_scale( + state, + timeScale, + ); + } + + late final _spine_animation_state_set_time_scalePtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(spine_animation_state, + ffi.Float)>>('spine_animation_state_set_time_scale'); + late final _spine_animation_state_set_time_scale = + _spine_animation_state_set_time_scalePtr + .asFunction(); } class spine_atlas extends ffi.Struct { @@ -259,11 +478,15 @@ class spine_render_command extends ffi.Struct { } class spine_skeleton_drawable extends ffi.Struct { - external ffi.Pointer skeleton; + external spine_skeleton skeleton; - external ffi.Pointer animationState; + external spine_animation_state animationState; external ffi.Pointer clipping; external ffi.Pointer renderCommand; } + +typedef spine_skeleton = ffi.Pointer; +typedef spine_animation_state = ffi.Pointer; +typedef spine_track_entry = ffi.Pointer; diff --git a/spine-flutter/lib/spine_widget.dart b/spine-flutter/lib/spine_widget.dart index a9c964fb8..b38041672 100644 --- a/spine-flutter/lib/spine_widget.dart +++ b/spine-flutter/lib/spine_widget.dart @@ -9,78 +9,114 @@ import 'package:http/http.dart' as http; import 'spine_flutter.dart'; -abstract class SpineWidgetController { +class SpineWidgetController { + Atlas? _atlas; + SkeletonData? _data; + SkeletonDrawable? _drawable; + final void Function(SpineWidgetController controller)? onInitialized; + bool initialized = false; + + SpineWidgetController([this.onInitialized]); + + void _initialize(Atlas atlas, SkeletonData data, SkeletonDrawable drawable) { + _atlas = atlas; + _data = data; + _drawable = drawable; + initialized = true; + onInitialized?.call(this); + } + + Atlas? get atlas => _atlas; + SkeletonData? get skeletonData => _data; + AnimationState? get animationState => _drawable?.animationState; + Skeleton? get skeleton => _drawable?.skeleton; } enum AssetType { Asset, File, - Http + Http, + Raw } class SpineWidget extends StatefulWidget { - final String skeletonFile; - final String atlasFile; + final String? skeletonFile; + final String? atlasFile; + final SkeletonData? skeletonData; + final Atlas? atlas; + final SpineWidgetController controller; final AssetType _assetType; - const SpineWidget.asset(this.skeletonFile, this.atlasFile, {super.key}): _assetType = AssetType.Asset; - const SpineWidget.file(this.skeletonFile, this.atlasFile, {super.key}): _assetType = AssetType.File; - const SpineWidget.http(this.skeletonFile, this.atlasFile, {super.key}): _assetType = AssetType.Http; + const SpineWidget.asset(this.skeletonFile, this.atlasFile, this.controller, {super.key}): _assetType = AssetType.Asset, atlas = null, skeletonData = null; + const SpineWidget.file(this.skeletonFile, this.atlasFile, this.controller, {super.key}): _assetType = AssetType.File, atlas = null, skeletonData = null; + const SpineWidget.http(this.skeletonFile, this.atlasFile, this.controller, {super.key}): _assetType = AssetType.Http, atlas = null, skeletonData = null; + const SpineWidget.raw(this.skeletonData, this.atlas, this.controller, {super.key}): _assetType = AssetType.Raw, atlasFile = null, skeletonFile = null; @override State createState() => _SpineWidgetState(); } class _SpineWidgetState extends State { - SpineSkeletonDrawable? skeletonDrawable; + SkeletonDrawable? skeletonDrawable; @override void initState() { super.initState(); - loadSkeleton(widget.skeletonFile, widget.atlasFile, widget._assetType); + if (widget._assetType == AssetType.Raw) { + loadRaw(widget.skeletonData!, widget.atlas!); + } else { + loadFromAsset(widget.skeletonFile!, widget.atlasFile!, widget._assetType); + } } - void loadSkeleton(String skeletonFile, String atlasFile, AssetType assetType) async { - late SpineAtlas atlas; - late SpineSkeletonData skeletonData; + void loadRaw(SkeletonData skeletonData, Atlas atlas) { + skeletonDrawable = SkeletonDrawable(atlas, skeletonData, false); + skeletonDrawable?.update(0.016); + } + + void loadFromAsset(String skeletonFile, String atlasFile, AssetType assetType) async { + late Atlas atlas; + late SkeletonData skeletonData; switch (assetType) { case AssetType.Asset: - atlas = await SpineAtlas.fromAsset(rootBundle, atlasFile); + atlas = await Atlas.fromAsset(rootBundle, atlasFile); skeletonData = skeletonFile.endsWith(".json") - ? SpineSkeletonData.fromJson( + ? SkeletonData.fromJson( atlas, await rootBundle.loadString(skeletonFile)) - : SpineSkeletonData.fromBinary( + : SkeletonData.fromBinary( atlas, (await rootBundle.load(skeletonFile)).buffer.asUint8List()); break; case AssetType.File: - atlas = await SpineAtlas.fromFile(atlasFile); + atlas = await Atlas.fromFile(atlasFile); skeletonData = skeletonFile.endsWith(".json") - ? SpineSkeletonData.fromJson( + ? SkeletonData.fromJson( atlas, utf8.decode(await File(skeletonFile).readAsBytes())) - : SpineSkeletonData.fromBinary( + : SkeletonData.fromBinary( atlas, await File(skeletonFile).readAsBytes()); break; case AssetType.Http: - atlas = await SpineAtlas.fromUrl(atlasFile); + atlas = await Atlas.fromUrl(atlasFile); skeletonData = skeletonFile.endsWith(".json") - ? SpineSkeletonData.fromJson( + ? SkeletonData.fromJson( atlas, utf8.decode((await http.get(Uri.parse(skeletonFile))).bodyBytes)) - : SpineSkeletonData.fromBinary( + : SkeletonData.fromBinary( atlas, (await http.get(Uri.parse(skeletonFile))).bodyBytes); break; } - skeletonDrawable = SpineSkeletonDrawable(atlas, skeletonData); + skeletonDrawable = SkeletonDrawable(atlas, skeletonData, true); skeletonDrawable?.update(0.016); - setState(() {}); + widget.controller._initialize(atlas, skeletonData, skeletonDrawable!); + setState(() { + }); } @override Widget build(BuildContext context) { if (skeletonDrawable != null) { print("Skeleton loaded, rebuilding painter"); - return _SpineRenderObjectWidget(skeletonDrawable!); + return _SpineRenderObjectWidget(skeletonDrawable!, widget.controller); } else { print("Skeleton not loaded yet"); return SizedBox(); @@ -95,30 +131,32 @@ class _SpineWidgetState extends State { } class _SpineRenderObjectWidget extends LeafRenderObjectWidget { - final SpineSkeletonDrawable skeletonDrawable; + final SkeletonDrawable _skeletonDrawable; + final SpineWidgetController _controller; - _SpineRenderObjectWidget(this.skeletonDrawable); + _SpineRenderObjectWidget(this._skeletonDrawable, this._controller); @override RenderObject createRenderObject(BuildContext context) { - return _SpineRenderObject(skeletonDrawable); + return _SpineRenderObject(_skeletonDrawable, _controller); } @override void updateRenderObject(BuildContext context, covariant _SpineRenderObject renderObject) { - renderObject.skeletonDrawable = skeletonDrawable; + renderObject.skeletonDrawable = _skeletonDrawable; } } class _SpineRenderObject extends RenderBox { - SpineSkeletonDrawable _skeletonDrawable; + SkeletonDrawable _skeletonDrawable; + SpineWidgetController _controller; double _deltaTime = 0; final Stopwatch _stopwatch = Stopwatch(); - _SpineRenderObject(this._skeletonDrawable); + _SpineRenderObject(this._skeletonDrawable, this._controller); - set skeletonDrawable(SpineSkeletonDrawable skeletonDrawable) { + set skeletonDrawable(SkeletonDrawable skeletonDrawable) { if (_skeletonDrawable == skeletonDrawable) return; _skeletonDrawable = skeletonDrawable; diff --git a/spine-flutter/src/spine_flutter.cpp b/spine-flutter/src/spine_flutter.cpp index 8131a393a..15f5ee10f 100644 --- a/spine-flutter/src/spine_flutter.cpp +++ b/spine-flutter/src/spine_flutter.cpp @@ -5,6 +5,10 @@ using namespace spine; +spine::SpineExtension *spine::getDefaultExtension() { + return new spine::DebugExtension(new spine::DefaultSpineExtension()); +} + FFI_PLUGIN_EXPORT int32_t spine_major_version() { return SPINE_MAJOR_VERSION; } @@ -15,11 +19,11 @@ FFI_PLUGIN_EXPORT int32_t spine_minor_version() { FFI_PLUGIN_EXPORT spine_atlas* spine_atlas_load(const char *atlasData) { if (!atlasData) return nullptr; - int length = strlen(atlasData); + int length = (int)strlen(atlasData); auto atlas = new Atlas(atlasData, length, "", (TextureLoader*)nullptr, false); spine_atlas *result = SpineExtension::calloc(1, __FILE__, __LINE__); result->atlas = atlas; - result->numImagePaths = atlas->getPages().size(); + result->numImagePaths = (int32_t)atlas->getPages().size(); result->imagePaths = SpineExtension::calloc(result->numImagePaths, __FILE__, __LINE__); for (int i = 0; i < result->numImagePaths; i++) { result->imagePaths[i] = strdup(atlas->getPages()[i]->texturePath.buffer()); @@ -213,10 +217,10 @@ FFI_PLUGIN_EXPORT spine_render_command *spine_skeleton_drawable_render(spine_ske worldVertices.setSize(mesh->getWorldVerticesLength(), 0); pageIndex = ((AtlasRegion *) mesh->getRendererObject())->page->index; mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), worldVertices.buffer(), 0, 2); - verticesCount = mesh->getWorldVerticesLength() >> 1; + verticesCount = (int)(mesh->getWorldVerticesLength() >> 1); uvs = &mesh->getUVs(); indices = &mesh->getTriangles(); - indicesCount = indices->size(); + indicesCount = (int)indices->size(); } else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { ClippingAttachment *clip = (ClippingAttachment *) slot.getAttachment(); @@ -234,10 +238,10 @@ FFI_PLUGIN_EXPORT spine_render_command *spine_skeleton_drawable_render(spine_ske if (clipper.isClipping()) { clipper.clipTriangles(worldVertices, *indices, *uvs, 2); vertices = &clipper.getClippedVertices(); - verticesCount = clipper.getClippedVertices().size() >> 1; + verticesCount = (int)(clipper.getClippedVertices().size() >> 1); uvs = &clipper.getClippedUVs(); indices = &clipper.getClippedTriangles(); - indicesCount = clipper.getClippedTriangles().size(); + indicesCount = (int)(clipper.getClippedTriangles().size()); } spine_render_command *cmd = spine_render_command_create(verticesCount, indicesCount, (spine_blend_mode)slot.getData().getBlendMode(), pageIndex); @@ -261,6 +265,68 @@ FFI_PLUGIN_EXPORT spine_render_command *spine_skeleton_drawable_render(spine_ske return drawable->renderCommand; } -spine::SpineExtension *spine::getDefaultExtension() { - return new spine::DebugExtension(new spine::DefaultSpineExtension()); +FFI_PLUGIN_EXPORT void spine_animation_state_update(spine_animation_state state, float delta) { + if (state == nullptr) return; + AnimationState *_state = (AnimationState*)state; + _state->update(delta); +} + +FFI_PLUGIN_EXPORT void spine_animation_state_apply(spine_animation_state state, spine_skeleton skeleton) { + if (state == nullptr) return; + AnimationState *_state = (AnimationState*)state; + _state->apply(*(Skeleton*)skeleton); +} + +FFI_PLUGIN_EXPORT void spine_animation_state_clear_tracks(spine_animation_state state) { + if (state == nullptr) return; + AnimationState *_state = (AnimationState*)state; + _state->clearTracks(); +} + +FFI_PLUGIN_EXPORT void spine_animation_state_clear_track(spine_animation_state state, int32_t trackIndex) { + if (state == nullptr) return; + AnimationState *_state = (AnimationState*)state; + _state->clearTrack(trackIndex); +} + +FFI_PLUGIN_EXPORT spine_track_entry spine_animation_state_set_animation(spine_animation_state state, int32_t trackIndex, const char* animationName, int32_t loop) { + if (state == nullptr) return nullptr; + AnimationState *_state = (AnimationState*)state; + return _state->setAnimation(trackIndex, animationName, loop); +} + +FFI_PLUGIN_EXPORT spine_track_entry spine_animation_state_add_animation(spine_animation_state state, int32_t trackIndex, const char* animationName, int32_t loop, float delay) { + if (state == nullptr) return nullptr; + AnimationState *_state = (AnimationState*)state; + return _state->addAnimation(trackIndex, animationName, loop, delay); +} + +FFI_PLUGIN_EXPORT spine_track_entry spine_animation_state_set_empty_animation(spine_animation_state state, int32_t trackIndex, float mixDuration) { + if (state == nullptr) return nullptr; + AnimationState *_state = (AnimationState*)state; + return _state->setEmptyAnimation(trackIndex, mixDuration); +} + +FFI_PLUGIN_EXPORT spine_track_entry spine_animation_state_add_empty_animation(spine_animation_state state, int32_t trackIndex, float mixDuration, float delay) { + if (state == nullptr) return nullptr; + AnimationState *_state = (AnimationState*)state; + return _state->addEmptyAnimation(trackIndex, mixDuration, delay); +} + +FFI_PLUGIN_EXPORT void spine_animation_state_set_empty_animations(spine_animation_state state, float mixDuration) { + if (state == nullptr) return; + AnimationState *_state = (AnimationState*)state; + _state->setEmptyAnimations(mixDuration); +} + +FFI_PLUGIN_EXPORT float spine_animation_state_get_time_scale(spine_animation_state state) { + if (state == nullptr) return 0; + AnimationState *_state = (AnimationState*)state; + return _state->getTimeScale(); +} + +FFI_PLUGIN_EXPORT void spine_animation_state_set_time_scale(spine_animation_state state, float timeScale) { + if (state == nullptr) return; + AnimationState *_state = (AnimationState*)state; + _state->setTimeScale(timeScale); } diff --git a/spine-flutter/src/spine_flutter.h b/spine-flutter/src/spine_flutter.h index 1e7fcbb34..b80b9a3bb 100644 --- a/spine-flutter/src/spine_flutter.h +++ b/spine-flutter/src/spine_flutter.h @@ -65,9 +65,13 @@ typedef struct spine_render_command { struct spine_render_command *next; } spine_render_command; +typedef void* spine_skeleton; +typedef void* spine_animation_state; +typedef void* spine_track_entry; + typedef struct spine_skeleton_drawable { - void *skeleton; - void *animationState; + spine_skeleton skeleton; + spine_animation_state animationState; void *clipping; spine_render_command *renderCommand; } spine_skeleton_drawable; @@ -77,3 +81,14 @@ FFI_PLUGIN_EXPORT void spine_skeleton_drawable_update(spine_skeleton_drawable *d FFI_PLUGIN_EXPORT spine_render_command *spine_skeleton_drawable_render(spine_skeleton_drawable *drawable); FFI_PLUGIN_EXPORT void spine_skeleton_drawable_dispose(spine_skeleton_drawable *drawable); +FFI_PLUGIN_EXPORT void spine_animation_state_update(spine_animation_state state, float delta); +FFI_PLUGIN_EXPORT void spine_animation_state_apply(spine_animation_state state, spine_skeleton skeleton); +FFI_PLUGIN_EXPORT void spine_animation_state_clear_tracks(spine_animation_state state); +FFI_PLUGIN_EXPORT void spine_animation_state_clear_track(spine_animation_state state, int32_t trackIndex); +FFI_PLUGIN_EXPORT spine_track_entry spine_animation_state_set_animation(spine_animation_state state, int32_t trackIndex, const char* animationName, int32_t loop); +FFI_PLUGIN_EXPORT spine_track_entry spine_animation_state_add_animation(spine_animation_state state, int32_t trackIndex, const char* animationName, int32_t loop, float delay); +FFI_PLUGIN_EXPORT spine_track_entry spine_animation_state_set_empty_animation(spine_animation_state state, int32_t trackIndex, float mixDuration); +FFI_PLUGIN_EXPORT spine_track_entry spine_animation_state_add_empty_animation(spine_animation_state state, int32_t trackIndex, float mixDuration, float delay); +FFI_PLUGIN_EXPORT void spine_animation_state_set_empty_animations(spine_animation_state state, float mixDuration); +FFI_PLUGIN_EXPORT float spine_animation_state_get_time_scale(spine_animation_state state); +FFI_PLUGIN_EXPORT void spine_animation_state_set_time_scale(spine_animation_state state, float timeScale);