ゆべねこの足跡

IT系のはなしを残しておく場所です

UnityからMQTTブローカに接続し、メッセージをUIとして表示させる

MQTTについて色々やっているうちに「UnityからMQTTブローカに接続する方法があるのだろうか」という思いに駆られてしまったので、実践してみることにしました。今回はそのことについて書いていきます。

実行環境

  • Mac OS 10.14.2
  • Unity2018.3.4f1

Unityの.NETのバージョンは4.xとします。

使用するMQTTクライアントライブラリについて

C#用に作られたMQTTクライアントのライブラリにはいくつか種類がありますが、有名どころだとM2MqttかMQTTnetあたりになるようです。双方のGitHubのコミット履歴を見てみたところ、MQTTnetの方が開発がアクティブであったため、今回はMQTTnetを使ってやっていくことにします。

UnityにDLLファイルをインポートする

NuGet GalleryからMQTTnetの最新の安定バージョンである2.8.5をダウンロードします。ダウンロードはこちらから。ダウンロードされたデータ名はmqttnet.2.8.5.nupkgとなっていますが、このままでは展開できないので拡張子を.nupkgから.zipに変えて展開します。展開後、mqttnet.2.8.5 > lib > net472 中にある MQTTnet.dll を使用するUnityプロジェクトのAssets/Plugins/以下にインポートします。これでMQTTnetをUnity内で使用する準備ができました。

実装

この記事での具体的な実装としては、UnityからMQTTブローカに接続、SubscribeしてPublishされたメッセージをUnity内でテキストとして表示させるということをします。テキストの表示にはTextMeshProを利用しました。また、メッセージを受信した際のコールバック(ApplicationMessageReceived)内で受信したメッセージを他クラスに渡せるようにしています(UniRxの機能の一部を利用)。

非同期処理に注意

MQTTController.csの20行目と67行目に注目すると普段あまり見慣れないことが書いてありますね。

var context = SynchronizationContext.Current;

//            ~~~~  略 ~~~~

context.Post (_ =>
            {
                OnMessageReceived.OnNext (message);
            }, null);

実はUnity固有の処理はメインスレッド以外から実行することができません。これに気付かなくて1時間くらい表示されないテキストを前にして唸ってました...

MQTTnetの内部ではたくさんの箇所で非同期処理が行われており、そのほとんどの処理においてconfigureawait(false)となっています(こちらを参照)。結果、MQTTController.cs内ではスレッドIDが刻々と変化してしまい、Unity固有の処理をできなくなる...となるわけです。実験としてMQTTController.csの各所にスレッドIDを表示するログを出力するようにしてみます。

async void Start ()
    {
        var context = SynchronizationContext.Current;
        Debug.Log ($"はじめ : {Thread.CurrentThread.ManagedThreadId}");

        //   ~~~~ 略 ~~~~

        mqttClient.Connected += async (s, e) =>
        {
            Debug.Log ("MQTTブローカに接続しました");
            Debug.Log ($"ブローカ接続後 : {Thread.CurrentThread.ManagedThreadId}");
            await mqttClient.SubscribeAsync (
                new TopicFilterBuilder ()
                .WithTopic ("your/Subscribe/Topic")
                .Build ());
            Debug.Log ("指定したトピックをSubscribeしました");
            Debug.Log ($"Subscribe後 : {Thread.CurrentThread.ManagedThreadId}");
        };

        //   ~~~~ 略 ~~~~

        mqttClient.ApplicationMessageReceived += (s, e) =>
        {
            var message = System.Text.Encoding.UTF8.GetString (e.ApplicationMessage.Payload);
            Debug.Log ($"メッセージ受信 : {message}");
            Debug.Log ($"メッセージ受信後(context.Post以前) : {Thread.CurrentThread.ManagedThreadId}");
            context.Post (_ =>
            {
                Debug.Log ($"メッセージ受信後(context.Post内) : {Thread.CurrentThread.ManagedThreadId}");
                OnMessageReceived.OnNext (message);
            }, null);
        };

結果

f:id:yubeshineko:20190210025651p:plain

確かに、各処理の後においてスレッドIDが異なっていることが分かります。そして、context.Post内ではちゃんとUnityのメインスレッドに戻っていますね。試しにcontext.Postの部分をコメントアウトして実行してみると、テキストが表示されなくなります。

完成品

ちょっとした遊び心で「ぬこ」というメッセージをPublishすると猫が現れるようにしました。

参考記事

MQTTnetをUnityで使えるようにする

MQTTnet を Unity で使う - Qiita

うまくいかなかった原因解明のきっかけ

Oculus GoとNode-REDでMQTTをやり取りするメモ – 1ft-seabass.jp.MEMO

非同期処理とSynchronizationContextについて

【Unity開発者向け】「SynchronizationContext」と「Taskのawait」 - Qiita

Unity2017で始めるTask(async~await) - Qiita