UnityでゲームやXRシステムを作成している際に、WebSocketAPIを使用してリアルタイム通信を実装したい時に便利なライブラリがあります。本記事では、WebSocketSharpライブラリを使用してリアルタイム通信を実装する方法について説明します。
バックエンド側の基本情報
サーバー側の基本仕様は以下のように実装しようと思います。
- プロトコル: WebSocket (WSS)
-
エンドポイント:
/ws/game - 認証: 不要(プレイヤーIDのみ)
- メッセージ形式: JSON
接続フロー
クライアントが接続して自身のプレイヤーIDを送るだけの最小フローです。単一プレイヤー(1人)での動作確認を想定しています。
- 接続確立: クライアントがWebSocket接続を開始
- プレイヤー登録: プレイヤー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;
}
}
}
}