Flutter龙骨动画插件spine_flutter的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter龙骨动画插件spine_flutter的使用

spine-flutter 是一个用于在 Flutter 应用中加载、操作和渲染 Spine 骨骼动画数据的运行时库。它基于 spine-cpp,支持桌面和移动平台的 Flutter 部署目标。目前,spine-flutter 不支持 Flutter 的 Web 部署目标。

文档

更多详细信息,请参阅 spine-flutter 官方文档

许可证

您可以免费评估 Spine Runtimes 和我们提供的示例。将 Spine Runtimes 集成到您的软件中也是免费的,但您的用户必须拥有自己的 Spine 许可证。请告知您的用户这一要求!这通常是那些制作开发工具(如 SDK、游戏工具包或软件库)的人的选择。

如果您希望将包含 Spine Runtimes 的软件分发给没有 Spine 许可证的其他人,您需要在集成时购买 Spine 许可证。然后,您可以随意分发包含 Spine Runtimes 的软件,前提是其他人不能修改它或用它创建新软件。如果其他人希望这样做,他们需要自己的 Spine 许可证。

有关 Spine Runtimes 的官方法律条款,请阅读 Spine Runtimes 许可协议Spine 编辑器许可协议第 2 节

Spine 版本

spine-flutter 支持从 Spine 4.2.xx 导出的数据。spine-flutter 支持所有 Spine 功能,但不支持双色着色和屏幕混合模式。

支持的平台

spine-flutter 运行时适用于桌面、移动和 Web 平台。Web 部署需要使用 Canvaskit,这将为您的 Web 部署添加约 2MB 的依赖项。您可以使用以下命令编译 Web 应用:

flutter build web --web-renderer canvaskit

设置

要在您的 Flutter 项目中添加 spine_flutter,请在 pubspec.yaml 文件中添加以下依赖项:

dependencies:
  ...
  spine_flutter: ^4.2.11

在您的 main() 函数中,添加以下两行代码以初始化 Spine Flutter 运行时:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initSpineFlutter(enableMemoryDebugging: false);
  runApp(MyApp());
}

示例

如果您直接从 pub.dev 拉取了 spine_flutter 包,可以直接运行 example/ 文件夹中的示例:

cd path/to/downloaded/spine_flutter
cd example
flutter run

否则,您可以按以下步骤运行示例:

  1. 安装 Flutter SDK,然后运行 flutter doctor,它会指示您安装其他依赖项。
  2. 克隆此仓库:git clone https://github.com/esotericsoftware/spine-runtimes
  3. spine-flutter/ 文件夹中运行 setup.sh。在 Windows 上,您可以使用 Git for Windows 中包含的 Git Bash 运行 setup.sh 脚本。

然后,您可以在支持 Flutter 的 IDE 或编辑器(如 IntelliJ IDEA/Android StudioVisual Studio Code)中打开 spine-flutter 以检查和运行示例。

或者,您也可以从 命令行 运行示例。

开发

如果您只修改插件的 Dart 源代码,则开发设置与上述“示例”部分描述的设置相同。

如果您需要为 spine-cpp 工作于 dart:ffi 绑定,还需要安装 Emscripten

要根据 src/spine_flutter.h 头文件生成绑定,请运行 dart run ffigen --config ffigen.yaml。生成绑定后,必须将 src/spine_flutter_bindings_generated.dart 文件中的 import 'dart:ffi' as ffi; 替换为 import 'ffi_proxy.dart' as ffi;。否则,绑定将无法在 Web 上编译。

如果您对 spine-cppsrc/ 文件夹中的源文件进行了更改,必须运行 compile-wasm.sh。这将编译 spine-cpp 和绑定,并将更新后的 libspine_flutter.jslibspine_flutter.wasm 文件放置在 lib/assets/ 文件夹中。对于 Web 构建,lib/init_web.dart 文件中的 initSpineFlutterFFI() 函数将从包的资源包中加载这些文件。

示例代码

以下是一个简单的示例代码,展示了如何在 Flutter 应用中使用 spine_flutter 插件:

import 'package:spine_flutter/spine_flutter.dart';
import 'package:flutter/material.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initSpineFlutter(enableMemoryDebugging: false);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Spine Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ExampleSelector(),
    );
  }
}

class ExampleSelector extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    const spacer = SizedBox(height: 10);
    return Scaffold(
      appBar: AppBar(title: const Text('Spine Examples')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ElevatedButton(
              child: const Text('Simple Animation'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => const SimpleAnimation(),
                  ),
                );
              },
            ),
            spacer,
            ElevatedButton(
              child: const Text('Pause/Play animation'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => const PlayPauseAnimation(),
                  ),
                );
              },
            ),
            spacer,
            ElevatedButton(
              child: const Text('Animation State Listener'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => const AnimationStateEvents(),
                  ),
                );
              },
            ),
            spacer,
            ElevatedButton(
              child: const Text('Debug Rendering'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => const DebugRendering(),
                  ),
                );
              },
            ),
            spacer,
            ElevatedButton(
              child: const Text('Dress Up'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => const DressUp(),
                  ),
                );
              },
            ),
            spacer,
            ElevatedButton(
              child: const Text('IK Following'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => const IkFollowing(),
                  ),
                );
              },
            ),
            spacer,
            ElevatedButton(
              child: const Text('Physics'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => const PhysicsTest(),
                  ),
                );
              },
            ),
            spacer,
            ElevatedButton(
              child: const Text('Flame: Simple Example'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => SpineFlameGameWidget(SimpleFlameExample()),
                  ),
                );
              },
            ),
            spacer,
            ElevatedButton(
              child: const Text('Flame: Pre-load and share Spine data'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => SpineFlameGameWidget(PreloadAndShareSpineDataExample()),
                  ),
                );
              },
            ),
            spacer,
            ElevatedButton(
              child: const Text('Flame: Dragon Example'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute<void>(
                    builder: (context) => SpineFlameGameWidget(DragonExample()),
                  ),
                );
              },
            ),
            spacer,
          ],
        ),
      ),
    );
  }
}

