🔰

【Flutter × 初心者】Live2D サンプルキャラを Unity で表示!flutter_unity_widget 動作編

2024/09/23に公開

はじめに

前回のプロジェクトをそのまま使ってます。
https://zenn.dev/tiel/articles/0613c6d0d4497c

今回の実装内容は下記です!

  • Flutter側でボタンを押下時にUnityのキャラがモーションを起こす。

要するに
Flutter -> Unityの通信になります。
主にUnity側の設定が多かった気がします。

今回の構想です。
1.Flutter レイアウト 実装
2.Unity モーション、Script 実装
3.Flutter Unityへの通信 実装

[2024年09月24日 追記]
自作のLive2dを動かしたい方はUnityの箇所を下記の方を参考にした方が良いと思われます。
https://zenn.dev/miri/articles/473068d32830ee

主なプログラム経験

  • 会社員時代
    • OracleDB 5年
    • Java 5年
  • フリーランス時代
    • supabse 2年
    • nextjs 2年
    • hubspot 2年

対象者

  • Android開発のみ
  • 同じぐらいのflutter初心者

書き方や対処法も独自で対応した部分もあるので別に良い方法があれば教えていただきたいです。
それではやっていきましょう。

開発環境

前回のページと同じです!
https://zenn.dev/tiel/articles/0613c6d0d4497c

  • OS
    • Mac M3
  • IDE
    • Visual Studio Code
  • Flutter
    • version 3.22
       後から記述しますが3.19と3.24はunity連携のとこでバグを確認したので3.22に変更しました。
    • Flutterで使用するパッケージ
      • flutter_unity_widget: ^2022.2.1
  • Unity
    • 2022.3.46f1
    • unityで使用するパッケージファイル
      • CubismSdkForUnity-5-r.2.unitypackage
      • fuw-2022.2.0.unitypackage

Flutter側の事前実装

まず最初に見た目を整えときます!
/unity_live2d_sample/lib/main.dart :

  child: Container(
    color: Colors.yellow,
-   child: UnityWidget(
-     onUnityCreated: onUnityCreated,
-   ),
+   child: Column(
+       children: [
+           Expanded(
+               child: UnityWidget(
+                   onUnityCreated: onUnityCreated,
+               ),
+           ),
+           ElevatedButton(
+               onPressed: () {
+                   print('Button pressed');
+               },
+               child: const Text('応援する'),
+           ),
+      ],
+   )
  ),


「応援する」となってるのはサンプルのモーションが応援してる感じだったからです。

一旦はこれで大丈夫です。
onPressedでflutterからunityへ通信を送る必要がありますが後で記載します。

Unity側の実装

先にUnity実装を行います!
やることは順に3つ

  1. キャラ(GameObject:Koharu)にモーションをアタッチする。(紐付けする)
  1. モーションが初期起動で動くことを確認する。
  2. flutterのボタンが押されたら動くようにするための準備

キャラにモーションをアタッチする

まずは下記フォルダを確認

Assets/Live2D/Cubism/Samples/Models/Koharu/Animation/

フォルダには9つのファイルがありますが3つのモーションです。
・body
・face01
・face02

今回は初めのBodyを使っていきます。

赤枠で囲ったものはAnimation CripというUnityの標準アニメーションらしいです。
また赤枠の横にある。.motion3.jsonはLive2Dでのモーションファイルです。

当初は両方の動かし方を試そうとしましたが
公式サイトを見るにmotion3.jsonをAnimationCripに変換するそうなので今回は無しにしました。
https://docs.live2d.com/cubism-sdk-tutorials/animation/

Animation Controller 作成する

GameObject(Koharu)にアニメーションをつけるには、直接設定するのではなく
Animation を制御するものを使います。

GameObject <= Animator <= Animation Controller <= Animation Crip
となります。

今回はAssetsの中にフォルダを作成します。

/Assets
+ - Controller
+   - Animation
  - FlutterUnityIntegration
  ... 略

Animation フォルダに新規作成で「Animation Controller」を作成
「KoharuBodyController」とします。

Animation Controller を Animator にアタッチ

Animator <= Animation Controller

すでにあるAnimatorのController検索欄に先ほど作成した「KoharuBodyController」
を入力します。

設定されたらKoharuBodyControllerをダブルクリックをすると
Animatorのエディタが開きます。

Animator エディタに アニメーションをアタッチする。

Assets/Live2D/Cubism/Samples/Models/Koharu/Animation/Body
Bodyをドラッグ&ドロップします。

Entry -> Bodyと設定されます。
この状態で再生ボタンを押下するとモーションが動きます。

ちなみにbodyアニメーションのインスペクターにLoopするかのチェックがあるのではずしときます。

これで 初期起動で1回だけ動くようになります。

Flutterで今の状態を確認する。

ナビゲーションのFlutterから Export Android (Debug) を選択してBuildします。

その後、runコマンドでAndroidの状態を確認します。

flutter run

はい!これで初期表示にモーションが動くことを確認できました。
ループをチェックするともちろんループして応援し続けます。

flutterのボタンが押されたら動くようにするための準備

