見出し画像

【Unity】Websocket-sharpで通信する

Websocket-sharpを使ってUnityのサーバとクライアントでWebsocket通信を行ってみます。

1. Websocket-sharpライブラリの作成

Websocket-sharpをUnityで使用するためには、ビルドして生成されたDLLファイルをUnityに読み込ませる必要があります。まずは下記のWebsocket-sharpのリポジトリからプロジェクトファイルをダウンロードします。

ダウンロードが完了したら、websocket-sharp.slnファイルをダブルクリックします。Visual Studioがインストールされていない場合は、インストールを行ってください。今回はVisual Studio 2019でビルドを行います。

スクリーンショット 2021-06-23 153107_New

プロジェクトファイルを開いたときに、アップグレードのダイアログが表示された場合は[OK]ボタンを押して先に進みます。

無題

Visual Studioのソリューションエクスプローラーウィンドウで「Example」、「Example~Example3」の項目を削除します。

スクリーンショット 2021-06-25 111115_New

次にメニューからビルド形式を「Release」に設定します。

スクリーンショット 2021-06-25 111918_New

最後にメニューの[ビルド] > [ソリューションのビルド]を選択してビルドを実行します。

スクリーンショット 2021-06-25 111542_New

ビルドが成功すると、プロジェクトファイルが存在するフォルダの\websocket-sharp\bin\Releaseに「websocket-sharp.dll」が生成されます。

スクリーンショット 2021-06-25 112344

2. UnityへのWebsocket-sharpライブラリ読み込み

生成された「websocket-sharp.dll」をUnityエディタ上のProjectパネルにドラッグアンドドロップすると読み込ませることができます。今回はREADMEにならってAssets/Pluginsフォルダを作成してそこに保存します。

3. コード

websocket-sharpはクライアントとサーバの両方の機能に対応しています。両者をwebsocket-sharpで実装する場合は、Unityのプロジェクトを分けると良いでしょう。クライアント側のコードは以下のようになります。

using UnityEngine;
using System.Collections;
using WebSocketSharp;

public class ClientExample : MonoBehaviour {

   private WebSocket ws;

   void Start()
   {
       ws = new WebSocket("ws://localhost:3000/");

       ws.OnOpen += (sender, e) =>
       {
           Debug.Log("WebSocket Open");
       };

       ws.OnMessage += (sender, e) =>
       {
           Debug.Log("WebSocket Message Type: " + e.Type + ", Data: " + e.Data);
       };

       ws.OnError += (sender, e) =>
       {
           Debug.Log("WebSocket Error Message: " + e.Message);
       };

       ws.OnClose += (sender, e) =>
       {
           Debug.Log("WebSocket Close");
       };

       ws.Connect();

   }

   void Update()
   {

       if (Input.GetKeyUp("s"))
       {
           ws.Send("Test Message");
       }

   }

   void OnDestroy()
   {
       ws.Close();
       ws = null;
   }
}

実行中に「s」キーを押すとサーバに「Test message」という文字列を送信します。次にサーバ側のコードは以下のようになります。

using UnityEngine;
using System.Collections;
using WebSocketSharp;
using WebSocketSharp.Server;

public class ServerExample : MonoBehaviour {

   private WebSocketServer server;

   void Start ()
   {
       server = new WebSocketServer(3000);

       server.AddWebSocketService<Echo>("/");
       server.Start();

   }

   void OnDestroy()
   {
       server.Stop();
       server = null;
   }

}

public class Echo : WebSocketBehavior
{
   protected override void OnMessage (MessageEventArgs e)
   {
       Debug.Log(e.data);
       Sessions.Broadcast(e.Data);
   }
}

サーバはクライアントからメッセージを受け取ると同じ内容をブロードキャストします。

4. 実行

サーバとクライアントをUnityエディタ上で実行し、クライアントから「s」キーを押してメッセージを送ってみます。両者のConsoleパネル上にメッセージが表示されれば成功です。

5. 応用編

クライアントからデータを受け取ってサーバ側のシーン上で何か処理を行いたい場合は少し工夫が必要になります。

