[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
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"),
);

View File

@ -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<spine_atlas> _atlas;
List<Image> atlasPages;
List<Paint> atlasPagePaints;
class Atlas {
final Pointer<spine_atlas> _atlas;
final List<Image> atlasPages;
final List<Paint> atlasPagePaints;
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 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<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());
}
static Future<SpineAtlas> fromFile(String atlasFileName) async {
static Future<Atlas> fromFile(String atlasFileName) async {
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 (await http.get(Uri.parse(file))).bodyBytes;
});
@ -79,13 +79,13 @@ class SpineAtlas {
}
}
class SpineSkeletonData {
Pointer<spine_skeleton_data> _skeletonData;
class SkeletonData {
final Pointer<spine_skeleton_data> _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<Uint8> 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<spine_skeleton_drawable> _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 &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;
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<SpineRenderCommand> render() {
List<RenderCommand> render() {
if (_disposed) return [];
Pointer<spine_render_command> nativeCmd = _bindings.spine_skeleton_drawable_render(_drawable);
List<SpineRenderCommand> commands = [];
List<RenderCommand> 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<spine_render_command> nativeCmd, double pageWidth, double pageHeight) {
RenderCommand(Pointer<spine_render_command> nativeCmd, double pageWidth, double pageHeight) {
atlasPageIndex = nativeCmd.ref.atlasPage;
int numVertices = nativeCmd.ref.numVertices;
int numIndices = nativeCmd.ref.numIndices;

View File

@ -208,6 +208,225 @@ class SpineFlutterBindings {
late final _spine_skeleton_drawable_dispose =
_spine_skeleton_drawable_disposePtr
.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 {
@ -259,11 +478,15 @@ class spine_render_command 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<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';
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<SpineWidget> createState() => _SpineWidgetState();
}
class _SpineWidgetState extends State<SpineWidget> {
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<SpineWidget> {
}
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;

View File

@ -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<spine_atlas>(1, __FILE__, __LINE__);
result->atlas = atlas;
result->numImagePaths = atlas->getPages().size();
result->numImagePaths = (int32_t)atlas->getPages().size();
result->imagePaths = SpineExtension::calloc<char *>(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);
}

View File

@ -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);