Selaa lähdekoodia

[flutter] Add more callbacks to controller, add IK example.

Mario Zechner 2 vuotta sitten
vanhempi
commit
7ea45ed77b

+ 1 - 1
spine-flutter/example/lib/animation_state_events.dart

@@ -7,7 +7,7 @@ class AnimationStateEvents extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     reportLeaks();
     reportLeaks();
-    final controller = SpineWidgetController((controller) {
+    final controller = SpineWidgetController(onInitialized: (controller) {
       for (final bone in controller.skeleton.getBones()) {
       for (final bone in controller.skeleton.getBones()) {
         print(bone);
         print(bone);
       }
       }

+ 1 - 1
spine-flutter/example/lib/dress_up.dart

@@ -54,7 +54,7 @@ class DressUpState extends State<DressUp> {
         _selectedSkins[skin.getName()] = false;
         _selectedSkins[skin.getName()] = false;
       }
       }
       _drawable = drawable;
       _drawable = drawable;
-      _controller = SpineWidgetController((controller) {
+      _controller = SpineWidgetController(onInitialized: (controller) {
         controller.animationState.setAnimationByName(0, "dance", true);
         controller.animationState.setAnimationByName(0, "dance", true);
       });
       });
       setState(() {
       setState(() {

+ 50 - 0
spine-flutter/example/lib/ik_following.dart

@@ -0,0 +1,50 @@
+import 'package:flutter/material.dart';
+import 'package:spine_flutter/spine_flutter.dart';
+
+class IkFollowing extends StatefulWidget {
+  const IkFollowing({Key? key}) : super(key: key);
+
+  @override
+  IkFollowingState createState() => IkFollowingState();
+}
+
+class IkFollowingState extends State<IkFollowing> {
+  late SpineWidgetController controller;
+  Offset? crossHairPosition;
+
+  @override
+  void initState() {
+    super.initState();
+
+    controller = SpineWidgetController(onInitialized: (controller) {
+      // Set the walk animation on track 0, let it loop
+      controller.animationState.setAnimationByName(0, "walk", true);
+      controller.animationState.setAnimationByName(1, "aim", true);
+    }, onAfterUpdateWorldTransforms: (controller) {
+      var worldPosition = crossHairPosition;
+      if (worldPosition == null) return;
+      var bone = controller.skeleton.findBone("crosshair");
+      if (bone == null) return;
+      var position = bone.getParent()?.worldToLocal(worldPosition.dx, worldPosition.dy) ?? Vector2(0, 0);
+      bone.setX(position.x);
+      bone.setY(position.y);
+    });
+  }
+
+  void _updateBonePosition(Offset position) {
+    crossHairPosition = controller.toSkeletonCoordinates(position);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    reportLeaks();
+
+    return Scaffold(
+        appBar: AppBar(title: const Text('IK Following')),
+        body: GestureDetector(
+          onPanDown: (drag) => _updateBonePosition(drag.localPosition),
+          onPanUpdate: (drag) => _updateBonePosition(drag.localPosition),
+          child: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", controller),
+        ));
+  }
+}

+ 13 - 0
spine-flutter/example/lib/main.dart

@@ -5,6 +5,7 @@ import 'animation_state_events.dart';
 import 'pause_play_animation.dart';
 import 'pause_play_animation.dart';
 import 'skins.dart';
 import 'skins.dart';
 import 'dress_up.dart';
 import 'dress_up.dart';
+import 'ik_following.dart';
 
 
 class ExampleSelector extends StatelessWidget {
 class ExampleSelector extends StatelessWidget {
   const ExampleSelector({super.key});
   const ExampleSelector({super.key});
@@ -77,6 +78,18 @@ class ExampleSelector extends StatelessWidget {
                   );
                   );
                 },
                 },
               ),
               ),
+              spacer,
+              ElevatedButton(
+                child: const Text('IK Following'),
+                onPressed: () {
+                  Navigator.push(
+                    context,
+                    MaterialPageRoute<void>(
+                      builder: (context) => const IkFollowing(),
+                    ),
+                  );
+                },
+              ),
               spacer
               spacer
             ]
             ]
           )
           )

