mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-06 15:24:55 +08:00
[flutte] Add controller, wrap AnimationState.
This commit is contained in:
parent
2e78d64a23
commit
1dd2c99c70
@ -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"),
|
||||
);
|
||||
|
||||
@ -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 <= 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<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;
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user