ピアツーピアマッチメイキングにSDKを使用する - ピアツーピアでのクイックマッチ - (Unityモジュール)
注釈:本資料はAI技術を用いて翻訳されています。
AccelByte Gaming Services (AGS) SDK for UnityはWebGL上でP2Pをサポートしていません。このモジュールはWebGLビルドでは使用できません。
ラッパーを開く
このチュートリアルでは、AccelByte Gaming Services (AGS) Software Development Kit (SDK)を使用してAGSマッチメイキングを実装する方法を学びます。Byte Warsでは、ゲームインスタンスラッパーがMatchmakingP2PWrapperクラスで定義されています。このラッパーは、マッチメイキングの開始やキャンセルなどのマッチメイキング関連のアクションを管理します。このチュートリアルでは、MatchmakingP2PWrapperクラスのスターターバージョンであるMatchmakingP2PWrapper_Starterクラスを使用します。
スターターパックの内容
このチュートリアルに従うには、MatchmakingP2PWrapper_Starterというスターターラッパークラスを使用します。このクラスは、以下のファイルで定義されているMatchmakingEssentialsWrapperクラスを継承しています。
- C#ファイル:
Assets/Resources/Modules/Play/MatchmakingEssentials/Scripts/MatchmakingEssentialsWrapper.cs
MatchmakingP2PWrapper_Starterクラスは以下のファイルで定義されています。
- C#ファイル:
Assets/Resources/Modules/Play/MatchmakingP2P/Scripts/MatchmakingP2PWrapper_Starter.cs
さらに、以下のファイルで定義されているヘルパー関数と変数を含むモデルクラスがあります。
- C#ファイル:
Assets/Resources/Modules/Play/OnlineSessionUtils/Scripts/AccelByteWarsOnlineSessionModels.cs - C#ファイル:
Assets/Resources/Modules/Play/MatchmakingEssentials/Scripts/MatchmakingEssentialsModels.cs
MatchmakingEssentialsWrapper自体はSessionEssentialsWrapperクラスを継承しており、セッションへの参加、退出、セッション招待の拒否などの基本的なセッション管理機能が含まれています。この機能の実装方法については、前のセッション入門モジュールで学びました。MatchmakingEssentialsWrapperには、以下を含むいくつかの事前定義されたヘルパーコンポーネントが含まれています。
-
AGS SDKインターフェースを参照するためのヘルパー変数。これらの変数は初期化時に割り当てられます。
protected static MatchmakingV2 Matchmaking;
protected override void Awake()
{
base.Awake();
Matchmaking ??= AccelByteSDK.GetClientRegistry().GetApi().GetMatchmakingV2();
} -
マッチメイキングの状態を処理するためのデリゲート:
public static ResultCallback<MatchmakingV2CreateTicketResponse> OnMatchmakingStarted = delegate { };
public static ResultCallback<MatchmakingV2MatchFoundNotification> OnMatchFound = delegate { };
public static ResultCallback OnMatchmakingCanceled = delegate { };
public static ResultCallback<MatchmakingV2TicketExpiredNotification> OnMatchmakingExpired = delegate { };
public static ResultCallback<SessionV2GameInvitationNotification> OnSessionInviteReceived = delegate { };
MatchmakingEssentialsWrapperのルート親クラスであるAccelByteWarsOnlineSessionクラスにも、以下を含むいくつかの事前定義されたヘルパーコンポーネントが含まれています。
-
ピアツーピアホストを起動し、ゲームクライアントをピアツーピアホストに接続するためのヘルパーメソッド。AccelByte SDKを使用してP2Pネットワーキングを統合するには、ネットワークトランスポートとして
AccelByteNetworkTransportManagerプラグインを使用する必要があります。public virtual void TravelToP2PHost(SessionV2GameSession session, InGameMode gameMode)
{
AccelByteNetworkTransportManager transportManager = NetworkManager.Singleton.GetComponent<AccelByteNetworkTransportManager>();
if (transportManager == null)
{
transportManager = NetworkManager.Singleton.gameObject.AddComponent<AccelByteNetworkTransportManager>();
transportManager.Initialize(AccelByteSDK.GetClientRegistry().GetApi());
transportManager.OnTransportEvent += GameManager.Instance.OnTransportEvent;
}
InitialConnectionData initialData = new InitialConnectionData()
{
inGameMode = gameMode,
serverSessionId = session.id,
userId = GameData.CachedPlayerState.PlayerId
};
NetworkManager.Singleton.NetworkConfig.ConnectionData = GameUtility.ToByteArray(initialData);
NetworkManager.Singleton.NetworkConfig.NetworkTransport = transportManager;
bool isHost = session.leaderId == GameData.CachedPlayerState.PlayerId;
GameManager.Instance.ShowTravelingLoading(() =>
{
GameManager.Instance.ResetCache();
GameData.ServerType = ServerType.OnlinePeer2Peer;
if (isHost)
{
BytewarsLogger.Log("Start as P2P host");
GameData.ServerSessionID = session.id;
NetworkManager.Singleton.StartHost();
}
else
{
BytewarsLogger.Log($"Start as P2P client. Target host: {session.leaderId}");
transportManager.SetTargetHostUserId(session.leaderId);
NetworkManager.Singleton.StartClient();
}
},
isHost ? StartingAsHostMessage : WaitingHostMessage);
}
AccelByteWarsOnlineSessionModelsファイルには、以下を含むいくつかの事前定義されたヘルパーコンポーネントが含まれています。
-
管理ポータルで設定されたセッションテンプレートとマッチプールに基づいて、セッションテンプレート名を保存するための文字列定数:
public const string EliminationP2PSessionTemplateName = "unity-elimination-p2p";
public const string TeamDeathmatchP2PSessionTemplateName = "unity-teamdeathmatch-p2p"; -
セッションテンプレート名に基づいて、ゲーム内モードをセッションリクエストモデルにマッピングするヘルパー辞書:
public static readonly Dictionary<InGameMode, Dictionary<GameSessionServerType, SessionV2GameSessionCreateRequest>> SessionCreateRequestModels = new()
{
...
{
InGameMode.MatchmakingElimination, new()
{
...
{
GameSessionServerType.PeerToPeer,
new SessionV2GameSessionCreateRequest()
{
type = SessionConfigurationTemplateType.P2P,
joinability = SessionV2Joinability.OPEN,
configurationName = EliminationP2PSessionTemplateName,
matchPool = EliminationP2PSessionTemplateName,
}
},
...
}
},
{
InGameMode.MatchmakingTeamDeathmatch, new()
{
...
{
GameSessionServerType.PeerToPeer,
new SessionV2GameSessionCreateRequest()
{
type = SessionConfigurationTemplateType.P2P,
joinability = SessionV2Joinability.OPEN,
configurationName = TeamDeathmatchP2PSessionTemplateName,
matchPool = TeamDeathmatchP2PSessionTemplateName,
}
},
...
}
},
...
}; -
ゲーム内モードでセッションリクエストモデルを取得するためのヘルパー関数:
public static SessionV2GameSessionCreateRequest GetGameSessionRequestModel(
InGameMode gameMode,
GameSessionServerType serverType)
{
if (!SessionCreateRequestModels.TryGetValue(gameMode, out var matchTypeDict))
{
return null;
}
matchTypeDict.TryGetValue(serverType, out var request);
return request;
}
MatchmakingEssentialsModelsファイルには、以下を含むいくつかの事前定義されたヘルパーコンポーネントが含まれています。
-
マッチメイキング開始時にマッチチケット結果を解析するための文字列定数。これを使用して、マッチメイキングの開始を再試行するかどうかを決定します。
public static readonly string MatchTicketIdAttributeKey = "ticketID";
マッチメイキングを開始する
このセクションでは、AGS SDKを使用してマッチメイキングを開始する方法について説明します。
-
MatchmakingP2PWrapper_Starterクラスを開き、以下の関数を作成します。この関数は、マッチメイキングを開始する前に、アクティブなセッション(存在する場合)から退出します。マッチメイキングが正常に開始されると、バックエンドは新しいマッチチケットとマッチチケットIDを作成します。これを使用してマッチメイキングをキャンセルできます。プレイヤーは1つのマッチチケットIDしか持つことができないため、競合するマッチチケットIDが原因でマッチメイキングの開始に失敗した場合、この関数は最初に既存のチケットをキャンセルしてからマッチメイキングの開始を再試行します。リクエストが完了すると、割り当てられたデリゲートを呼び出し、セッションをローカルにキャッシュします。以下のコードから、MatchmakingV2CreateTicketRequestOptionalParamsのオプションパラメータにいくつかの値を追加することでリクエストを強化していることがわかります。これらのパラメータの機能は次のとおりです。sessionId: パーティーメンバーがマッチメイキングに参加できるように、パーティーセッションIDを追加します。
public override void StartMatchmaking(
string matchPool,
ResultCallback<MatchmakingV2CreateTicketResponse> onComplete)
{
// If there is an existing session, leave it first.
if (CachedSession != null)
{
LeaveGameSession(CachedSession.id, (leaveResult) =>
{
if (leaveResult.IsError)
{
BytewarsLogger.LogWarning($"Failed to start matchmaking. Error {leaveResult.Error.Code}: {leaveResult.Error.Message}");
Result<MatchmakingV2CreateTicketResponse> errorResult = Result<MatchmakingV2CreateTicketResponse>.CreateError(leaveResult.Error);
OnMatchmakingStarted?.Invoke(errorResult);
onComplete?.Invoke(errorResult);
return;
}
StartMatchmaking(matchPool, onComplete);
});
return;
}
MatchmakingV2CreateTicketRequestOptionalParams optionalParams = new() { attributes = new() };
// Add party session id for playing with party feature.
if (CachedParty != null && !string.IsNullOrEmpty(CachedParty.id))
{
optionalParams.sessionId = CachedParty.id;
}
Matchmaking.CreateMatchmakingTicket(matchPool, optionalParams, (startResult) =>
{
// Matchmaking started successfully.
if (!startResult.IsError)
{
BytewarsLogger.Log($"Success to start matchmaking. Match ticket ID: {startResult.Value.matchTicketId}");
OnMatchmakingStarted?.Invoke(startResult);
onComplete?.Invoke(startResult);
return;
}
BytewarsLogger.LogWarning($"Failed to start matchmaking. Error {startResult.Error.Code}: {startResult.Error.Message}");
// Attempt recovery if conflict due to existing ticket. Otherwise, return the result.
if (startResult.Error.Code != ErrorCode.MatchmakingV2CreateMatchTicketConflict)
{
OnMatchmakingStarted?.Invoke(startResult);
onComplete?.Invoke(startResult);
return;
}
// Abort if the message variable is null.
string messageVariablesJson = startResult.Error.messageVariables?.ToJsonString();
if (string.IsNullOrEmpty(messageVariablesJson))
{
OnMatchmakingStarted?.Invoke(startResult);
onComplete?.Invoke(startResult);
return;
}
// Abort if the message variable does not contain conflicted match ticket attribute.
Dictionary<string, object> messageVariables = JsonConvert.DeserializeObject<Dictionary<string, object>>(messageVariablesJson);
if (messageVariables == null ||
!messageVariables.TryGetValue(MatchTicketIdAttributeKey, out var existingMatchTicketIdObj) ||
existingMatchTicketIdObj is not string existingMatchTicketId)
{
OnMatchmakingStarted?.Invoke(startResult);
onComplete?.Invoke(startResult);
return;
}
// Cancel existing ticket and retry.
CancelMatchmaking(existingMatchTicketId, cancelResult =>
{
if (cancelResult.IsError)
{
BytewarsLogger.LogWarning($"Failed to start matchmaking. Error {cancelResult.Error.Code}: {cancelResult.Error.Message}");
Result<MatchmakingV2CreateTicketResponse> errorResult = Result<MatchmakingV2CreateTicketResponse>.CreateError(cancelResult.Error);
OnMatchmakingStarted?.Invoke(errorResult);
onComplete?.Invoke(errorResult);
return;
}
StartMatchmaking(matchPool, onComplete);
});
});
}
マッチメイキングをキャンセルする
このセクションでは、AGS SDKを使用してマッチメイキングをキャンセルする方法について説明します。
-
MatchmakingP2PWrapper_Starterクラスを開き、以下の関数を作成します。この関数は、マッチチケットIDを削除するリクエストを送信して、マッチメイキングリクエストをキャンセルします。リクエストが完了すると、割り当てられたデリゲートを呼び出します。public override void CancelMatchmaking(
string matchTicketId,
ResultCallback onComplete)
{
Matchmaking.DeleteMatchmakingTicket(matchTicketId, (result) =>
{
if (result.IsError)
{
BytewarsLogger.LogWarning(
$"Failed to cancel matchmaking with ticket ID: {matchTicketId}. " +
$"Error {result.Error.Code}: {result.Error.Message}");
}
else
{
BytewarsLogger.Log($"Success to cancel matchmaking. Match ticket ID: {matchTicketId}");
}
OnMatchmakingCanceled?.Invoke(result);
onComplete?.Invoke(result);
});
}
マッチメイキングイベントをリッスンする
このセクションでは、マッチメイキングイベントをリッスンする方法について説明します。
-
MatchmakingP2PWrapper_Starterクラスを開き、事前定義されたAwake()関数を以下のコードに置き換えます。このコードは、マッチメイキングが期限切れになったとき、マッチメイキングがマッチするプレイヤーを見つけたとき、マッチが見つかった後にセッション招待を受信したときなどのイベントをリッスンするためにデリゲートをバインドします。protected override void Awake()
{
base.Awake();
Lobby.MatchmakingV2TicketExpired += (result) => OnMatchmakingExpired?.Invoke(result);
Lobby.MatchmakingV2MatchFound += (result) => OnMatchFound?.Invoke(result);
Lobby.SessionV2InvitedUserToGameSession += (result) => OnSessionInviteReceived?.Invoke(result);
} -
最後に、以下の関数を作成して、親クラスの関数を呼び出すことでセッション参加リクエストを送信します。これは、セッション入門モジュールで学んだ内容です。リクエストが完了すると、ピアツーピアホストが接続する準備ができているかどうかを確認します。
public override void JoinGameSession(
string sessionId,
ResultCallback<SessionV2GameSession> onComplete)
{
base.JoinGameSession(sessionId, (result) =>
{
if (!result.IsError)
{
InGameMode requestedGameMode = GetGameSessionGameMode(result.Value);
if (requestedGameMode == InGameMode.None)
{
BytewarsLogger.LogWarning($"Failed to travel to the P2P host. Session's game mode is not supported by the game.");
onComplete?.Invoke(Result<SessionV2GameSession>.CreateError(ErrorCode.NotAcceptable, InvalidSessionTypeMessage));
return;
}
TravelToP2PHost(result.Value, requestedGameMode);
}
onComplete?.Invoke(result);
});
}
リソース
-
このチュートリアルで使用されているファイルは、Unity Byte Wars GitHubリポジトリで入手できます。
- Assets/Resources/Modules/Play/SessionEssentials/Scripts/SessionEssentialsWrapper.cs
- Assets/Resources/Modules/Play/OnlineSessionUtils/Scripts/AccelByteWarsOnlineSession.cs
- Assets/Resources/Modules/Play/OnlineSessionUtils/Scripts/AccelByteWarsOnlineSessionModels.cs
- Assets/Resources/Modules/Play/MatchmakingEssentials/Scripts/MatchmakingEssentialsWrapper.cs
- Assets/Resources/Modules/Play/MatchmakingEssentials/Scripts/MatchmakingEssentialsModels.cs
- Assets/Resources/Modules/Play/MatchmakingP2P/Scripts/MatchmakingP2PWrapper_Starter.cs