Help us understand the problem. What is going on with this article?

【Unity】Unity上でFlutter!? UIWidgetsアセットの初歩的な使い方

はじめに

image.jpg
UnityでFlutter!?
なんとも最初目にしたときは驚きました。UIWidgetsというアセットを使えばUnityでFlutterと同じようなことができてしまうというのですから。
sampleを動かすとこれはホントにUnityかと疑うほどです。
しかし,わりと新しいアセットであることもあってか日本語の情報が少ない…いえ,全体的に情報が少ないです。
なので,備忘録も兼ねて,使い方を簡単にまとめておきたいと思います。

UIWidgets アセットとは

Googleが開発した言語Dartで使えるフレームワークであるFlutterで実装するように,コードを描くことでUnity上で同様のUIを実装できるアセットです。開発元はUnityテクノロジー(おそらくUnity China)ですので,長期的なアップデートも安心できそうです。
今後Unity本体に統合されるかもしれませんね。
UnityのUIのようにシーン上のCanvasに配置していくというよりはコードを書いていく形です。Dartで書くのをC#で記述できるようになり,なおかつ使い慣れたUnityで制作できるのが強みでしょうか。
また,高効率のレンダリングが可能なようです。

環境

Windows 10 Pro
Unity 2019.1.10f1 → Unity 2018.3.13f1(2018.3.xを推奨)

追記(2019-09-20)

Unity 2019.2.x系統最新版のUnity 2019.2.6f1およびUIWidgets最新版1.5.4-preview.0との組み合わせの場合は後述のAndroidOSにおける表示の不具合は発生しないことを確認して頂きました。

インポート

まずはUnity Asset Storeからマイアセットに追加し,UnityEditor上でダウンロードします。
そして
インポートを行います。
image.png

つづき的なもの

こちらに画像の表示の際の注意点やら,そのほか色々試した続き的な記事があります。お暇があればぜひ。

Sampleを動かす

まずはサンプルを動かしてみることにします。
Windows>UIWidgets>Tests>Galleryからサンプルアプリが実行できます。
sampleの動きや,その他UIWidgets用のインスペクターの使用方法などは「【Unity提供アセット】FlutterライクのワークフローをUnityで実現!強力なUIプラグインパッケージが新登場。60fps以上のレンダリング効率、クロスプラットフォーム対応。1ドローコールで描画する「UIWidgets」」をぜひ参考にしてみてください!(投げやり)

Hello Worldを出す!

それでは恒例(?)のHello World!を描画してみましょう。

スクリプトの作成

今回は軽く確認したいだけだったので以下のようなスクリプトを用意しました。
C#では名前付き引数が利用できるので,ホントにDartのように書けます。まあ,newは省略できませんが,個人的にインスタンスにnewがないのはむず痒いのでむしろ好都合かも。

Test.cs
using Unity.UIWidgets.engine;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;

public class Test : UIWidgetsPanel
{
    protected override Widget createWidget()
    {
        return new MaterialApp(
            home: new Center(
                child: new Text(
                    "Hello World!",
                    textAlign: TextAlign.center,
                    style: new TextStyle(
                        fontSize: 32.0f
                    )
                )
            )
        );
    }
}

これ,言語がC#になっただけでやること変わんないな!?
結局はDartだろうがC#だろうが,Flutterのアプリの作り方はある程度理解しておく必要がありそうですね。逆に言えば,Dartでもうすでにある程度慣れている場合はすんなりと入って行けそうです。

Unity側の設定

標準のCanvasとPanelを用意します。
PanelからImageコンポーネントを削除しましょう(要はCanvasにレンダリングされる空のゲームオブジェクトが必要)。
このPanelに先ほどのスクリプトをアタッチすることで表示されます。
コメント 2019-07-21 191650.png

やったぁ!「Hello World!」が表示された!
これ,当たり前のように画面(シーンやゲーム)に表示されましたが,凄いですよね。だって,まだ実行していないんですもん。
ですが,テキストしか出してないので,Unityのダサい背景のままであまりアプリケーションという気がしませんね。次の項で実際にマテリアルデザインを適用していきましょう。

StatelessWidgetsを使用する

先ほどのTest.csを以下のように書き換えてあげます。

Test.cs
using System.Collections.Generic;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;

public class Test : UIWidgetsPanel
{
    protected override Widget createWidget()
    {
        return new MyStatelessWidget();
    }
}

public class MyStatelessWidget : StatelessWidget
{
    public override Widget build(BuildContext context)
    {
        return new MaterialApp(
            home: new Scaffold(
                appBar: new AppBar(title: new Text("Title")),
                body: new Center(
                    child: new Text(
                        "Hello World!",
                        textAlign: TextAlign.center,
                        style: new TextStyle(
                            fontSize: 32.0f
                        )
                    )
                )
            )
        );
    }
}

