[flutte] Add controller, wrap AnimationState.

This commit is contained in:
Mario Zechner 2022-08-26 15:46:48 +02:00
parent 2e78d64a23
commit 1dd2c99c70
6 changed files with 536 additions and 75 deletions

View File

@ -35,9 +35,13 @@ class Spineboy extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final controller = SpineWidgetController((controller) {
controller.animationState?.setAnimation(0, "walk", true);
});
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Spineboy')), 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.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"), // body: const SpineWidget.http("https://marioslab.io/dump/spineboy/spineboy-pro.json", "https://marioslab.io/dump/spineboy/spineboy.atlas"),
); );

View File

@ -16,15 +16,15 @@ int majorVersion() => _bindings.spine_major_version();
int minorVersion() => _bindings.spine_minor_version(); int minorVersion() => _bindings.spine_minor_version();
void reportLeaks() => _bindings.spine_report_leaks(); void reportLeaks() => _bindings.spine_report_leaks();
class SpineAtlas { class Atlas {
Pointer<spine_atlas> _atlas; final Pointer<spine_atlas> _atlas;
List<Image> atlasPages; final List<Image> atlasPages;
List<Paint> atlasPagePaints; final List<Paint> atlasPagePaints;
bool _disposed; bool _disposed;
SpineAtlas(this._atlas, this.atlasPages, this.atlasPagePaints): _disposed = false; Atlas(this._atlas, this.atlasPages, this.atlasPagePaints): _disposed = false;
static Future<SpineAtlas> _load(String atlasFileName, Future<Uint8List> Function(String name) loadFile) async { static Future<Atlas> _load(String atlasFileName, Future<Uint8List> Function(String name) loadFile) async {
final atlasBytes = await loadFile(atlasFileName); final atlasBytes = await loadFile(atlasFileName);
final atlasData = convert.utf8.decode(atlasBytes); final atlasData = convert.utf8.decode(atlasBytes);
final atlasDataNative = atlasData.toNativeUtf8(); final atlasDataNative = atlasData.toNativeUtf8();
@ -54,18 +54,18 @@ class SpineAtlas {
); );
} }
return SpineAtlas(atlas, atlasPages, atlasPagePaints); return Atlas(atlas, atlasPages, atlasPagePaints);
} }
static Future<SpineAtlas> fromAsset(AssetBundle assetBundle, String atlasFileName) async { static Future<Atlas> fromAsset(AssetBundle assetBundle, String atlasFileName) async {
return _load(atlasFileName, (file) async => (await assetBundle.load(file)).buffer.asUint8List()); return _load(atlasFileName, (file) async => (await assetBundle.load(file)).buffer.asUint8List());
} }
static Future<SpineAtlas> fromFile(String atlasFileName) async { static Future<Atlas> fromFile(String atlasFileName) async {
return _load(atlasFileName, (file) => File(file).readAsBytes()); return _load(atlasFileName, (file) => File(file).readAsBytes());
} }
static Future<SpineAtlas> fromUrl(String atlasFileName) async { static Future<Atlas> fromUrl(String atlasFileName) async {
return _load(atlasFileName, (file) async { return _load(atlasFileName, (file) async {
return (await http.get(Uri.parse(file))).bodyBytes; return (await http.get(Uri.parse(file))).bodyBytes;
}); });
@ -79,13 +79,13 @@ class SpineAtlas {
} }
} }
class SpineSkeletonData { class SkeletonData {
Pointer<spine_skeleton_data> _skeletonData; final Pointer<spine_skeleton_data> _skeletonData;
bool _disposed; 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 jsonNative = json.toNativeUtf8();
final skeletonData = _bindings.spine_skeleton_data_load_json(atlas._atlas, jsonNative.cast()); final skeletonData = _bindings.spine_skeleton_data_load_json(atlas._atlas, jsonNative.cast());
if (skeletonData.ref.error.address != nullptr.address) { if (skeletonData.ref.error.address != nullptr.address) {
@ -94,10 +94,10 @@ class SpineSkeletonData {
_bindings.spine_skeleton_data_dispose(skeletonData); _bindings.spine_skeleton_data_dispose(skeletonData);
throw Exception("Couldn't load skeleton data: " + message); 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<Uint8> binaryNative = malloc.allocate(binary.lengthInBytes); final Pointer<Uint8> binaryNative = malloc.allocate(binary.lengthInBytes);
binaryNative.asTypedList(binary.lengthInBytes).setAll(0, binary); binaryNative.asTypedList(binary.lengthInBytes).setAll(0, binary);
final skeletonData = _bindings.spine_skeleton_data_load_binary(atlas._atlas, binaryNative.cast(), binary.lengthInBytes); 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); _bindings.spine_skeleton_data_dispose(skeletonData);
throw Exception("Couldn't load skeleton data: " + message); throw Exception("Couldn't load skeleton data: " + message);
} }
return SpineSkeletonData(skeletonData); return SkeletonData(skeletonData);
} }
void dispose() { void dispose() {
@ -118,14 +118,127 @@ class SpineSkeletonData {
} }
} }
class SpineSkeletonDrawable { class Skeleton {
SpineAtlas atlas; final spine_skeleton _skeleton;
SpineSkeletonData skeletonData;
late Pointer<spine_skeleton_drawable> _drawable; 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 &lt;= 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 &lt;= 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<spine_skeleton_drawable> _drawable;
late final Skeleton skeleton;
late final AnimationState animationState;
final bool _ownsData;
bool _disposed; 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); _drawable = _bindings.spine_skeleton_drawable_create(skeletonData._skeletonData);
skeleton = Skeleton(_drawable.ref.skeleton);
animationState = AnimationState(_drawable.ref.animationState);
} }
void update(double delta) { void update(double delta) {
@ -133,13 +246,13 @@ class SpineSkeletonDrawable {
_bindings.spine_skeleton_drawable_update(_drawable, delta); _bindings.spine_skeleton_drawable_update(_drawable, delta);
} }
List<SpineRenderCommand> render() { List<RenderCommand> render() {
if (_disposed) return []; if (_disposed) return [];
Pointer<spine_render_command> nativeCmd = _bindings.spine_skeleton_drawable_render(_drawable); Pointer<spine_render_command> nativeCmd = _bindings.spine_skeleton_drawable_render(_drawable);
List<SpineRenderCommand> commands = []; List<RenderCommand> commands = [];
while(nativeCmd.address != nullptr.address) { while(nativeCmd.address != nullptr.address) {
final atlasPage = atlas.atlasPages[nativeCmd.ref.atlasPage]; 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; nativeCmd = nativeCmd.ref.next;
} }
return commands; return commands;
@ -148,17 +261,19 @@ class SpineSkeletonDrawable {
void dispose() { void dispose() {
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
atlas.dispose(); if (_ownsData) {
skeletonData.dispose(); atlas.dispose();
skeletonData.dispose();
}
_bindings.spine_skeleton_drawable_dispose(_drawable); _bindings.spine_skeleton_drawable_dispose(_drawable);
} }
} }
class SpineRenderCommand { class RenderCommand {
late Vertices vertices; late final Vertices vertices;
late int atlasPageIndex; late final int atlasPageIndex;
SpineRenderCommand(Pointer<spine_render_command> nativeCmd, double pageWidth, double pageHeight) { RenderCommand(Pointer<spine_render_command> nativeCmd, double pageWidth, double pageHeight) {
atlasPageIndex = nativeCmd.ref.atlasPage; atlasPageIndex = nativeCmd.ref.atlasPage;
int numVertices = nativeCmd.ref.numVertices; int numVertices = nativeCmd.ref.numVertices;
int numIndices = nativeCmd.ref.numIndices; int numIndices = nativeCmd.ref.numIndices;

View File

@ -208,6 +208,225 @@ class SpineFlutterBindings {
late final _spine_skeleton_drawable_dispose = late final _spine_skeleton_drawable_dispose =
_spine_skeleton_drawable_disposePtr _spine_skeleton_drawable_disposePtr
.asFunction<void Function(ffi.Pointer<spine_skeleton_drawable>)>(); .asFunction<void Function(ffi.Pointer<spine_skeleton_drawable>)>();
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 Function(spine_animation_state, double)>();
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 Function(spine_animation_state, spine_skeleton)>();
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<ffi.NativeFunction<ffi.Void Function(spine_animation_state)>>(
'spine_animation_state_clear_tracks');
late final _spine_animation_state_clear_tracks =
_spine_animation_state_clear_tracksPtr
.asFunction<void Function(spine_animation_state)>();
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<void Function(spine_animation_state, int)>();
spine_track_entry spine_animation_state_set_animation(
spine_animation_state state,
int trackIndex,
ffi.Pointer<ffi.Int8> 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.Int8>,
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<ffi.Int8>, int)>();
spine_track_entry spine_animation_state_add_animation(
spine_animation_state state,
int trackIndex,
ffi.Pointer<ffi.Int8> 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.Int8>,
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<ffi.Int8>, 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<void Function(spine_animation_state, double)>();
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<ffi.NativeFunction<ffi.Float Function(spine_animation_state)>>(
'spine_animation_state_get_time_scale');
late final _spine_animation_state_get_time_scale =
_spine_animation_state_get_time_scalePtr
.asFunction<double Function(spine_animation_state)>();
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<void Function(spine_animation_state, double)>();
} }
class spine_atlas extends ffi.Struct { class spine_atlas extends ffi.Struct {
@ -259,11 +478,15 @@ class spine_render_command extends ffi.Struct {
} }
class spine_skeleton_drawable extends ffi.Struct { class spine_skeleton_drawable extends ffi.Struct {
external ffi.Pointer<ffi.Void> skeleton; external spine_skeleton skeleton;
external ffi.Pointer<ffi.Void> animationState; external spine_animation_state animationState;
external ffi.Pointer<ffi.Void> clipping; external ffi.Pointer<ffi.Void> clipping;
external ffi.Pointer<spine_render_command> renderCommand; external ffi.Pointer<spine_render_command> renderCommand;
} }
typedef spine_skeleton = ffi.Pointer<ffi.Void>;
typedef spine_animation_state = ffi.Pointer<ffi.Void>;
typedef spine_track_entry = ffi.Pointer<ffi.Void>;

View File

@ -9,78 +9,114 @@ import 'package:http/http.dart' as http;
import 'spine_flutter.dart'; 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 { enum AssetType {
Asset, Asset,
File, File,
Http Http,
Raw
} }
class SpineWidget extends StatefulWidget { class SpineWidget extends StatefulWidget {
final String skeletonFile; final String? skeletonFile;
final String atlasFile; final String? atlasFile;
final SkeletonData? skeletonData;
final Atlas? atlas;
final SpineWidgetController controller;
final AssetType _assetType; final AssetType _assetType;
const SpineWidget.asset(this.skeletonFile, this.atlasFile, {super.key}): _assetType = AssetType.Asset; 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, {super.key}): _assetType = AssetType.File; 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, {super.key}): _assetType = AssetType.Http; 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 @override
State<SpineWidget> createState() => _SpineWidgetState(); State<SpineWidget> createState() => _SpineWidgetState();
} }
class _SpineWidgetState extends State<SpineWidget> { class _SpineWidgetState extends State<SpineWidget> {
SpineSkeletonDrawable? skeletonDrawable; SkeletonDrawable? skeletonDrawable;
@override @override
void initState() { void initState() {
super.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 { void loadRaw(SkeletonData skeletonData, Atlas atlas) {
late SpineAtlas atlas; skeletonDrawable = SkeletonDrawable(atlas, skeletonData, false);
late SpineSkeletonData skeletonData; skeletonDrawable?.update(0.016);
}
void loadFromAsset(String skeletonFile, String atlasFile, AssetType assetType) async {
late Atlas atlas;
late SkeletonData skeletonData;
switch (assetType) { switch (assetType) {
case AssetType.Asset: case AssetType.Asset:
atlas = await SpineAtlas.fromAsset(rootBundle, atlasFile); atlas = await Atlas.fromAsset(rootBundle, atlasFile);
skeletonData = skeletonFile.endsWith(".json") skeletonData = skeletonFile.endsWith(".json")
? SpineSkeletonData.fromJson( ? SkeletonData.fromJson(
atlas, await rootBundle.loadString(skeletonFile)) atlas, await rootBundle.loadString(skeletonFile))
: SpineSkeletonData.fromBinary( : SkeletonData.fromBinary(
atlas, (await rootBundle.load(skeletonFile)).buffer.asUint8List()); atlas, (await rootBundle.load(skeletonFile)).buffer.asUint8List());
break; break;
case AssetType.File: case AssetType.File:
atlas = await SpineAtlas.fromFile(atlasFile); atlas = await Atlas.fromFile(atlasFile);
skeletonData = skeletonFile.endsWith(".json") skeletonData = skeletonFile.endsWith(".json")
? SpineSkeletonData.fromJson( ? SkeletonData.fromJson(
atlas, utf8.decode(await File(skeletonFile).readAsBytes())) atlas, utf8.decode(await File(skeletonFile).readAsBytes()))
: SpineSkeletonData.fromBinary( : SkeletonData.fromBinary(
atlas, await File(skeletonFile).readAsBytes()); atlas, await File(skeletonFile).readAsBytes());
break; break;
case AssetType.Http: case AssetType.Http:
atlas = await SpineAtlas.fromUrl(atlasFile); atlas = await Atlas.fromUrl(atlasFile);
skeletonData = skeletonFile.endsWith(".json") skeletonData = skeletonFile.endsWith(".json")
? SpineSkeletonData.fromJson( ? SkeletonData.fromJson(
atlas, utf8.decode((await http.get(Uri.parse(skeletonFile))).bodyBytes)) atlas, utf8.decode((await http.get(Uri.parse(skeletonFile))).bodyBytes))
: SpineSkeletonData.fromBinary( : SkeletonData.fromBinary(
atlas, (await http.get(Uri.parse(skeletonFile))).bodyBytes); atlas, (await http.get(Uri.parse(skeletonFile))).bodyBytes);
break; break;
} }
skeletonDrawable = SpineSkeletonDrawable(atlas, skeletonData); skeletonDrawable = SkeletonDrawable(atlas, skeletonData, true);
skeletonDrawable?.update(0.016); skeletonDrawable?.update(0.016);
setState(() {}); widget.controller._initialize(atlas, skeletonData, skeletonDrawable!);
setState(() {
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (skeletonDrawable != null) { if (skeletonDrawable != null) {
print("Skeleton loaded, rebuilding painter"); print("Skeleton loaded, rebuilding painter");
return _SpineRenderObjectWidget(skeletonDrawable!); return _SpineRenderObjectWidget(skeletonDrawable!, widget.controller);
} else { } else {
print("Skeleton not loaded yet"); print("Skeleton not loaded yet");
return SizedBox(); return SizedBox();
@ -95,30 +131,32 @@ class _SpineWidgetState extends State<SpineWidget> {
} }
class _SpineRenderObjectWidget extends LeafRenderObjectWidget { class _SpineRenderObjectWidget extends LeafRenderObjectWidget {
final SpineSkeletonDrawable skeletonDrawable; final SkeletonDrawable _skeletonDrawable;
final SpineWidgetController _controller;
_SpineRenderObjectWidget(this.skeletonDrawable); _SpineRenderObjectWidget(this._skeletonDrawable, this._controller);
@override @override
RenderObject createRenderObject(BuildContext context) { RenderObject createRenderObject(BuildContext context) {
return _SpineRenderObject(skeletonDrawable); return _SpineRenderObject(_skeletonDrawable, _controller);
} }
@override @override
void updateRenderObject(BuildContext context, void updateRenderObject(BuildContext context,
covariant _SpineRenderObject renderObject) { covariant _SpineRenderObject renderObject) {
renderObject.skeletonDrawable = skeletonDrawable; renderObject.skeletonDrawable = _skeletonDrawable;
} }
} }
class _SpineRenderObject extends RenderBox { class _SpineRenderObject extends RenderBox {
SpineSkeletonDrawable _skeletonDrawable; SkeletonDrawable _skeletonDrawable;
SpineWidgetController _controller;
double _deltaTime = 0; double _deltaTime = 0;
final Stopwatch _stopwatch = Stopwatch(); final Stopwatch _stopwatch = Stopwatch();
_SpineRenderObject(this._skeletonDrawable); _SpineRenderObject(this._skeletonDrawable, this._controller);
set skeletonDrawable(SpineSkeletonDrawable skeletonDrawable) { set skeletonDrawable(SkeletonDrawable skeletonDrawable) {
if (_skeletonDrawable == skeletonDrawable) return; if (_skeletonDrawable == skeletonDrawable) return;
_skeletonDrawable = skeletonDrawable; _skeletonDrawable = skeletonDrawable;

View File

@ -5,6 +5,10 @@
using namespace spine; using namespace spine;
spine::SpineExtension *spine::getDefaultExtension() {
return new spine::DebugExtension(new spine::DefaultSpineExtension());
}
FFI_PLUGIN_EXPORT int32_t spine_major_version() { FFI_PLUGIN_EXPORT int32_t spine_major_version() {
return 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) { FFI_PLUGIN_EXPORT spine_atlas* spine_atlas_load(const char *atlasData) {
if (!atlasData) return nullptr; if (!atlasData) return nullptr;
int length = strlen(atlasData); int length = (int)strlen(atlasData);
auto atlas = new Atlas(atlasData, length, "", (TextureLoader*)nullptr, false); auto atlas = new Atlas(atlasData, length, "", (TextureLoader*)nullptr, false);
spine_atlas *result = SpineExtension::calloc<spine_atlas>(1, __FILE__, __LINE__); spine_atlas *result = SpineExtension::calloc<spine_atlas>(1, __FILE__, __LINE__);
result->atlas = atlas; result->atlas = atlas;
result->numImagePaths = atlas->getPages().size(); result->numImagePaths = (int32_t)atlas->getPages().size();
result->imagePaths = SpineExtension::calloc<char *>(result->numImagePaths, __FILE__, __LINE__); result->imagePaths = SpineExtension::calloc<char *>(result->numImagePaths, __FILE__, __LINE__);
for (int i = 0; i < result->numImagePaths; i++) { for (int i = 0; i < result->numImagePaths; i++) {
result->imagePaths[i] = strdup(atlas->getPages()[i]->texturePath.buffer()); 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); worldVertices.setSize(mesh->getWorldVerticesLength(), 0);
pageIndex = ((AtlasRegion *) mesh->getRendererObject())->page->index; pageIndex = ((AtlasRegion *) mesh->getRendererObject())->page->index;
mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), worldVertices.buffer(), 0, 2); mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), worldVertices.buffer(), 0, 2);
verticesCount = mesh->getWorldVerticesLength() >> 1; verticesCount = (int)(mesh->getWorldVerticesLength() >> 1);
uvs = &mesh->getUVs(); uvs = &mesh->getUVs();
indices = &mesh->getTriangles(); indices = &mesh->getTriangles();
indicesCount = indices->size(); indicesCount = (int)indices->size();
} else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { } else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) {
ClippingAttachment *clip = (ClippingAttachment *) slot.getAttachment(); ClippingAttachment *clip = (ClippingAttachment *) slot.getAttachment();
@ -234,10 +238,10 @@ FFI_PLUGIN_EXPORT spine_render_command *spine_skeleton_drawable_render(spine_ske
if (clipper.isClipping()) { if (clipper.isClipping()) {
clipper.clipTriangles(worldVertices, *indices, *uvs, 2); clipper.clipTriangles(worldVertices, *indices, *uvs, 2);
vertices = &clipper.getClippedVertices(); vertices = &clipper.getClippedVertices();
verticesCount = clipper.getClippedVertices().size() >> 1; verticesCount = (int)(clipper.getClippedVertices().size() >> 1);
uvs = &clipper.getClippedUVs(); uvs = &clipper.getClippedUVs();
indices = &clipper.getClippedTriangles(); 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); 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; return drawable->renderCommand;
} }
spine::SpineExtension *spine::getDefaultExtension() { FFI_PLUGIN_EXPORT void spine_animation_state_update(spine_animation_state state, float delta) {
return new spine::DebugExtension(new spine::DefaultSpineExtension()); 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);
} }

View File

@ -65,9 +65,13 @@ typedef struct spine_render_command {
struct spine_render_command *next; struct spine_render_command *next;
} spine_render_command; } spine_render_command;
typedef void* spine_skeleton;
typedef void* spine_animation_state;
typedef void* spine_track_entry;
typedef struct spine_skeleton_drawable { typedef struct spine_skeleton_drawable {
void *skeleton; spine_skeleton skeleton;
void *animationState; spine_animation_state animationState;
void *clipping; void *clipping;
spine_render_command *renderCommand; spine_render_command *renderCommand;
} spine_skeleton_drawable; } 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 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_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);