0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity×WebSocket】WebSocketSharpで実装する接続・応答・再接続の最小構成ガイド

Posted at

UnityでゲームやXRシステムを作成している際に、WebSocketAPIを使用してリアルタイム通信を実装したい時に便利なライブラリがあります。本記事では、WebSocketSharpライブラリを使用してリアルタイム通信を実装する方法について説明します。

バックエンド側の基本情報

サーバー側の基本仕様は以下のように実装しようと思います。

  • プロトコル: WebSocket (WSS)
  • エンドポイント: /ws/game
  • 認証: 不要(プレイヤーIDのみ)
  • メッセージ形式: JSON

接続フロー

クライアントが接続して自身のプレイヤーIDを送るだけの最小フローです。単一プレイヤー(1人)での動作確認を想定しています。

  1. 接続確立: クライアントがWebSocket接続を開始
  2. プレイヤー登録: プレイヤーIDを送信して接続確認(ゲーム開始は行いません)

メッセージ仕様

ここでは「接続確認」のみを扱います。クライアントはプレイヤーIDを送信し、サーバーは接続が受理されたことを応答します。

クライアント → サーバー

接続確認メッセージ
クライアントは自身を識別するためのplayerIdを送信します。

{
  "type": "connection_check",
  "data": {
    "playerId": "player-uuid-here"
  }
}

サーバー → クライアント

接続応答メッセージ
サーバーは接続が受理されたことを返します。動作確認ではプレイヤー数は常に1人で問題ありません。

{
  "type": "connection_response",
  "data": {
    "playerId": "player-uuid-here",
    "message": "接続確認完了"
  }
}

Unity側の実装

ここからはUnity(クライアント)側の実装です。「接続→ID送信→応答受信」までの最小実装に絞って解説します。

using WebSocketSharp; // WebSocketクライアントの接続・送受信・イベントハンドリングを提供
using System; // 例外、Guid、属性などC#の基本機能
using System.Collections.Generic; // System.Collections.Generic: Queue<Action>などの汎用コレクション
using UnityEngine; // MonoBehaviour、Debug、UnityのメインスレッドAPI など


// WebsocketAPIで通信するためのメッセージ(クライアント→サーバー)の型
[Serializable]
public class WebSocketMessage
{
    public string type;
    public object data;
}

// WebsocketAPIで通信するためのメッセージ(サーバー→クライアント)の型
[Serializable]
public class ConnectionResponse
{
    public string type;
    public ConnectionData data;

    [Serializable]
    public class ConnectionData
    {
        public string playerId;
        public string message;
    }
}

// WebSocketManager: WebSocketの接続・送受信・イベントハンドリングを管理
public class WebSocketManager : MonoBehaviour
{
    private WebSocket ws;
    private string serverUrl = "wss://your-server.com/ws/game"; // みなさんのURLに合わせて適宜変更してください.ローカルの場合はlocalhostになります.

    // メインスレッド実行用
    private readonly Queue<Action> _mainThreadActions = new Queue<Action>();
    private readonly object _actionsLock = new object();

    // 再接続設定
    private int maxRetryAttempts = 3;
    private int retryCount = 0;
    private float retryDelay = 3f;

    void Start()
    {
        // WebSocketの接続を開始
        ConnectToServer();
    }

    void Update()
    {
        // メインスレッドで実行するアクションを処理
        ProcessMainThreadActions();
    }

    void ConnectToServer()
    {
        // WebSocketの接続を開始
        ws = new WebSocket(serverUrl);

        // WebSocketのイベントハンドラーを設定
        ws.OnOpen += OnWebSocketOpen;

        // WebSocketのメッセージを受信
        ws.OnMessage += OnWebSocketMessage;

        // WebSocketのエラーを受信
        ws.OnError += OnWebSocketError;

        // WebSocketの接続が切れた
        ws.OnClose += OnWebSocketClose;

        // WebSocketの接続を開始
        ws.Connect();
    }


    // WebSocketの接続が開いた
    void OnWebSocketOpen(object sender, EventArgs e)
    {
        Debug.Log("WebSocket接続成功");
        retryCount = 0; // 接続成功でリセット

        // プレイヤーIDを生成
        string playerId = Guid.NewGuid().ToString();
        // WebsocketAPIで通信するためのメッセージ(クライアント→サーバー)を構築
        string message = $"{\"type\":\"connection_check\",\"data\":{\"playerId\":\"{playerId}\"}}";

        // メッセージを送信
        ws.Send(message);
    }