UIWidgetsPanelを継承したTestクラスはDartで書いたとこで言う以下に相当するものですね,たぶん笑

Test.dart
void main() => runApp(MyStatelessWidget());

話が少しそれましたが,Test.csを先ほどのものにして保存すると以下のようになります。こちらももちろんUnity側の実行ボタンは押していません。
image.png

StatefulWidgetを使用する

それではStatefulWidgetも扱ってみましょう。Test.csを以下のように書き換えます。

Test.cs
using System.Collections.Generic;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;

public class Test : UIWidgetsPanel
{
    protected override Widget createWidget()
    {
        return new MaterialApp(
            home: new MyStatefulWidget()
        );
    }

    protected override void OnEnable()
    {
        FontManager.instance.addFont(Resources.Load<Font>(path: "MaterialIcons-Regular"), "Material Icons");
        Screen.fullScreen = false; // ナビゲーションバー表示用
        base.OnEnable();
    }
}

public class MyStatefulWidget : StatefulWidget
{
    public override State createState()
    {
        return new MyWidgetState();
    }
}

class MyWidgetState : State<MyStatefulWidget>
{
    public override Widget build(BuildContext context)
    {
        return new Theme(
            data: new ThemeData(),
            child: new Scaffold(
                appBar: new AppBar(title: new Text("Test App")),
                body: new Center(
                    child: new Text(
                        "Hello World!",
                        textAlign: TextAlign.center,
                        style: new TextStyle(
                            fontSize: 32.0f
                        )
                    )
                ),
                floatingActionButton: new FloatingActionButton(
                    child: new Icon(Icons.add),
                    onPressed: () =>
                    {
                        Navigator.push(
                            context,
                            new MaterialPageRoute(
                                fullscreenDialog: true,
                                builder: (buildContext => new SecondMyStatefulWidget())
                            )
                        );
                    }
                ),
                bottomNavigationBar: new BottomAppBar(
                    child: new Row(
                        mainAxisSize: MainAxisSize.max,
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: new List<Widget>
                        {
                            new IconButton(icon: new Icon(Icons.menu), onPressed: () => { }),
                            new IconButton(icon: new Icon(Icons.home), onPressed: () => { })
                        })
                )
            )
        );
    }
}

class SecondMyStatefulWidget : StatefulWidget
{
    public override State createState()
    {
        return new SecondMyState();
    }
}

class SecondMyState : State<SecondMyStatefulWidget>
{
    public override Widget build(BuildContext context)
    {
        return new Theme(
            data: new ThemeData(),
            child: new Scaffold(
                appBar: new AppBar(title: new Text("Test App")),
                body: new Center(
                    child: new Text(
                        "Hello World!",
                        textAlign: TextAlign.center,
                        style: new TextStyle(
                            fontSize: 32.0f
                        )
                    )
                ),
                floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
                floatingActionButton: FloatingActionButton.extended(
                    icon: new Icon(Icons.add),
                    label: new Text("Big FAB"),
                    onPressed: (() => { })
                ),
                bottomNavigationBar: new BottomAppBar(
                    child: new Row(
                        mainAxisSize: MainAxisSize.max,
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: new List<Widget>
                        {
                            new IconButton(icon: new Icon(Icons.menu), onPressed: () => { }),
                            new IconButton(icon: new Icon(Icons.home), onPressed: () => { })
                        })
                )
            )
        );
    }
}

OnEnable()に以下の記述がないとアイコン各種が読み込まれませんので注意してください。

FontManager.instance.addFont(Resources.Load<Font>(path: "MaterialIcons-Regular"), "Material Icons");

こちらも反映されると以下のようになります。
87y4u-j8x3i.gif

StatefulWidgetである意味合いがないコードですが,ご了承ください…

実機にビルドしてみる

ビルド方法は普通のUnityプロジェクトのと何ら変わりませんので詳細は省きます。

ビルドしたものをAndroidなどの実機で試すとわかりますが,そのままではステータスバーやナビゲーションバーが表示されません(記載したコードはナビゲーションバーのみ表示されるはずですが)。より通常のアプリらしく表示したいところですよね。これに関してはUIWidgets側で推奨されているものや,【Unity】Unity 5.3以降でAndroidのStatus Barを表示する方法が大変参考になります。

Androidへのビルドにおける描画不具合

出来上がったものがこちら。

…おや? おやおや?
なぜかFABしか表示されていません。FABをタップすると遷移し,×ボタンがあるであろうポイントをタップすると閉じて最初のページに戻ります。つまり存在はしているようだが,描画が上手くいっていないようです(なお,Windows向けビルドは正常に表示された)。

何が原因か知りませんが,2019の環境(2019.1.10f1)では上記のようになります。
もしやと思い,2018の環境(2018.3.13f1)にして再度プロジェクトを作り直してみたら無事表示されました。なぜ…大人しく2018の環境で開発した方がよさそうです

