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); };
結果
確かに、各処理の後においてスレッドIDが異なっていることが分かります。そして、context.Post内ではちゃんとUnityのメインスレッドに戻っていますね。試しにcontext.Postの部分をコメントアウトして実行してみると、テキストが表示されなくなります。
完成品
UnityからMQTTブローカに接続
— ゆべしネコ (@yubeshineko) February 9, 2019
IoTデバイスのセンサの値とかを使ってUnityでなんかできそう pic.twitter.com/yQPn8wNqST
ちょっとした遊び心で「ぬこ」というメッセージをPublishすると猫が現れるようにしました。
参考記事
MQTTnetをUnityで使えるようにする
うまくいかなかった原因解明のきっかけ
Oculus GoとNode-REDでMQTTをやり取りするメモ – 1ft-seabass.jp.MEMO
非同期処理とSynchronizationContextについて