カスタムバックエンドサービスに接続するラッパーの実装 - ゲームクライアント統合 - (Unity モジュール)
注釈:本資料はAI技術を用いて翻訳されています。
ラッパーを開く
このチュートリアルでは、WebSocket 接続を介してゲームクライアントをサンプルマッチメイキングバックエンドサービスに接続する方法を説明します。Byte Wars には、CustomMatchmakingWrapper_Starter クラスで定義された完成したラッパーがあります。このチュートリアルでは、そのラッパーのスターターバージョンを使用して、機能をゼロから統合します。
スターターパックの内容
このチュートリアルに従うには、CustomMatchmakingWrapper_Starter というスターターラッパークラスを使用します。このラッパーは以下のファイルで定義されています:
- CS ファイル:
Assets/Resources/Modules/CustomMatchmaking/Scripts/CustomMatchmakingWrapper_Starter.cs
また、以下のファイルにモデルクラスが定義されています:
- CS ファイル:
Assets/Resources/Modules/CustomMatchmaking/Scripts/CustomMatchmakingModels.cs
CustomMatchmakingModels クラスには、以下の定義済み定数、構造体、およびヘルパー関数が含まれています:
-
マッチメイキングの状態に基づいてメッセージを表示する文字列。
public static readonly string TravelingMessage = "Traveling to Server";
public static readonly string FindingMatchMessage = "Finding Match";
public static readonly string RequestMatchmakingMessage = "Requesting";
public static readonly string CancelMatchmakingMessage = "Canceling";
public static readonly string MatchmakingCanceledErrorMessage = "Canceled";
public static readonly string MatchmakingErrorMessage = "Connection failed. Make sure the matchmaking server is running, reachable, and the address and port is set properly.";
public static readonly string MatchmakingInvalidPayloadErrorMessage = "Received invalid payload format from matchmaking server. Make sure you are running a compatible version."; -
マッチメイキングペイロードを解析するための構造体と列挙型。
public enum MatchmakerPayloadType
{
OnFindingMatch,
OnMatchFound,
OnServerReady,
OnServerClaimFailed
}
public class MatchmakerPayload
{
[JsonProperty(Required = Required.Always)]
public MatchmakerPayloadType type { get; set; }
[JsonProperty(Required = Required.Always)]
public string message { get; set; }
} -
マッチメーカー URL を取得するヘルパー関数。
-CustomMatchmakingUrl=<your_url>起動パラメータを使用して URL を上書きできます。デフォルト値は localhostws://127.0.0.1:8080です。public static string GetMatchmakerUrl()
{
// Get from launch parameter first.
string customMatchmakerUrl = TutorialModuleUtil.GetLaunchParamValue($"-{CustomMatchmakerConfigKey}=");
// Get from config file if laucnh params are empty.
if (ConfigurationReader.Config != null)
{
if (string.IsNullOrEmpty(customMatchmakerUrl))
{
customMatchmakerUrl = ConfigurationReader.Config.AMSModuleConfiguration.customMatchmakingUrl;
}
}
// Parse localhost if any, because it is not supported by Unity's networking.
return Utilities.TryParseLocalHostUrl(customMatchmakerUrl);
}
カスタムマッチメイキングの実装
Byte Wars はデスクトップと WebGL の両方をサポートするように構築されているため、2 種類の WebSocket を使用しています。デスクトップには .NET の NativeWebSocket.WebSocket を使用します。WebGL については、Unity が WebGL 用の WebSocket を公式にサポートしていないため、Unity の公式 GitHub リポジトリにあるコミュニティ貢献による Unity Netcode WebSocket の Netcode.Transports.WebSocket.IWebSocketClient を使用しています。
-
CustomMatchmakingWrapper_Starterクラスを開き、以下の WebSocket 変数を宣言します。#if !UNITY_WEBGL
// Use native WebSocket.
private WebSocket nativeWebSocket;
#else
// Use Unity Netcode's WebSocket that supports WebGL.
private IWebSocketClient netcodeWebSocket;
#endif -
マッチメイキングイベントをブロードキャストするために、以下のデリゲートを宣言します。このデリゲートは後で UI で使用します。
public Action OnMatchmakingStarted;
public Action<WebSocketCloseCode /*closeCode*/, string /*closeMessage*/> OnMatchmakingStopped;
public Action<CustomMatchmakingModels.MatchmakerPayload /*payload*/> OnMatchmakingPayload;
public Action<string /*serverIp*/, ushort /*serverPort*/> OnMatchmakingServerReady;
public Action<string /*errorMessage*/> OnMatchmakingError; -
次に、以下のヘルパー変数を宣言して、先ほど宣言したデリゲートを介してブロードキャストする前に、マッチメイキング WebSocket のクローズメッセージを追跡します。
private string pendingCloseMessage = string.Empty; -
次に、WebSocket 経由でサンプルマッチメイキングバックエンドサービスに接続してマッチメイキングを開始する新しい関数を作成します。以下のコードでは、プラットフォームに応じて
NativeWebSocket.WebSocketとNetcode.Transports.WebSocket.IWebSocketClientを切り替えます。WebSocket コールバック関数はまだ定義されていないことに注意してください。後で作成します。public void StartMatchmaking()
{
BytewarsLogger.Log("Start matchmaking.");
pendingCloseMessage = string.Empty;
string matchmakerUrl = CustomMatchmakingModels.GetMatchmakerUrl();
#if !UNITY_WEBGL
nativeWebSocket = new WebSocket(matchmakerUrl);
nativeWebSocket.OnOpen += OnMatchmakerOpen;
nativeWebSocket.OnClose += (int closeCode) =>
{
OnMatchmakerClosed(
Enum.IsDefined(typeof(WebSocketCloseCode), closeCode) ?
(WebSocketCloseCode)closeCode :
WebSocketCloseCode.Undefined);
};
nativeWebSocket.OnMessage += (byte[] data) =>
{
OnMatchmakerPayload(data);
};
nativeWebSocket.OnError += OnMatchmakerError;
nativeWebSocket.Connect();
#else
netcodeWebSocket = WebSocketClientFactory.Create(
useSecureConnection: false,
url: matchmakerUrl,
username: string.Empty,
password: string.Empty);
netcodeWebSocket.Connect();
#endif
} -
次に、サンプルマッチメイキングバックエンドサービスからの WebSocket 接続を閉じてマッチメイキングをキャンセルする新しい関数を作成します。同様に、以下のコードはプラットフォームに応じて
NativeWebSocket.WebSocketとNetcode.Transports.WebSocket.IWebSocketClientを切り替えます。public void CancelMatchmaking(bool isIntentional)
{
// Use generic error message if the cancelation is intentional.
if (isIntentional)
{
pendingCloseMessage = CustomMatchmakingModels.MatchmakingCanceledErrorMessage;
}
#if !UNITY_WEBGL
if (nativeWebSocket == null)
{
BytewarsLogger.LogWarning("Cannot cancel matchmaking. WebSocket to matchmaker is null.");
OnMatchmakerClosed(WebSocketCloseCode.Normal);
return;
}
#else
if (netcodeWebSocket == null)
{
BytewarsLogger.LogWarning("Cannot cancel matchmaking. WebSocket to matchmaker is null.");
OnMatchmakerClosed(WebSocketCloseCode.Normal);
return;
}
#endif
BytewarsLogger.Log("Cancel matchmaking.");
#if !UNITY_WEBGL
nativeWebSocket.Close();
#else
netcodeWebSocket.Close();
#endif
} -
次に、サンプルマッチメイキングバックエンドサービスから受信した WebSocket イベントをポーリングする新しい関数を作成します。同様に、以下のコードはプラットフォームに応じて
NativeWebSocket.WebSocketとNetcode.Transports.WebSocket.IWebSocketClientを切り替えます。WebSocket コールバック関数はまだ定義されていないことに注意してください。後で作成します。private void PollMatchmakerEvent()
{
#if !UNITY_WEBGL
if (nativeWebSocket == null)
{
return;
}
nativeWebSocket.DispatchMessageQueue();
#else
if (netcodeWebSocket == null)
{
return;
}
WebSocketEvent pollEvent = netcodeWebSocket.Poll();
if (pollEvent != null)
{
switch (pollEvent.Type)
{
case WebSocketEventType.Open:
OnMatchmakerOpen();
break;
case WebSocketEventType.Close:
OnMatchmakerClosed(
Enum.IsDefined(typeof(WebSocketCloseCode), (int)pollEvent.CloseCode) ?
(WebSocketCloseCode)pollEvent.CloseCode :
WebSocketCloseCode.Undefined);
break;
case WebSocketEventType.Payload:
OnMatchmakerPayload(pollEvent.Payload);
break;
case WebSocketEventType.Error:
OnMatchmakerError(pollEvent.Error);
break;
}
}
#endif
} -
次に、Unity の
Update()関数を作成し、PollMatchmakerEvent()関数を呼び出して、毎フレーム WebSocket イベントをポーリングします。private void Update()
{
PollMatchmakerEvent();
} -
サンプルマッチメイキングバックエンドサービスの WebSocket 接続が開いたときに処理する新しいコールバック関数を作成します。この関数は、先ほど作成したデリゲートを単純にブロードキャストします。
private void OnMatchmakerOpen()
{
BytewarsLogger.Log("Connected to matchmaker.");
OnMatchmakingStarted?.Invoke();
} -
次に、サンプルマッチメイキングバックエンドサービスの WebSocket 接続が閉じたときに処理する新しいコールバック関数を作成します。この関数は WebSocket 変数をリセットし、先ほど作成した適切な WebSocket クローズメッセージでデリゲートをブロードキャストします。
private void OnMatchmakerClosed(WebSocketCloseCode closeCode)
{
#if !UNITY_WEBGL
nativeWebSocket = null;
#else
netcodeWebSocket = null;
#endif
// Store and clear the pending close message.
string closeMessage = pendingCloseMessage;
pendingCloseMessage = string.Empty;
/* Handle WebSocket close conditions:
* If closed normally with a close message, set the close code to undefined.
* If closed abnormally, use the generic error message.
* Otherwise, retain the WebSocket's native close code and message. */
if (closeCode == WebSocketCloseCode.Normal && !string.IsNullOrEmpty(closeMessage))
{
closeCode = WebSocketCloseCode.Undefined;
}
else if (closeCode != WebSocketCloseCode.Normal)
{
closeMessage = CustomMatchmakingModels.MatchmakingErrorMessage;
}
BytewarsLogger.Log($"Disconnected from matchmaker. Close code: {closeCode}. Close message: {closeMessage}");
OnMatchmakingStopped?.Invoke(closeCode, closeMessage);
} -
次に、サンプルマッチメイキングバックエンドサービスの WebSocket 接続エラーが発生したときに処理する新しいコールバック関数を作成します。この関数は WebSocket 変数をリセットし、WebSocket エラーメッセージをクローズメッセージ変数に保存し、適切なデリゲートをブロードキャストします。
private void OnMatchmakerError(string errorMessage)
{
BytewarsLogger.Log($"Connection to matchmaker error. Error: {errorMessage}");
#if !UNITY_WEBGL
nativeWebSocket = null;
#else
netcodeWebSocket = null;
#endif
pendingCloseMessage = errorMessage;
OnMatchmakingError?.Invoke(errorMessage);
} -
次に、ゲームがサンプルマッチメイキングバックエンドサービスからペイロードを受信したときに処理する新しいコールバック関数を作成します。この関数はペイロードが有効かどうかをチェックします。ペイロードが有効な場合、関数はデリゲートを介してそれをブロードキャストします。ペイロードが無効な場合、関数はマッチメイキングプロセスをキャンセルします。さらに、ペイロードがサーバーの準備ができていることを示している場合、関数はゲームクライアントをゲームサーバーに接続する別の関数を呼び出します。これは後で作成します。
private void OnMatchmakerPayload(byte[] bytes)
{
// Get the payload.
string payloadStr = System.Text.Encoding.UTF8.GetString(bytes);
if (string.IsNullOrEmpty(payloadStr))
{
BytewarsLogger.LogWarning("Cannot handle matchmaker payload. Payload is null.");
return;
}
// Try parse the payload.
CustomMatchmakingModels.MatchmakerPayload payload = null;
try
{
/* Configure settings to prevent enum deserialization from integer values.
* The enum value must match the string representation defined in the class.*/
JsonSerializerSettings settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new StringEnumConverter { AllowIntegerValues = false } },
NullValueHandling = NullValueHandling.Ignore
};
payload = JsonConvert.DeserializeObject<CustomMatchmakingModels.MatchmakerPayload>(payloadStr, settings);
}
catch (Exception e)
{
BytewarsLogger.LogWarning($"Cannot handle matchmaker payload. Unable to parse payload. Error: {e.Message}");
payload = null;
}
// Abort matchmaking if payload is invalid.
if (payload == null)
{
BytewarsLogger.LogWarning("Cannot handle matchmaker payload. Matchmaker payload is null.");
pendingCloseMessage = CustomMatchmakingModels.MatchmakingInvalidPayloadErrorMessage;
CancelMatchmaking(isIntentional: false);
return;
}
// Broadcast the payload.
BytewarsLogger.Log($"Received payload from matchmaker: {payloadStr}");
OnMatchmakingPayload?.Invoke(payload);
// Travel to the server.
if (payload.type == CustomMatchmakingModels.MatchmakerPayloadType.OnServerReady)
{
OnMatchmakerServerReady(payload.message);
}
} -
サーバーの準備ができると、サンプルマッチメイキングバックエンドサービスは
IPaddress:port形式のサーバー情報を含むペイロードをゲームクライアントに送信します。したがって、以下の新しいヘルパー関数を作成してこのメッセージを解析する必要があります:private bool TryParseServerInfo(
string serverInfoToParse,
out string serverIp,
out ushort serverPort)
{
serverIp = null;
serverPort = 0;
if (string.IsNullOrWhiteSpace(serverInfoToParse))
{
BytewarsLogger.LogWarning("Server info is empty.");
return false;
}
// Try to split server Ip and port.
string[] serverAddressParts = serverInfoToParse.Split(':');
if (serverAddressParts.Length != 2)
{
BytewarsLogger.LogWarning("Server info does not contain Ip address and port.");
return false;
}
// Try to parse server Ip.
if (!IPAddress.TryParse(serverAddressParts[0], out _) &&
Uri.CheckHostName(serverAddressParts[0]) == UriHostNameType.Unknown)
{
BytewarsLogger.LogWarning("Server info has invalid Ip address.");
return false;
}
// Try to parse server port.
if (!ushort.TryParse(serverAddressParts[1], out serverPort))
{
BytewarsLogger.LogWarning("Server info has invalid port.");
return false;
}
// Parse localhost if any, because it is not supported by Unity's networking.
serverIp = Utilities.TryParseLocalHostUrl(serverAddressParts[0]);
return true;
} -
最後に、サーバー準備完了ペイロードを処理する新しい関数を作成します。この関数はサーバー情報を解析し、サーバーの IP アドレスとポートを使用してゲームクライアントをゲームサーバーに接続します。
private void OnMatchmakerServerReady(string serverInfo)
{
// Travel to the server if the server info is valid.
if (TryParseServerInfo(serverInfo, out string serverIp, out ushort serverPort))
{
BytewarsLogger.Log($"Server info found. Start traveling to {serverIp}:{serverPort}.");
OnMatchmakingServerReady?.Invoke(serverIp, serverPort);
GameManager.Instance.ShowTravelingLoading(() =>
{
GameManager.Instance.StartAsClient(serverIp, serverPort, CustomMatchmakingModels.DefaultGameMode);
},
CustomMatchmakingModels.TravelingMessage);
}
else
{
BytewarsLogger.LogWarning($"Cannot travel to server. Unable to parse server info Ip address and port.");
pendingCloseMessage = CustomMatchmakingModels.MatchmakingInvalidPayloadErrorMessage;
CancelMatchmaking(isIntentional: false);
}
}
リソース
-
このチュートリアルセクションで使用されているファイルは、Unity Byte Wars GitHub リポジトリで入手できます。