というのも、コードから見て分かるように、サーバ側の受信処理はWebSocketBehaviorを継承したクラスをWebSocketServerにサービスとして追加することで実現しています。このとき、WebSocketBehaviorの初期化やメッセージの受信時に実行されるOnMessageはメインスレッドとは異なるスレッドで非同期に処理されます。

言い換えると、受信のタイミングでシーン上のText UIにメッセージを表示させたり、オブジェクトを動かすといった操作はメインスレッドでしか実行できないため、このままのコードではデータをシーンに反映させることができません。(Debug.Logはメインスレッド以外でも処理可能です)

最も簡単に受信データをメインスレッドで利用する手段としてはWebSocketBehaviorクラスにstatic変数を追加する方法があります。コードとしては以下のようになります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;
using WebSocketSharp.Server;
using UnityEngine.UI;

public class Controller : MonoBehaviour
{
   private WebSocketServer server;
   [SerializeField] Text messageText;

   void Start() {
       server = new WebSocketServer(3000);
       server.AddWebSocketService<Echo>("/");
       server.Start();       
   }

   void Update()
   {
      messageText.text = Echo.message;
   }

   void OnDestroy() {
       server.Stop();
       server = null;
   }
}

public class Echo : WebSocketBehavior
{

   public static string message;

   protected override void OnOpen() {
       Debug.Log("OnOpen");
   }

   protected override void OnMessage (MessageEventArgs e) {
       message = e.Data;
   }
}

static変数やUpdate()を使用したくない場合は、UniRxを使う方法があります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;
using WebSocketSharp.Server;
using UniRx;
using UnityEngine.UI;

public class Controller : MonoBehaviour
{
   private WebSocketServer server;
   [SerializeField] Text messageText;

   private Subject<Quaternion> subject = new Subject<Quaternion>();

   void Start() {
       server = new WebSocketServer(3000);
       server.AddWebSocketService<Echo>("/", service => {
           service.SetSubject(subject);
       });

       subject
           .ObserveOnMainThread()
           .Subscribe(msg => {
               messageText.text = msg;
           }).AddTo(this);
       
       server.Start();
   }

   void OnDestroy() {
       server.Stop();
       server = null;
   }
}

public class Echo : WebSocketBehavior
{
   private Subject<string> subject;

   public void SetSubject(Subject<Quaternion> subject) {
       this.subject = subject;
   }

   protected override void OnOpen() {
       Debug.Log("OnOpen");
   }

   protected override void OnMessage (MessageEventArgs e) {
       this.subject.OnNext(e.Data);
   }
}

AddWebSocketServiceの第二引数でWebSocketBehaviorのインスタンスを受け取れるため、ここでUniRxのSubjectを渡しています。あとはSubscribeをメインスレッドで処理するように指定することで、データをメインスレッドで扱うことができるようになります。

他にもっと良い方法があるかもしれませんが、WebSocketBehaviorとデータを利用する両者でSubjectを用意し、参照の受け渡しを行う必要があるため、static変数を使う場合と比較するとやや冗長な感じにはなります。

6. ハマりポイント

Windowsマシンで実行する場合、サーバとクライアントで異なる端末で通信するときは、Windowsのファイヤーウォールで使用するポート(例:3000番)を開放する必要があります。

ポートの解放は実行するアプリケーション単位で設定可能なため、node.jsでは問題なくてもUnityでは通信できないといった症状が発生します。この場合はUnityからでもポートの送受信が可能なように、セキュリティに留意した上でファイヤーウォールの設定を行ってください。

7. おわりに

websocket-sharpを解説したサイトは他にもたくさんありますが、エコーサーバやチャットサーバの内容が多いため、この記事では受け取ったデータをシーンで活用する方法に重点を置いて解説しました。

最後にwebsocket-sharpを使ってスマホの傾きをサーバへ送り、シーンに配置してあるバーチャルスマホとシンクロさせてみました。リアルタイムに処理する手段としてぜひ活用してみてください。🌱

8. 参考


この記事が気に入ったらサポートをしてみませんか?