class SimpleAnimation extends StatefulWidget {
  const SimpleAnimation({Key? key}) : super(key: key);

  @override
  _SimpleAnimationState createState() => _SimpleAnimationState();
}

class _SimpleAnimationState extends State<SimpleAnimation> {
  late SpineWidget _spineWidget;

  @override
  void initState() {
    super.initState();
    _spineWidget = SpineWidget(
      skeletonJson: 'assets/skeleton.json',
      atlas: 'assets/skeleton.atlas',
      animation: 'animation_name',
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Simple Animation')),
      body: Center(child: _spineWidget),
    );
  }
}

在这个示例中,我们创建了一个简单的 Flutter 应用,其中包含一个按钮列表,每个按钮都会导航到一个不同的示例页面。SimpleAnimation 页面展示了一个基本的 Spine 动画。

希望这些信息对您有所帮助!如果您有任何问题或需要进一步的帮助,请随时提问。


更多关于Flutter龙骨动画插件spine_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter龙骨动画插件spine_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用spine_flutter插件来集成和播放Spine动画的示例代码。spine_flutter插件允许你在Flutter应用中加载和渲染Spine动画。

首先,确保你已经在pubspec.yaml文件中添加了spine_flutter依赖:

dependencies:
  flutter:
    sdk: flutter
  spine_flutter: ^x.y.z  # 请替换为最新版本号

然后,运行flutter pub get来安装依赖。

接下来,是一个简单的示例代码,展示了如何在Flutter中加载和播放Spine动画:

import 'package:flutter/material.dart';
import 'package:spine_flutter/spine_flutter.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Spine Flutter Example'),
        ),
        body: Center(
          child: SpineAnimationWidget(
            atlasPath: 'assets/spine_atlas.atlas',  // Atlas文件的路径
            jsonPath: 'assets/spine_animation.json',  // 动画JSON文件的路径
            scale: 1.0,  // 缩放比例
            loop: true,  // 是否循环播放
          ),
        ),
      ),
    );
  }
}

class SpineAnimationWidget extends StatefulWidget {
  final String atlasPath;
  final String jsonPath;
  final double scale;
  final bool loop;

  SpineAnimationWidget({
    required this.atlasPath,
    required this.jsonPath,
    required this.scale,
    required this.loop,
  });

  @override
  _SpineAnimationWidgetState createState() => _SpineAnimationWidgetState();
}

class _SpineAnimationWidgetState extends State<SpineAnimationWidget> {
  late final SpineAnimationController spineAnimationController;

  @override
  void initState() {
    super.initState();
    spineAnimationController = SpineAnimationController(
      atlasPath: widget.atlasPath,
      jsonPath: widget.jsonPath,
      scale: widget.scale,
    );

    // 加载动画并播放
    spineAnimationController.loadAnimation().then((_) {
      if (widget.loop) {
        spineAnimationController.playAnimationLoop(0); // 播放第一个动画并循环
      } else {
        spineAnimationController.playAnimationOnce(0); // 播放第一个动画一次
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: spineAnimationController,
      child: Container(),
      builder: (context, child) {
        return CustomPaint(
          painter: SpinePainter(
            spineAnimationController: spineAnimationController,
          ),
        );
      },
    );
  }

  @override
  void dispose() {
    spineAnimationController.dispose();
    super.dispose();
  }
}

class SpinePainter extends CustomPainter {
  final SpineAnimationController spineAnimationController;

  SpinePainter({required this.spineAnimationController});

  @override
  void paint(Canvas canvas, Size size) {
    spineAnimationController.draw(canvas, size);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return oldDelegate != this;
  }
}

在这个示例中,我们创建了一个SpineAnimationWidget,它负责加载和渲染Spine动画。SpineAnimationController用于控制动画的加载和播放。我们使用了AnimatedBuilder来监听动画的更新,并使用CustomPaint来绘制动画帧。

注意,你需要将你的Spine动画资源(.atlas.json文件)放在assets文件夹中,并在pubspec.yaml文件中声明它们:

flutter:
  assets:
    - assets/spine_atlas.atlas
    - assets/spine_animation.json

这样,你就可以在Flutter应用中成功集成和播放Spine动画了。请确保你使用的是最新版本的spine_flutter插件,并根据需要调整代码。

回到顶部