ここでFlutter側で呼び出せるようにUnity側で準備を行います。

Flutterを検知できるように受信をするScriptを作成します。

Assetsの中にScriptsフォルダを作成します。

/Assets
 - Controller
 - Animation
 - FlutterUnityIntegration
  ... 略
+ - Scripts

Scriptsの中でC#Scriptを作成します。

ファイル名とクラス名を「NewBehaviourScript」→ 「KoharuMotionScript」に変更します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
+ public class NewBehaviourScript : MonoBehaviour
+ public class KoharuMotionScript : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Unityで再生ボタンを教えてビルドでエラーにならないことを確認します。

Flutterで受信するScriptを書く

KoharuMotionController:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class KoharuMotionScript : MonoBehaviour
{
    public Animator animator; // UnityのAnimatorを使用
    void Start()
    {  
        // アタッチされてるGameObjectのAnimatorコンポーネントを取得
        animator = GetComponent<Animator>();
    }
    // UnityがFlutterから呼び出される時にこのメソッドが動く
    public void PlayAnimation(string animationName) {
        // animationNameで受け取ったアニメーションを再生
        Debug.Log("Playing animation: " + animationName);
        animator.Play(animationName,0, 0.0f);
    }

    void Update()
    {
        
    }
}

大体コメント通りです。

animator.Play(animationName,0, 0.0f);

で 0を指定しているのは今回のモーションは初期表示時に再生されます。
(animatorの Entry -> Body のこと)
もう一度動かす時には再生済みになっているのでそれをまた初めから再生するためです。

Flutterで受信するScriptをGameObject(Koharu)にアタッチする

Add Compornentから作成したKoharuMotionScriptを選択します。

KoharuMotionScriptを検索

KoharuMotionScriptが追加された

再生をして異常がエラーにならないを確認したら
ナビゲーションのFlutterから Export Android (Debug) を選択してBuildします。

これでUnity側は完了です。

Flutter側の実装

ここでFlutterからUnityに送信するソースを書きます。

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

void main() {
  runApp(
    const MaterialApp(
      home: UnityDemoScreen(),
    ),
  );
}

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

  @override
  State<UnityDemoScreen> createState() => _UnityDemoScreenState();
}

class _UnityDemoScreenState extends State<UnityDemoScreen> {
  static final GlobalKey<ScaffoldState> _scaffoldKey =
      GlobalKey<ScaffoldState>();
  UnityWidgetController? _unityWidgetController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      body: SafeArea(
        bottom: false,
        child: WillPopScope(
          onWillPop: () async {
            // Pop the category page if Android back button is pressed.
            return true;
          },
          child: Container(
              color: Colors.yellow,
              child: Column(
                children: [
                  Expanded(
                    child: UnityWidget(
                      onUnityCreated: onUnityCreated,
                    ),
                  ),
                  ElevatedButton(
                    onPressed: () {
-                     print('Button pressed');
+                     _sendPlayMotionMessage();
                    },
                    child: const Text('応援する'),
                  ),
                ],
              )),
        ),
      ),
    );
  }

+ void _sendPlayMotionMessage() {
+   _unityWidgetController?.postMessage(
+       'Koharu', // Unity側のゲームオブジェクト名
+       'PlayAnimation', // Unity側のメソッド名
+       'body'); // 引数
+ }

  // Callback that connects the created controller to the unity controller
  void onUnityCreated(controller) {
    _unityWidgetController = controller;
  }
}

重要なのが
_sendPlayMotionMessageのpostMessageです。

第一引数:GameObject名

第二引数:アタッチされているScriptのメソッド名

KoharuMotionScript:

// UnityがFlutterから呼び出される時にこのメソッドが動く
public void PlayAnimation(string animationName) {
    // animationNameで受け取ったアニメーションを再生
    Debug.Log("Playing animation: " + animationName);
    animator.Play(animationName,0, 0.0f);
}

第三引数:メソッドの引数
KoharuMotionScript:

PlayAnimation(string animationName)

今回は第三引数にanimationNameを渡しています。
これはAnimatorのbodyのことです。

ここの名前がbody_loopの場合は第三引数がbody_loopになります。

動かしてみる

いざ!

いい感じですね!連打しても落ちない!
とりあえずこれでモーションの確認はできました。

Animationの動かし方だでトリガーで動かす方もあったのですが
メインはFlutter -> Unityのやり取りだったので省きました。
余裕があったら書き起こします。

やってみた結果

前回がきつかったのですが今回は1日である程度できました。
引っかかるとこは正直postMessageの引数たちぐらいで
Unityのどれを示しているのかというのが分かりづらく動かして確認することに近かったです。

勝手に自滅したのはmotion3.json をそのまま使って表示させようとしたことです。
必要になるケースもあるかと思いますが簡単なうちは必要なさそうなので割愛

このまま進めてLive2dのキャラ自作まで行けたら理想だけどいつになるやら。

拙い文章でしたがここまで読んでいただきありがとうございました。
余裕がある時にunity => Flutter の通信を調べたいですね。
ではまた

Discussion