+ 1 - 1
spine-flutter/example/lib/pause_play_animation.dart

@@ -15,7 +15,7 @@ class PlayPauseAnimationState extends State<PlayPauseAnimation> {
   @override
   @override
   void initState() {
   void initState() {
     super.initState();
     super.initState();
-    controller = SpineWidgetController((controller) {
+    controller = SpineWidgetController(onInitialized: (controller) {
       controller.animationState.setAnimationByName(0, "walk", true);
       controller.animationState.setAnimationByName(0, "walk", true);
     });
     });
     isPlaying = true;
     isPlaying = true;

+ 1 - 1
spine-flutter/example/lib/simple_animation.dart

@@ -7,7 +7,7 @@ class SimpleAnimation extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     reportLeaks();
     reportLeaks();
-    final controller = SpineWidgetController((controller) {
+    final controller = SpineWidgetController(onInitialized: (controller) {
       // Set the walk animation on track 0, let it loop
       // Set the walk animation on track 0, let it loop
       controller.animationState.setAnimationByName(0, "walk", true);
       controller.animationState.setAnimationByName(0, "walk", true);
     });
     });

+ 1 - 1
spine-flutter/example/lib/skins.dart

@@ -21,7 +21,7 @@ class SkinsState extends State<Skins> {
       for (var skin in drawable.skeletonData.getSkins()) {
       for (var skin in drawable.skeletonData.getSkins()) {
         _selectedSkins[skin.getName()] = false;
         _selectedSkins[skin.getName()] = false;
       }
       }
-      _controller = SpineWidgetController((controller) {
+      _controller = SpineWidgetController(onInitialized: (controller) {
         controller.animationState.setAnimationByName(0, "walk", true);
         controller.animationState.setAnimationByName(0, "walk", true);
       });
       });
       drawable.skeleton.setSkinByName("full-skins/girl");
       drawable.skeleton.setSkinByName("full-skins/girl");

+ 37 - 19
spine-flutter/lib/spine_widget.dart

@@ -8,15 +8,18 @@ import 'spine_flutter.dart';
 
 
 class SpineWidgetController {
 class SpineWidgetController {
   SkeletonDrawable? _drawable;
   SkeletonDrawable? _drawable;
+  double _offsetX = 0, _offsetY = 0, _scaleX = 1, _scaleY = 1;
   final void Function(SpineWidgetController controller)? onInitialized;
   final void Function(SpineWidgetController controller)? onInitialized;
-  bool initialized = false;
+  final void Function(SpineWidgetController controller)? onBeforeUpdateWorldTransforms;
+  final void Function(SpineWidgetController controller)? onAfterUpdateWorldTransforms;
+  final void Function(SpineWidgetController controller, Canvas canvas)? onBeforePaint;
+  final void Function(SpineWidgetController controller, Canvas canvas)? onAfterPaint;
 
 
-  SpineWidgetController([this.onInitialized]);
+  SpineWidgetController({this.onInitialized, this.onBeforeUpdateWorldTransforms, this.onAfterUpdateWorldTransforms, this.onBeforePaint, this.onAfterPaint});
 
 
   void _initialize(SkeletonDrawable drawable) {
   void _initialize(SkeletonDrawable drawable) {
     if (_drawable != null) throw Exception("SpineWidgetController already initialized. A controller can only be used with one widget.");
     if (_drawable != null) throw Exception("SpineWidgetController already initialized. A controller can only be used with one widget.");
     _drawable = drawable;
     _drawable = drawable;
-    initialized = true;
     onInitialized?.call(this);
     onInitialized?.call(this);
   }
   }
 
 
@@ -49,6 +52,19 @@ class SpineWidgetController {
     if (_drawable == null) throw Exception("Controller is not initialized yet.");
     if (_drawable == null) throw Exception("Controller is not initialized yet.");
     return _drawable!;
     return _drawable!;
   }
   }
+
+  void _setCoordinateTransform(double offsetX, double offsetY, double scaleX, double scaleY) {
+    _offsetX = offsetX;
+    _offsetY = offsetY;
+    _scaleX = scaleX;
+    _scaleY = scaleY;
+  }
+
+  Offset toSkeletonCoordinates(Offset position) {
+    var x = position.dx;
+    var y = position.dy;
+    return Offset(x / _scaleX - _offsetX, y / _scaleY - _offsetY);
+  }
 }
 }
 
 
 enum AssetType { Asset, File, Http, Drawable }
 enum AssetType { Asset, File, Http, Drawable }
@@ -166,7 +182,6 @@ class SpineWidget extends StatefulWidget {
 }
 }
 
 
 class _SpineWidgetState extends State<SpineWidget> {
 class _SpineWidgetState extends State<SpineWidget> {
-  SkeletonDrawable? skeletonDrawable;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -179,16 +194,12 @@ class _SpineWidgetState extends State<SpineWidget> {
   }
   }
 
 
   void loadDrawable(SkeletonDrawable drawable) {
   void loadDrawable(SkeletonDrawable drawable) {
-    skeletonDrawable = drawable;
-    widget._controller._initialize(skeletonDrawable!);
-    skeletonDrawable?.update(0);
+    widget._controller._initialize(drawable);
+    drawable.update(0);
     setState(() {});
     setState(() {});
   }
   }
 
 
   void loadFromAsset(String skeletonFile, String atlasFile, AssetType assetType) async {
   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:
         loadDrawable(await SkeletonDrawable.fromAsset(skeletonFile, atlasFile));
         loadDrawable(await SkeletonDrawable.fromAsset(skeletonFile, atlasFile));
@@ -206,9 +217,9 @@ class _SpineWidgetState extends State<SpineWidget> {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    if (skeletonDrawable != null) {
+    if (widget._controller._drawable != null) {
       print("Skeleton loaded, rebuilding painter");
       print("Skeleton loaded, rebuilding painter");
-      return _SpineRenderObjectWidget(skeletonDrawable!, widget._fit, widget._alignment, widget._boundsProvider, widget._sizedByBounds);
+      return _SpineRenderObjectWidget(widget._controller._drawable!, widget._controller, widget._fit, widget._alignment, widget._boundsProvider, widget._sizedByBounds);
     } else {
     } else {
       print("Skeleton not loaded yet");
       print("Skeleton not loaded yet");
       return const SizedBox();
       return const SizedBox();
@@ -217,23 +228,24 @@ class _SpineWidgetState extends State<SpineWidget> {
 
 
   @override
   @override
   void dispose() {
   void dispose() {
-    skeletonDrawable?.dispose();
     super.dispose();
     super.dispose();
+    widget._controller._drawable?.dispose();
   }
   }
 }
 }
 
 
 class _SpineRenderObjectWidget extends LeafRenderObjectWidget {
 class _SpineRenderObjectWidget extends LeafRenderObjectWidget {
   final SkeletonDrawable _skeletonDrawable;
   final SkeletonDrawable _skeletonDrawable;
+  final SpineWidgetController _controller;
   final BoxFit _fit;
   final BoxFit _fit;
   final Alignment _alignment;
   final Alignment _alignment;
   final BoundsProvider _boundsProvider;
   final BoundsProvider _boundsProvider;
   final bool _sizedByBounds;
   final bool _sizedByBounds;
 
 
-  _SpineRenderObjectWidget(this._skeletonDrawable, this._fit, this._alignment, this._boundsProvider, this._sizedByBounds);
+  const _SpineRenderObjectWidget(this._skeletonDrawable, this._controller, this._fit, this._alignment, this._boundsProvider, this._sizedByBounds);
 
 
   @override
   @override
   RenderObject createRenderObject(BuildContext context) {
   RenderObject createRenderObject(BuildContext context) {
-    return _SpineRenderObject(_skeletonDrawable, _fit, _alignment, _boundsProvider, _sizedByBounds);
+    return _SpineRenderObject(_skeletonDrawable, _controller, _fit, _alignment, _boundsProvider, _sizedByBounds);
   }
   }
 
 
   @override
   @override
@@ -248,6 +260,7 @@ class _SpineRenderObjectWidget extends LeafRenderObjectWidget {
 
 
 class _SpineRenderObject extends RenderBox {
 class _SpineRenderObject extends RenderBox {
   SkeletonDrawable _skeletonDrawable;
   SkeletonDrawable _skeletonDrawable;
+  SpineWidgetController _controller;
   double _deltaTime = 0;
   double _deltaTime = 0;
   final Stopwatch _stopwatch = Stopwatch();
   final Stopwatch _stopwatch = Stopwatch();
   BoxFit _fit;
   BoxFit _fit;
@@ -255,7 +268,7 @@ class _SpineRenderObject extends RenderBox {
   BoundsProvider _boundsProvider;
   BoundsProvider _boundsProvider;
   bool _sizedByBounds;
   bool _sizedByBounds;
   Bounds _bounds;
   Bounds _bounds;
-  _SpineRenderObject(this._skeletonDrawable, this._fit, this._alignment, this._boundsProvider, this._sizedByBounds): _bounds = _boundsProvider.computeBounds(_skeletonDrawable);
+  _SpineRenderObject(this._skeletonDrawable, this._controller, this._fit, this._alignment, this._boundsProvider, this._sizedByBounds): _bounds = _boundsProvider.computeBounds(_skeletonDrawable);
 
 
   set skeletonDrawable(SkeletonDrawable skeletonDrawable) {
   set skeletonDrawable(SkeletonDrawable skeletonDrawable) {
     if (_skeletonDrawable == skeletonDrawable) return;
     if (_skeletonDrawable == skeletonDrawable) return;
@@ -368,7 +381,9 @@ class _SpineRenderObject extends RenderBox {
     _deltaTime = _stopwatch.elapsedTicks / _stopwatch.frequency;
     _deltaTime = _stopwatch.elapsedTicks / _stopwatch.frequency;
     _stopwatch.reset();
     _stopwatch.reset();
     _stopwatch.start();
     _stopwatch.start();
+    _controller.onBeforeUpdateWorldTransforms?.call(_controller);
     _skeletonDrawable.update(_deltaTime);
     _skeletonDrawable.update(_deltaTime);
+    _controller.onAfterUpdateWorldTransforms?.call(_controller);
     markNeedsPaint();
     markNeedsPaint();
   }
   }
 
 
@@ -403,12 +418,13 @@ class _SpineRenderObject extends RenderBox {
         break;
         break;
     }
     }
 
 
+    var offsetX = offset.dx + size.width / 2.0 + (_alignment.x * size.width / 2.0);
+    var offsetY = offset.dy + size.height / 2.0 + (_alignment.y * size.height / 2.0);
     canvas
     canvas
-      ..translate(
-          offset.dx + size.width / 2.0 + (_alignment.x * size.width / 2.0),
-          offset.dy + size.height / 2.0 + (_alignment.y * size.height / 2.0))
+      ..translate(offsetX, offsetY)
       ..scale(scaleX, scaleY)
       ..scale(scaleX, scaleY)
       ..translate(x, y);
       ..translate(x, y);
+    _controller._setCoordinateTransform(x + offsetX / scaleY, y + offsetY / scaleY, scaleX, scaleY);
   }
   }
 
 
   @override
   @override
@@ -420,7 +436,9 @@ class _SpineRenderObject extends RenderBox {
     canvas.save();
     canvas.save();
     _setCanvasTransform(canvas, offset);
     _setCanvasTransform(canvas, offset);
 
 
+    _controller.onBeforePaint?.call(_controller, canvas);
     _skeletonDrawable.renderToCanvas(canvas);
     _skeletonDrawable.renderToCanvas(canvas);
+    _controller.onAfterPaint?.call(_controller, canvas);
 
 
     canvas.restore();
     canvas.restore();
     SchedulerBinding.instance.scheduleFrameCallback(_beginFrame);
     SchedulerBinding.instance.scheduleFrameCallback(_beginFrame);