    // WebSocketのメッセージを受信
    void OnWebSocketMessage(object sender, MessageEventArgs e)
    {
        // メインスレッドで実行するアクションを処理
        lock (_actionsLock)
        {
            // メインスレッドで実行するアクションを処理
            _mainThreadActions.Enqueue(() =>
            {
                // メッセージを処理
                ProcessMessage(e.Data);
            });
        }
    }


    // WebSocketのエラーを受信
    void OnWebSocketError(object sender, ErrorEventArgs e)
    {
        Debug.LogError($"WebSocket エラー: {e.Message}");
    }

    // WebSocketの接続が切れた
    void OnWebSocketClose(object sender, CloseEventArgs e)
    {
        Debug.LogWarning($"WebSocket 切断: {e.Code}, {e.Reason}");

        if (retryCount < maxRetryAttempts)
        {
            // 再接続試行回数を増やす
            retryCount++;
            Debug.Log($"再接続試行 {retryCount}/{maxRetryAttempts} - {retryDelay}秒後");
            // 再接続を試行
            Invoke(nameof(RetryConnection), retryDelay);
        }
        else
        {
            Debug.LogError("最大再接続試行回数に達しました");
            HandleConnectionFailure();
        }
    }

    // WebSocketの再接続を試行
    void RetryConnection()
    {
        if (ws?.ReadyState != WebSocketState.Open)
        {
            Debug.Log("WebSocket再接続中...");
            ConnectToServer();
        }
    }

    // WebSocketの接続に失敗した
    void HandleConnectionFailure()
    {
        Debug.LogError("WebSocket接続に失敗しました");
        // 必要であればUI表示やオフラインモード遷移など
    }

    // メインスレッドで実行するアクションを処理
    void ProcessMainThreadActions()
    {
        // メインスレッドで実行するアクションがない場合は処理を終了
        if (_mainThreadActions.Count == 0) return;

        // メインスレッドで実行するアクションを処理
        lock (_actionsLock)
        {
            while (_mainThreadActions.Count > 0)
            {
                // メインスレッドで実行するアクションを取得
                var action = _mainThreadActions.Dequeue();
                try
                {
                    // メインスレッドで実行するアクションを実行
                    action?.Invoke();
                }
                catch (Exception ex)
                {
                    Debug.LogError($"メインスレッドアクションエラー: {ex.Message}");
                }
            }
        }
    }

    // WebSocketのメッセージを処理
    void ProcessMessage(string message)
    {
        try
        {
            var typeCheck = JsonUtility.FromJson<WebSocketMessage>(message);
            switch (typeCheck.type)
            {
                case "connection_response":
                    // WebSocketの接続応答メッセージを処理
                    HandleConnectionResponse(message);
                    break;
                default:
                    Debug.LogWarning($"未知のメッセージタイプ: {typeCheck.type}");
                    break;
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"メッセージ解析エラー: {ex.Message}");
        }
    }

    // WebSocketの接続応答メッセージを処理
    void HandleConnectionResponse(string message)
    {
        // WebSocketの接続応答メッセージを処理
        var response = JsonUtility.FromJson<ConnectionResponse>(message);
        Debug.Log($"接続確認完了: {response.data.message}");
    }

    // アプリがポーズされた
    void OnApplicationPause(bool pauseStatus)
    {
        if (!pauseStatus)
        {
            Debug.Log("アプリ再開 - 接続状態を確認");
            // WebSocketの接続状態を確認
            CheckConnectionStatus();
        }
    }

    // アプリがフォーカスされた
    void OnApplicationFocus(bool hasFocus)
    {
        if (hasFocus)
        {
            Debug.Log("フォーカス獲得 - 接続状態を確認");
            // WebSocketの接続状態を確認
            CheckConnectionStatus();
        }
    }

    // WebSocketの接続状態を確認
    void CheckConnectionStatus()
    {
        if (ws?.ReadyState != WebSocketState.Open)
        {
            Debug.Log("接続が失われています - 再接続を試行");
            // WebSocketの接続を再試行
            ConnectToServer();
        }
    }

    // WebSocketの接続を終了
    void OnDestroy()
    {
        if (ws != null)
        {
            try
            {
                // WebSocketの接続状態を確認
                if (ws.ReadyState == WebSocketState.Open)
                {
                    // WebSocketの接続を終了
                    ws.Close();
                }
            }
            catch (Exception ex)
            {
                Debug.LogError($"WebSocket終了エラー: {ex.Message}");
            }
            finally
            {
                ws = null;
            }
        }
    }
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?