[7日目] Raspberry Pi 2にWindows 10 IoT Coreを入れてUnityアプリを動かしてみた


こんにちは。組み込みエンジニアの片岡です。アドベントカレンダー7日目、ついに一週間ですね。
先日、[2日目] ついに外の世界(ネットワーク)へ「引きこもり係数」ツイートの仕組みを解説 [前編](「ESP8266」で始めるIoT入門)を読んで、48時間外に出ないのは日常茶飯事なので、100時間ぐらいにしたほうがいいのでは?と提案しましたが、もっと外へ出ろとアドバイスされました。

さて、残念ながら私の担当製品はまだひみつなので、担当業務とは関係なく、先日の連休中に引きこもってRaspberry Pi 2とUnityで遊んでみた内容を書いてみたいと思います。


背景

Windows 10 IoT Core

以前、Raspberry PiでWindowsが動くというニュースには驚かされた方は多いのではないかと思います。Windows 10 IoT Coreは、Raspberry Pi 2などの組み込みボードで動くOSです。「Windows」を冠したOSですが、従来のWindowsとはかなり異なります。デスクトップもスタートメニューもありません。Visual Studio 2015で開発したWindows 10ユニバーサルアプリ(UWP)をデプロイして実行できる環境です。
Microsoft社のサイトには、Raspberry Pi 2などのデバイスとWindows 10 IoT Coreを用いた作例がいろいろと紹介されています。

Unity

Unityはクロスプラットフォームなゲームエンジンで、ゲームだけでなく様々なアプリケーションを開発できます。私自身は以前から映像作りなどの趣味でUnityを使用して遊んでいました。
Unityで開発したアプリケーションは、単一のソースからWindows、Mac、Linuxはもちろん、iPhone、Androidなど、数多くのプラットフォーム向けにビルドする事ができるのが特徴です。組み込み開発者としては、AndroidやLinuxの組み込み環境への対応が気になるところですが、今のところRaspberry PiなどのARM上のLinuxには対応していません。ARMでUnityを用いるには、Androidの環境を用意する必要があり、微妙な敷居がありました。
ところが今年9月に、Unity5.2からはUWPに対応するという記事が発表されました。UWPを書き出せるということは、先述のWindows 10 IoT Coreで動くということになるので、組み込み用途でUnityを使う際の新たな選択肢として、試してみることにしました。


環境構築

さて、試してみるとは言っても、UWPの開発には色々と条件があり、環境構築には時間がかかりました。おすすめの手順としては、

  1. Windows10でUWPを開発する環境を整える
  2. Windows 10 IoT CoreでUWPを実行、デバッグする手順を覚える
  3. UnityでUWP書き出しを使って、Visual Studioのプロジェクトを書き出す
  4. プロジェクトをVisual Studioからビルド、Windows 10 IoT Coreで実行!

という感じです。

Windows10でUWPを開発する環境を整える

UWPの開発にはまず、Windows10が必要です。(ここが最大の難関な気がします。。)
Windows10上にVisual StudioやWindows10 SDKをインストールして、一般的なUWP開発環境を構築します。詳しくはMicrosoft社の情報を参照してください。

Windows 10 IoT CoreでUWPを実行、デバッグする手順を覚える

めでたく最初のUWPがビルドできたら、Raspberry Pi 2などにWindows 10 IoT Coreをインストールして、UWPを動かしてみましょう。こちらの記事が非常に参考になりました。
ここまでで、Windows 10 IoT CoreでUWPを実行、デバッグできるようになりました。これだけでも色々できそうですが、今回は深入りせずにUnityを動かしてみました。

UnityでUWP書き出しを使って、Visual Studioのプロジェクトを書き出す

UnityがUWP書き出しに対応したというのは先述した通りなのですが、書き出し方が少し変わっています。Unityを使っている方はご存知の通り、色々なプラットフォーム向けの実行ファイルを簡単に生成できますが、UWP書き出しの場合は実行ファイルではなく、Visual Studioのソリューションが生成されます。生成されたソリューションをVisual Studioで開き、ビルドして実行ファイルを生成する、という流れになります。
とりあえずデフォルトのシーンをUWP書き出ししてみたりして、環境がうまく構築できているか確かめましょう。

プロジェクトをVisual Studioからビルド、Windows 10 IoT Coreで実行!

環境構築ができて、ソリューションが生成されたら、いよいよビルドして実行してみましょう。先ほど空のシーンを書きだした方は、Raspberry Pi 2上でいつものSkyboxを見ることができます。
無事にSkyboxを見れた方は気づいたと思いますが、Unityロゴの表示さえカクカクで、実効速度は非常に遅いことがわかります。Windows 10 IoT Core向けのUnityアプリケーションを作成する際は注意が必要です。


サンプルアプリケーション

さて、無事に環境構築ができたので、サンプルアプリケーションを作成してみました。限られたリソースの中でUnityらしさを発揮するために、Webカメラの画像をネットワーク経由で転送してみることにします。
UnityNetworkCameraSample
左上の黒い領域にリアルタイムにカメラ画像を表示します。Captureボタンを押すと、白い領域がに撮影した画像を表示すると同時に、指定したIPアドレスに画像を転送します。

サンプルプロジェクトの作成

今回、UnityのGUIシステムを使用します。UnityのuGUIを用いたGUIアプリケーションの作成方法については、ここでは省略しますので、公式のチュートリアルなどを参照してください。
サンプルアプリケーションのプロジェクト構成は単純で、UIとして画像を表示するRawImageを2つと、Captureボタンを配置して、ボタン投下時にスクリプトを実行するだけです。

