UnityEditorからスマホに直転送して確認したい

f:id:hirasho0:20191003164720p:plain

こんにちは。技術部平山です。

今回は、スマホでhttpサーバを動かした話 の続編でして、UnityEditorから直接スマホにデータを送って即確認する仕組みのお話です。 コードはgithubに全て置いてありますが、まだ実製品の開発には投入しておりません。 現状はプロトタイプです。

今回作ったものを使う流れ

  1. スマホに今回用意したアプリをインストールして起動しておきます。
  2. 今回作ったエディタ拡張ウィンドウに、スマホ側のIPアドレスとポート番号を入れます。
    • IPアドレスはスマホの画面上に出ています。ポート番号は本サンプルでは8080です。
  3. エディタのプロジェクトビューで、スマホで見たいプレハブを選択します。
    • 図ではKonchというプレハブが選択されていますね。
  4. 「選択してるものを送る」ボタンを押します。
  5. スマホで絵が出ます。MonoBehaviour.Start()で再生開始するアニメーションが入っていれば再生されます。

スマホ上の操作は以下のような感じです。

  • タッチ操作で回転、拡大縮小
  • Playボタンを押すとインスタンス生成からやり直し
  • Stopでインスタンス削除
  • Nextを押すと次のプレハブへ移行(複数送った場合)

選択するのはプレハブでもいいし、プレハブを含んだフォルダでもかまいません。 依存したアセットも一緒に転送されるので、単にプレハブだけを選ぶのが 一番使い勝手が良いかと思います。

おまけ機能

スマホ側に解像度が書かれたボタンがありまして、 これを押すと、解像度の縦横を1.414(sqrt(2))分の1にして、総画素数を半分にします。 「ピクセル処理が重いせいでフレームレートが落ちている」かどうかは、これですぐわかるわけです。

さらに進めて、UnityEngine.Profiling.Profilerを使って、スマホ側でプロファイリングを行い、 データをwifiでエディタに送る、ということも考えていますが、 使うのは主にアーティストさん、あるいは企画さんですから、 「シェーダ重い」「マテリアル多すぎ」のように一行でわかるレポートを出す方が、実用性は高いでしょう。 そのあたりは今後考えます。

注意点

  • UnityEditorのPlatformはつなぐ機械に合わせてください。iPhoneを使うならiOSです。でないと互換性がないデータを送ってしまいます。
  • スマホとPCは直接通信できる必要があります。ネットワークが分かれていて通らない、といったことがあると使えません。

何のために作った?

開発の反復速度(イテレーション)を上げるためです。

もちろん、Unityを使えば、作ったものはUnityEditorの画面上で見られます。 シーンビューなら即座に見えますし、 ゲームの流れの中でプログラムが入った状態での見え方を知りたければ、 再生してゲームビューで見ればいいわけです。 ですので、いちいちスマホに持っていって確認する必要はそんなにはありません。

ですが、スマホ実機で確認した方がいいことや、実機で確認せざるを得ないこともあるのです。

  • 処理負荷
    • 実際どれくらい重いのかは実機で動かさないとわかりません。
    • 今回のサンプルでは処理負荷のメーターも出しておきました。重ければすぐわかります。
  • 解像度の適正さや、サイズによる印象の確認
    • PCのモニタは大きいので、無駄に解像度を上げたくなってしまいがちですが、実機は小さいのでそんなに解像度がなくても気にならないことは多いはずです。
  • ハードウェアの個性の確認
    • たまにハードウェアの個性で違った見え方をすることがあります(バグとも言う)。Zバッファ書き込み制御が効いていないように見える機種を見たことがあります。

実機での確認は手間がかかるので、やらずに済む方がコストはかかりません。 「実機で確認するのがプロの心得」と言う人もいますが、 私は「どこまで実機確認せずに済ませられるか詰めてこそプロ」と思っていますので、 たぶん仲良くはなれないでしょう。 そんな私でも、実機確認が必要な場面があることは認識しており、 どうせ必要ならできるだけコストを安くすべきだ、と思うわけです。

これがなかったらどうなる?

PCからスマホに直接転送する手段がない場合、 代表的な確認の方法は二種類あるのではないかと思います。

アプリ本体にデータを入れてビルドする

原理的にはプログラマなしでもできますが、 アプリ本体のプロジェクトの適切な場所にデータをつっこむ必要があるため、 プログラマの手が必要になるケースも多いでしょう。

受け渡しにgitを使えば、gitの操作も必要になりますし、ビルドにjenkinsを使う場合はその手続きも必要になります。手順が効率化され、ビルドを高速なマシンでやるとしても、待ち時間を数分より短くするのは難しいでしょう。典型的には30分くらいはかかるのではないかと思います。

アセットバンドルをビルドしてサーバに配置する

アセットバンドルのビルドを行って、アプリからアクセスできるサーバに配置します。 ビルドをアーティストのPC上でやるか、一旦git等に上げてからjenkinsでビルドするかは 選択肢がありますが、できたものをサーバに上げる手続きが必要です。

プログラマの手を介する必要はないでしょうし、アプリのビルドほどの時間はかからないでしょうが、 それでも1分以内、というわけには行かないかと思います。

で、これをどう使うか?

今回のサンプルは、あくまでサンプルです。 実製品では アプリ本体に入っているスクリプトがないと動かないケースも多いでしょうし、 どのような機能が必要になるかは製品によります。

例えば、ライティングを朝と夜で切り換えて具合を見たいとか、 いくつかある品質設定を切り換えた時の処理負荷が見たいとか、 他のゲーム要素と一緒に出して見たいとか、 そういったことはいくらでも考えられます。

ですから、今回作ったものをそのまま使うことは想定しておらず、 現実的には、アプリの中に専用シーンを用意するか、 専用アプリを用意するか、といった選択肢になるかと思います。 専用アプリを用意するのは、 おそらく本体とアセットバンドルを別プロジェクトに分けているケースでしょう。 アーティストはアプリ本体でしか使わないスクリプト群を必要とせず、 プログラマの大半はアセットバンドル素材には触れないので、 分けた方が安全かつ効率的です。

アーティスト向けツールなどもアセットバンドル側プロジェクトに入れるのが おそらく良く、今回のツールもその例になるかと思います。 アプリ本体と同じスクリプトが必要なケースでは、 本体からパッケージをエクスポートして入れることになります。

実装

エディタ側は、選択されたものをこっそりアセットバンドル化して、 UnityWebRequestでスマホ側のサーバにPUTします。

スマホアプリ側には、以前紹介したhttpサーバが入っており、 ファイルアップロードのイベントで実行されるコールバックで、 アセットバンドルをロードし、中のプレハブを見つけてInstantiateします。

雑に言えばそれだけです。仕組みとしては難しいことはありません。 ただ、結構面倒くさいところもあります。

切断対策

オフィスの1階から3階に上がると何故かつながらない、 といったことが結構起きます。 違うルータに切り換えるにあたってwifiが一時的に切れたのでしょうが、 切れた後しばらく調子が悪いこともあります。アプリを再起動したり、 wifiを一度offにしたりしないといけないこともありました。

詳しいことはおいおい調べますが、 いずれにせよ、エディタ側、スマホ側の両方に「ネットワークの接続状態」 を表示しておかないと、「なんか動かない」となって使い勝手を 悪くしてしまうことが想像されます。

そこで、エディタ側では5秒ごとにスマホのAPI(pingという何もしないapiを用意してある) を叩いて、接続を確認するようにしています。これはもっといい手があるかもしれませんので、 ネットワークに詳しい方に教えを乞いたいところです。

スマホ側はサーバですので、単純にApplication.internetReachability を見て、定期的に画面表示を更新しています。 つながっていればIPアドレスを表示し、wifiが切れていれば「NO WIFI」と表示します。 この手のツールで4Gの回線を使うのはどうかと思うので、 wifiが切れていれば使えない、としています。 LANのIPアドレスを取るのは以下のようなコードです。

public static string GetLanIpAddress()
{
    string ret = null;
    foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
    {
        if ((ni.NetworkInterfaceType == NetworkInterfaceType.Wireless80211) || (ni.NetworkInterfaceType == NetworkInterfaceType.Ethernet))
        {
            foreach (var ip in ni.GetIPProperties().UnicastAddresses)
            {
                if (System.Net.IPAddress.IsLoopback(ip.Address))
                {
                    // 無視
                }
                else if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                {
                    ret = ip.Address.ToString();
                }
            }
        }
    }
    return ret;
}

速度が出ない

前にhttpサーバの記事を書いた時点では、たかだか数KBのファイルしか 送っていなかったので、速度のことは全く考えていませんでした。 しかしメガバイト量のデータを送ると数十秒かかることがわかり、 処理を見直しています。

具体的には、PUTを受け付けてファイルをpersistentDataに保存する処理を、 同期処理から非同期処理にし、CPUが無駄にスピンロックしないようにしました。 メインスレッドが休みなくwhileで回ってしまうと、 別のスレッドで動いているhttpサーバ処理も遅くなってしまうようです。

まだ秒間1MBも出ないので遅いのですが、 とりあえず使えなくもない速度になったかと思います。 より高速化する手法をご存知の方は是非教えてください!

作ったアセットバンドルをPCで見るとピンク

Android用のアセットバンドルには、Androidでしか使えないシェーダが入っています。 Android用のテクスチャはPCでも使えるのに、シェーダはダメなのです。 アセットバンドルにマテリアルが入っていて、 そのマテリアルが使っているシェーダがアプリ本体にしかない場合、 アセットバンドルの中にシェーダのコピーが入ります。 アプリ側のシェーダを変更してもアセットバンドルに入っている方は変わらないので 事故の元になる、というのはまあ別の話ですね。

さて、今回の仕組みをデバグするにあたって、 Android用のアセットバンドルをPCでも 見えるようにしたい、という欲求が出てきます。 ビューアアプリをエディタで再生状態にして、PCのIPアドレスを指定すれば 動くのですが、シェーダだけはダメです。ピンクになったり環境光が当たらなくなったりします。

というわけなので、エディタ実行時にはマテリアルの中のシェーダを PC側のシェーダに差し換えることにしました。

material.shader = Shader.Find(material.shader.name);

これだけです。InstantiateしたGameObjectからRendererを全部得て、 マテリアルを全て取り出し、Shader.FindでPC側のシェーダを見つけて差し換えます。

おまけ:高速化

この処理は、大量のマテリアルがあると、かなりのメモリと処理時間を食います。 また、マテリアルプロパティが数多くある複雑なシェーダだと、 マテリアルプロパティを一つづつコピーする処理が必要らしく、 べらぼうに重くなるのです。

エディタだけなら気にしなくてもいい、とも言えるのですが、 実製品では200MB以上のメモリと4秒以上の時間がかかったりもして、 ちょっと悲しかったので対処しました。 今回のサンプルにはこれが入れてあります。 ImcompatibleShaderReplacerという、英語が不安な名前のクラスでして、 これのReplace()を呼ぶと、エディタ実行時のみ再帰的にマテリアルのシェーダを差し換えてくれます。

その際に、「すでに差し換え処理をしたマテリアルはもう差し換えない」 という処理を足すことで、無駄な差し換え負荷を防いでいます。 素直にHashSetにMaterialを登録していくと無限にHashSetが 肥大化していって、そこに格納されたMaterialがメモリリークになるため、 固定の配列を用いて、一定数以上に増えないようにしてあります。

終わりに

いじっては確認、いじっては確認、というサイクルが速く回ることは 何にも増して重要です。 待ち時間が長いとサイクルを回す回数が減って質が落ち、 待ち時間のためにやる気が失せます。

今回はサイクルを早く回すための方法を一つ試してみました。 今後は特定の製品向けのカスタマイズを行って、実戦投入していく予定です。 そこで得た経験は、今回のサンプルにも反映させていくことになるでしょう。

なお、製品向けカスタマイズをするまでもなく、必要とわかっていることはいくつかあります。

  • ライト制御
    • ライトも当然動かしたい
  • hierarchyとinspectorに相当するものをエディタに表示して閲覧、値の変更をできるようにする
    • 特定のオブジェクトを出したり消したり動かしたりしたいはず
    • コンポーネントのパラメータも見たいしいじりたいはず
  • canvas対応
    • 現状canvasの下にInstantiateされる前提のprefabだと何も出ない。そして2D的に表示する機能もない。

このあたりができた時点で、また記事にするかもしれません。また、 プレハブだけでなく、シーン丸ごと、メッシュ単体、テクスチャ単体、 といったものも見られると良いのかもしれません。

ところで、UniteでUSBで実機と通信して似たようなことをする話 を聞きまして(106ページ目あたりから)、それもアリだなと思いました。 ネットワークに起因する面倒は結構ありますし、 IPを打ち込む手間もあります。 USBでつながっていれば、それを第一選択とするように拡張するのは良さそうです。