UnityでFlutterっぽい開発ができるので試してみた

# はじめに
<!-- 発端や概要を記載 -->
UnityでFlutterっぽいコードを書いて、アプリを作れるパッケージがあるという記事を見て、気になったので試してみました。

C#は5年、Dartは1~2年くらい経験があるので、本記事で紹介するパッケージを使ってみて、どこまでFlutterっぽいのか？ どういう違いがあるか？などを最後にまとめています。

**※本記事で紹介しているパッケージのバージョンですが、GitHubでアーカイブ化された古いバージョンのリポジトリのため、使うのは非推奨です。最新のリポジトリは以下になります。(私の環境だと使用できなかったため古いバージョンを使用しています。ご注意ください)**
https://github.com/UIWidgets/com.unity.uiwidgets

# 作成環境
* Unity エディタバージョン 2021.3.8.f1

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [UIWidgetsの導入](#UIWidgetsの導入)
1. [ToDoアプリっぽくする](#ToDoアプリっぽくする)
1. [さわってみた所感](#さわってみた所感)

<!-- 各チャプター -->
<a id="#UIWidgetsの導入"></a>
# UIWidgetsの導入
`UIWidgets`とは、UnityでFlutterっぽくアプリ開発ができるパッケージの名前です。
導入は[リポジトリ](https://github.com/UnityTech/UIWidgets)からコードをダウンロードし、パッケージを使用したいプロジェクトの`Packages`フォルダ配下に入れるだけです。
本章では、パッケージを導入 ~ 起動の確認まで説明したいと思います。
**※なお、冒頭で説明した通り、導入説明は古いバージョンのパッケージの説明になりますのでご注意ください**

### プロジェクト作成
まずUnityでプロジェクトを作成します。
テンプレートは`2D`、プロジェクト名を`UIWidgetsSample`としました。

### パッケージのダウンロード
#### Gitを使用する場合
* ターミナルを開いて、`cd`コマンドで作成した「プロジェクトフォルダ/Packages」に移動してください
* 以下のコマンドを実行してください
```
git clone https://github.com/UnityTech/UIWidgets.git com.unity.uiwidgets
```

#### Gitを使用しない場合
* [リポジトリ](https://github.com/UnityTech/UIWidgets)の`Download ZIP`からコードをダウンロードしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670089/adb4cadd-378b-98e7-fe3f-38303a88dd47.png)

* ダウンロードが終わったら解凍後、作成したプロジェクトフォルダの`Packages`フォルダにコピーか移動してください。

### 起動の確認
#### シーンの設定
* Unityで作成したプロジェクトを開く
* `UIWidgets`を使いたいシーンを開き、`UI‐Canvas`を追加
* 追加した`Canvas`に`UI‐Panel`を追加
* 追加した`Panel`の`Image`コンポーネントを削除

#### スクリプトの記述
* スクリプトを作成し、名前を`UIWidgetsExample`に変更します
* [リポジトリ](https://github.com/UnityTech/UIWidgets)のREADMEにあるサンプルコードをコピペします。
```C#:UIWidgetsExample.cs
 using System.Collections.Generic;
 using Unity.UIWidgets.animation;
 using Unity.UIWidgets.engine;
 using Unity.UIWidgets.foundation;
 using Unity.UIWidgets.material;
 using Unity.UIWidgets.painting;
 using Unity.UIWidgets.ui;
 using Unity.UIWidgets.widgets;
 using UnityEngine;
 using FontStyle = Unity.UIWidgets.ui.FontStyle;

 namespace UIWidgetsSample {
     public class UIWidgetsExample : UIWidgetsPanel {
         protected override void OnEnable() {
             // if you want to use your own font or font icons.
             // FontManager.instance.addFont(Resources.Load<Font>(path: "path to your font"), "font family name");

             // load custom font with weight & style. The font weight & style corresponds to fontWeight, fontStyle of
             // a TextStyle object
             // FontManager.instance.addFont(Resources.Load<Font>(path: "path to your font"), "Roboto", FontWeight.w500,
             //    FontStyle.italic);

             // add material icons, familyName must be "Material Icons"
             // FontManager.instance.addFont(Resources.Load<Font>(path: "path to material icons"), "Material Icons");

             base.OnEnable();
         }

         protected override Widget createWidget() {
             return new WidgetsApp(
                 home: new ExampleApp(),
                 pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
                     new PageRouteBuilder(
                         settings: settings,
                         pageBuilder: (BuildContext context, Animation<float> animation,
                             Animation<float> secondaryAnimation) => builder(context)
                     )
             );
         }

         class ExampleApp : StatefulWidget {
             public ExampleApp(Key key = null) : base(key) {
             }

             public override State createState() {
                 return new ExampleState();
             }
         }

         class ExampleState : State<ExampleApp> {
             int counter = 0;

             public override Widget build(BuildContext context) {
                 return new Column(
                     children: new List<Widget> {
                         new Text("Counter: " + this.counter),
                         new GestureDetector(
                             onTap: () => {
                                 this.setState(()
                                     => {
                                     this.counter++;
                                 });
                             },
                             child: new Container(
                                 padding: EdgeInsets.symmetric(20, 20),
                                 color: Colors.blue,
                                 child: new Text("Click Me")
                             )
                         )
                     }
                 );
             }
         }
     }
 }
```

### 起動確認
* `Panel`にスクリプトをアタッチ
* `Play`ボタンを押下する
以下のような画面になると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670089/86d8b914-b3a3-d6f2-c10c-785550f418a7.png)


<a id="#ToDoアプリっぽくする"></a>
# ToDoアプリっぽくする
上記だと全然Flutterっぽさがないので、FlutterっぽくToDoアプリっぽいものを作ってみました。
以下の動作を実装しています。
* タップすると項目がDoneになる
* スワイプで削除する

![BottomNavigationSampledfdf.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670089/c7975bad-c0e6-1d3b-aba5-1cd0a4c3558b.gif)

サンプルコード(ToDoのデータモデル)
```C#
public class ToDoData
{
    public int Id { get; private set; }
    public string Title { get; private set; }

    public ToDoData(int id, string title)
    {
        Id = id;
        Title = title;
    }
}
```

サンプルコード(ToDoリストのWidget)
```C#
class ExampleApp : StatefulWidget
 {


     public ExampleApp(Key key = null) : base(key)
     {
     }

     public override State createState()
     {
         return new ExampleState();
     }
 }

 class ExampleState : State<ExampleApp>
 {
     private List<ToDoData> _toDoDatas = new List<ToDoData>()
     {
         new ToDoData(1, "ToDo1"),
         new ToDoData(2, "ToDo2"),
         new ToDoData(3, "ToDo3"),
         new ToDoData(4, "ToDo4"),
         new ToDoData(5, "ToDo5"),
     };

     public override Widget build(BuildContext context)
     {
         return new Scaffold(
             appBar: new PreferredSize(
                 preferredSize: Size.fromHeight(120f),
                 child: new AppBar()
             ),
             body: ListView.builder(
                 itemBuilder: (BuildContext context, int index) => {
                     var toDoData = _toDoDatas[index];
                     return new Dismissible(
                         key: new ObjectKey(toDoData.Id.ToString()),
                         onDismissed: (direction) => {
                             if (direction == DismissDirection.endToStart)
                             {
                                 _toDoDatas.RemoveAt(index);
                             }
                         },
                         background: new Container(color: Colors.red),
                         child: new Card(
                             child: new ListTile(
                                 title: new Text(
                                     $"{_toDoDatas[index].Id:D4}:{_toDoDatas[index].Title}",
                                     style: new TextStyle(fontSize: 64)
                                 ),
                                 onTap: () => {
                                     setState(() => { _toDoDatas[index] = new ToDoData(toDoData.Id, "Done!"); });
                                 }
                             )
                         )
                     );
                 },
                 itemCount: _toDoDatas.Count
             )
         );
     }
 }
```

<a id="#さわってみた所感"></a>
# さわってみた所感
UIを記述する部分はほぼFlutterと同じで、Flutter経験者ならC#未経験でも書けそうだなと思いました。
そもそも`Dart`と`C#`の文法が結構似ているので、自分で書いていて、インスタンスの生成に`new`をつけるかつけないかと、`List`の書き方が若干違うくらいの差異しか感じなかったです。
Flutter経験者でUnityを触ってみたい人や、Unity経験者でFlutterの雰囲気を知りたい人の導入としては良さそうに思えました。
(ただがっつり勉強していくなら、このパッケージを使うよりはそれぞれの入門書などで勉強した方が良いと思います。)

あと気になった点として、動かしたときにデザインが崩れることがありました。
具体的には、`FloatingActionButton`が小さく表示され、大きさを変えるとテキストのサイズが変わり、UIのバランスが変わってしまいました。
Flutterで書くのとまったく同じ書き方なのがまずかったのか、Unityでのシーンの設定がまずかったのかはわかりません。
ただFlutterの場合はプロジェクトを作成してすぐにコードを書いて開発していけますが、`UIWidgets`でFlutterっぽいアプリを作る場合は、シーンの設定などしなければいけないので、その点はFlutterの知識だけではカバーできないようです。