ステータスバーの表示

一応ステータスバーの表示に関しても軽く触れておきます(軽く触れるつもりが熱中して色々試してしまいました)。
今回自分はまず始めにUIWidgets側で推奨されている方法にのっとりました。
image.png
この2つのファイルをプロジェクトAssetsのPlugins/Android下に置きます。もしPluginsなどのフォルダがUnity上で作成されてない場合はフォルダごと持ってきてしまいましょう。

あとはビルドするだけです。が,公式に推奨されているわりに前者はどの場合も微妙でした。

前者のUnityTransparentStatusBarThemeの場合

ナビゲーションバーにUIが食い込んで押せなくなります。。。

前者のUnityStatusBarThemeの場合

ナビゲーションバーにUIが食い込むことはないものの,21:9の画面比率が悪いのか表示が乱れる。

後者の表示方法の場合

やったにゃぁ~ん”ん”!!

ちなみに横にしても問題ナッシングです。
Screenshot_20190719-194425.png

無事いい感じになりました。この方法の場合,ナビゲーションバー(下)とステータスバー(上)で表示方式を変更できるので,こちらで細かに調整ができ使い勝手がいいです。またAndroidにのみ対応させるのであれば,スクリプトをコピペして使うだけなのでとても楽に実装できます。
※Unity製のためSonyのゲームエンハンサーにゲーム判定を受けています。

Unityの機能と合わせて使う

さて無事表示されたことですし,C#でFlutterができるってだけでもとても大きいことですが,せっかくUnity上で作っているのだからUnityの機能を合わせてみたいですよね。
実際このアセットの強みはUnityの機能を活用しつつFlutterアプリケーションを作れる(またその逆も然り)部分ですので,実際に試していきたいと思います。
以下のクラスをTest.csに追記します。

class UnityFunctionStateless : StatelessWidget
{
    private Player _player;

    public UnityFunctionStateless()
    {
        _player = GameObject.Find("Player").GetComponent<Player>();
    }

    public override Widget build(BuildContext context)
    {
        return new Stack(
            fit: StackFit.expand,
            children: new List<Widget>()
            {
                new Align(
                    alignment: Alignment.topCenter,
                    child: new Row(
                        children: new List<Widget>()
                        {
                            new Container(
                                margin: EdgeInsets.only(left: 30.0f, top: 30.0f),
                                child: new FloatingActionButton(
                                    child: new Icon(Icons.arrow_back),
                                    onPressed: (() => Navigator.pop(context))
                                )
                            ),
                            new Container(
                                margin: EdgeInsets.only(left: 30.0f, top: 30.0f),
                                child: new Text("Play Area",
                                    style: new TextStyle(color: Colors.white, fontSize: 32f, inherit: false)
                                )
                            )
                        }
                    )
                ),
                new Align(
                    alignment: new Alignment(0.0f, 1.0f),
                    child: new Container(
                        margin: EdgeInsets.only(bottom: 30.0f),
                        child: FloatingActionButton.extended(
                            icon: new Icon(Icons.arrow_upward),
                            label: new Text("Jump"),
                            onPressed: (() => _player.Jump())
                        )
                    )
                )
            }
        );
    }
}

あんまりGameObject.Find()したくなかったんですが,TestクラスにSerializeFieldのメンバを追加してもヒエラルキー上からは見えず,事前にPlayerを登録できなかったので,仕方なくFindしました。

まとめ

最初は脳死状態でポコポコオブジェクトを配置していくだけで,Flutterで製作するようなアプリケーションが作れるのかと頭お花畑なことを考えましたが,さすがにそんな甘くはなかったですね。あくまでもUnity上でFlutterと同じ要領でアプリケーションが開発できますよというアセットでした。
なので,結局UnityのGUIのプロになっていたとしてもあまり恩恵がなさそうです。書き方はDartで扱っていたFlutterと同等です。冒頭でも少し触れましたが,すでにFlutterでバンバンアプリケーション開発をしている方はこのアセットでもバンバンアプリケーションを開発できると思います。
ドキュメントもFlutter公式リファレンスを読むのが手っ取り早いでしょう。
(他にも,なんか優しく手取り足取り教えてくれそうなリファレンスもあったので一応)
Dart特有の記述方法はありますが,ほぼそのままC#で書けば同じようなことができます。

「言語がC#という(人によっては)Dartより慣れ親しんだ言語で書ける」,「Unityという環境を持っている場合はややこしいFlutterやDartの実行環境を整える必要がない」というメリットがあると思います。また,他にもUnityの機能やその他のアセットが利用できるのが大きな強みと言えそうです。

NWaka_1415
プログラミング(基本ゲーム制作)をしたり、絵を描く🖊大学生です。 Language:C#(Unity),Java,Python,(C++)
https://niwakai.blogspot.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした