🦴

FlutterでSpineアニメーションを再生してみる

2023/07/09に公開

Flutter用のSpineランタイムが登場

Spine公式よりFlutterランタイムがアナウンスされていましたので、早速触ってみました。

spine_flutterパッケージの導入方法

FlutterアプリでSpineアニメーションを再生するには、spine_flutter パッケージが必要です。
早速組み込んでいきます。

動作環境

ツール バージョン
fvm 2.4.1
Flutter 3.10.0
spine_flutter 4.1.1

flutterアプリ作成

まずはflutterアプリを作成します。
私はfvmを使っているので、fvm 〜 でコマンドを実行します。

fvm flutter create pub_flutter_spine_animation_example

pubspec.yamlを更新

dependenciesにspine_flutterを追記

spine_flutter 4.2.13 | pub devのコピーアイコンを押下して、yamlに記載する文字列をコピーします。
コピーした文字列をpubspec.yamlのdependenciesに貼り付けます。

name: pub_flutter_spine_animation_example
description: A new Flutter project.

…中略…

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  spine_flutter: ^4.1.1 # ←新規追加

Flutterとspine_flutterのバージョンの整合性は要チェックです!
現時点(2023.07.09時点)の最新のspine_flutter(v4.2.13)を動作させたい場合は、Flutterはv3.10.5以上である必要があります。

assetsを更新

次にpubspec.yamlのassetsを更新します。
ここで指定したassetsディレクトリに、Spineエクスポートファイルを格納先になります。

name: pub_flutter_spine_animation_example
description: A new Flutter project.

…中略…

flutter:
  assets:
    - assets/ # ←新規追加

pub get を実行

pubspec.yamlを更新後に fvm flutter pug getを実行します。
これでspine_flutterがインストールされたはずです。

spineエクスポートファイルをFlutterアプリに格納する

次に、spineアニメーションのエクスポートファイルをFlutterアプリに格納します。
出力形式ですが、.jsonでも.skelでも、どちらでも良いみたいです。

./
├── lib
│   └── main.dart
├── assets
│   ├── spineboy-pro.atlas ← 新規追加
│   ├── spineboy-pro.json ← 新規追加
│   └── spineboy-pro.png ← 新規追加
└── その他 …

main.dartを更新する

最後にmain.dartを更新します。要点は以下の4点です。

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const SpineExamplePage(),
    );
  }
}

class SpineExamplePage extends StatelessWidget {
  const SpineExamplePage({super.key});

  @override
  Widget build(BuildContext context) {
    final controller = SpineWidgetController(onInitialized: (controller) {
      controller.animationState.setAnimationByName(0, "walk", true);
    });

    return Scaffold(
      appBar: AppBar(title: const Text('Spine Example')),
      body: Center(
          child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            decoration: const BoxDecoration(
              color: Colors.pink,
            ),
            height: 400,
            width: 400,
            child: SpineWidget.fromAsset("assets/spineboy-pro.atlas",
                "assets/spineboy-pro.json", controller), // …3.
          ),
          const SizedBox(height: 10),
          ElevatedButton(
              onPressed: () {
                // …4.
                if (controller.animationState
                        .getCurrent(0)
                        ?.getAnimation()
                        .getName() ==
                    "walk") {
                  controller.animationState.setAnimationByName(0, "idle", true);
                } else {
                  controller.animationState.setAnimationByName(0, "walk", true);
                }
              },
              child: const Text('Toggle Animation'))
        ],
      )),
    );
  }
}
  1. spine_flutter パッケージを読み込みます。
  2. main関数にて、WidgetsFlutterBinding.ensureInitializedinitSpineFlutterを実行します。
    • spine_flutter Setupに記載されている通り、2行分をmain関数内に記載します。main関数にasyncをつけるのを忘れないように。
  3. SpineWidgetを利用して、Spineエクスポートファイルを読み込みます
    • example/lib
      /simple_animation.dart
      の通りに実装します。Spineをjsonでエクスポートしている場合は、SpineWidget.fromAssetの第一引数の拡張子を.jsonにします。.skelなら、.skelにします。
  4. ボタン押下時のアニメーション切り替え処理を実装
    • controller.animationState.setAnimationByNameでアニメーションを切り替えています。引数は順にトラック番号、アニメーション名、ループするか です。
    • controller.animationState.getCurrent(0)?.getAnimation().getName() でトラック0のアニメーション名を取得しています。取得したアニメーション名が walk なのか idle なのかを判定して、それ以降の処理につなげています。

実際に動かしてみる

spine_flutter_example

作成したFlutterプロジェクトのGithib リポジトリはこちら

まとめ

サンプルコードが用意されているおかげで、簡単に試せました。
spine-flutter Runtime Documentationを見てみると、Flutter Flameにも対応しているようなので、クロスプラットフォームゲームの作成にも使えそうですね。

FlutterだとRiveという強力なライバルがいますので、そちらとの棲み分けも気になるところ。

では、また。

Discussion