まずは必要なUIオブジェクトを配置します。CanvasオブジェクトのCanvas ScalerコンポーネントのUi Scale ModeをScale With Screen Sizeにしておくと、デバイス上で大画面で表示されます。
配置ができたら、新規スクリプトからWebCamView.csを作成して、下記のコードを入力して、保存してください。

using UnityEngine;
using UnityEngine.UI;
using System.Net;
using System.Net.Sockets;

public class WebCamView : MonoBehaviour
{
    // リアルタイム画像表示用UI
    public RawImage realtimeView;
    // キャプチャ画像表示用UI
    public RawImage capturedView;

    // 画像送信先
    public string sendToAddress;
    public int sendToPort;

    // カメラ画像用テクスチャ
    WebCamTexture webcamTexture;
    
    void Start()
    {
        // 最初に見つかったカメラ画像をQVGAで取得
        WebCamDevice camDevice = WebCamTexture.devices[0];
        webcamTexture = new WebCamTexture(camDevice.name, 320, 240);

        // UIのテクスチャに指定して、リアルタイムに更新
        realtimeView.texture = webcamTexture;
        webcamTexture.Play();
    }

    public void capturePixels()
    {
        // リアルタイム画像を複製して新しいテクスチャを生成
        var capturedTex = new Texture2D(webcamTexture.width, webcamTexture.height);
        capturedTex.SetPixels(webcamTexture.GetPixels());
        capturedTex.Apply();

        // UIのテクスチャに指定
        capturedView.texture = capturedTex;

        // JPEGエンコードして、生データを送信
        var jpg = capturedTex.EncodeToJPG(30);
        udpSend(jpg, sendToAddress, sendToPort);
    }

    // UDPデータ送信用関数
    void udpSend(byte[] data, string ipStr, int port)
    {
        //データと送信先を設定
        SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
        socketEventArg.SetBuffer(data, 0, data.Length);
        socketEventArg.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(ipStr), port);

        //UDPソケットを作成して送信
        new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp).SendToAsync(socketEventArg);
    }
}

作成したスクリプトはボタンに関連付けて、ClickイベントでcapturePixels()が呼ばれるように設定しておきます。また、スクリプトのSend To AddressとSend To Portを適切に設定しておきます。
UnityProject
以上でコーディングは終了ですが、UWPとして出力するにはいくつかプロジェクト設定が必要です。プロジェクト設定は、メニューのEdit→ProjectSettings→Playerを選択しUWPを示す緑のWindowsストアアイコンをクリックして行います。
まず、Other SettingsのScripting Define SymbolsがMOBILE_INPUTとなっている場合は、CROSS_PLATFORM_INPUTに書き換えます。これをしないと、マウスやキーボードの操作の一部が無効になり、イベントが発生しないことがあるようです。
次にPublishing SettingsのCapabilitiesのリストのうち、WebCamやInternet関連など、今回必要なものにチェックを入れます。
ChangeInputSettingEdit_ProjectSettings_Player_PublishingSettings
以上で設定作業まで完了です。あとはUWPとして書き出してみましょう。ビルド設定でUWPを選択し、Buildをクリックして出力フォルダをすると、Visual Studioのソリューションが書き出され、Raspberry Pi 2へのデプロイなどができるはずです。
UnityBuildSettings


サンプルアプリケーション動作確認

作ったサンプルの動作確認をしてみました。
Raspberry PiにUSBカメラとディスプレイ、マウスなどを接続し、サンプルアプリを起動しておきます。LANケーブルはルーターに接続しておきます。
ネットワーク機能の確認のため、ノートPCを同じネットワークにつなぎます。ノートPCでは、送信された画像データを表示するサーバーアプリを起動しておきます。こちらのサーバーアプリもUnityで作成しましたが、詳細は今回は割愛させてください。
ついでに、ノートPCにもWebカメラをつなぎ、サンプルアプリを起動しておきます。

UnityNetworkCameraSampleConfig

これで、RasPi上でCaptureボタンを押せば、LANでつながったPCのサーバーアプリに画像が表示され、PC側のアプリののCaptureボタンでも同様にサーバーアプリの画像が更新されるはずです。

実際にやってみた動画はこちら。Unityで異なる環境向けにビルドしたアプリケーションが、正常に動作しました。


まとめ

今回、Raspberry Pi 2にWindows 10 IoT Coreを入れて、その上で動くUnityアプリを作ってみました。Raspberry Pi 2とノートPCで動くアプリケーションを単一のソースからビルドし、動作確認することができました。
USBカメラ画像の取得やUDP通信についても、全く異なるデバイス上で正常動作したのは驚きでした。

ただ、UnityのUWP作成の際は、いくつかハマった点がありました。

  • UIに関して、適切なプロジェクト設定をしないとマウスやキーボードイベントが発生しないことがある。
  • WebCamへのアクセスやネットワークの仕様には、適切にプロジェクト設定を変更しておく必要がある。
  • UDP通信について、System.Net.Sockets.UdpClientなどの便利なクラスを使いたかったのですが、UWPではSocketクラスしか使えなかった。
  • 本当はSystem.Ports.SerialPortを用いてUARTなどを使いたかったが、SerialPortクラスはUWPでは使用できなかった。

これらの注意点や非常に限られたハードウェアリソースを踏まえて開発すれば、RasPi上でUnityアプリを動かすことができます。
Windows 10 IoT Coreが走るボードも徐々に増えてきているので、今後組み込みプロトタイピングの新たな選択肢となっていくのではないかと思います。

投稿者プロフィール

DaisukeKataoka
DaisukeKataoka