専用サーバーでのマッチメイキングに SDK を使用する - 専用サーバーでのクイックマッチ - (Unity モジュール)
注釈:本資料はAI技術を用いて翻訳されています。
ラッパーを開く
このチュートリアルでは、AccelByte Gaming Services (AGS) Software Development Kit (SDK) を使用して AGS マッチメイキングを実装する方法を学びます。Byte Wars では、ゲームインスタンスラッパーが MatchmakingDSWrapper クラスと MatchmakingDSServerWrapper クラスで定義されています。これらのラッパーは、マッチメイキングの開始やキャンセルなど、マッチメイキング関連のアクションを管理します。このチュートリアルでは、MatchmakingDSWrapper クラスと MatchmakingDSServerWrapper クラスのスターターバージョンである MatchmakingDSWrapper_Starter クラスと MatchmakingDSServerWrapper_Starter クラスを使用します。
スターターパックの内容
このチュートリアルを進めるには、ゲームクライアント用の MatchmakingDSWrapper_Starter というスターターラッパークラスと、サーバー用の MatchmakingDSServerWrapper_Starter クラスを使用します。これらのクラスは、以下のファイルで定義されている MatchmakingEssentialsWrapper クラスを継承しています。
- C# ファイル:
Assets/Resources/Modules/Play/MatchmakingEssentials/Scripts/MatchmakingEssentialsWrapper.cs
MatchmakingDSWrapper_Starter クラスと MatchmakingDSServerWrapper_Starter クラスは、以下のファイルで定義されています。
- C# ファイル:
Assets/Resources/Modules/Play/MatchmakingDS/Scripts/MatchmakingDSWrapper_Starter.cs - C# ファイル:
Assets/Resources/Modules/Play/MatchmakingDS/Scripts/MatchmakingDSServerWrapper_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;
#if UNITY_SERVER
protected static ServerDSHub ServerDSHub;
protected static ServerMatchmakingV2 ServerMatchmaking;
#endif
protected override void Awake()
{
base.Awake();
Matchmaking ??= AccelByteSDK.GetClientRegistry().GetApi().GetMatchmakingV2();
#if UNITY_SERVER
ServerDSHub ??= AccelByteSDK.GetServerRegistry().GetApi().GetDsHub();
ServerMatchmaking ??= AccelByteSDK.GetServerRegistry().GetApi().GetMatchmakingV2();
#endif
} -
マッチメイキングの状態を処理するためのデリゲート:
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 { };
public static ResultCallback<SessionV2DsStatusUpdatedNotification> OnDSStatusChanged = delegate { };
MatchmakingEssentialsWrapper のルート親クラスである AccelByteWarsOnlineSession クラスにも、以下を含むいくつかの事前定義されたヘルパーコンポーネントが含まれています。
-
ゲームクライアントを専用サーバーに接続するためのヘルパーメソッド。
public virtual void TravelToDS(SessionV2GameSession session, InGameMode gameMode)
{
SessionV2DsInformation dsInfo = session.dsInformation;
if (dsInfo == null)
{
BytewarsLogger.LogWarning("Failed to travel to dedicated server. Dedicated server information not found.");
return;
}
if (NetworkManager.Singleton.IsListening)
{
BytewarsLogger.LogWarning("Failed to travel to dedicated server. The instance is running as listen server.");
return;
}
string ip = dsInfo.server.ip;
ushort port = (ushort)dsInfo.server.port;
InitialConnectionData initialData = new InitialConnectionData()
{
sessionId = string.Empty,
inGameMode = gameMode,
serverSessionId = session.id,
userId = GameData.CachedPlayerState.PlayerId
};
GameManager.Instance.ShowTravelingLoading(() =>
{
BytewarsLogger.Log("Travel to dedicated server as client.");
GameManager.Instance.StartAsClient(ip, port, initialData);
});
}
AccelByteWarsOnlineSessionModels ファイルには、以下を含むいくつかの事前定義されたヘルパーコンポーネントが含まれています。
-
Admin Portal で設定されたセッションテンプレートとマッチプールに基づいて、セッションテンプレート名を格納する文字列定数:
public const string EliminationDSAMSSessionTemplateName = "unity-elimination-ds-ams";
public const string TeamDeathmatchDSAMSSessionTemplateName = "unity-teamdeathmatch-ds-ams"; -
セッションテンプレート名に基づいて、ゲーム内モードをセッションリクエストモデルにマッピングするヘルパーディクショナリ:
public static readonly Dictionary<InGameMode, Dictionary<GameSessionServerType, SessionV2GameSessionCreateRequest>> SessionCreateRequestModels = new()
{
...
{
InGameMode.MatchmakingElimination, new()
{
...
{
GameSessionServerType.DedicatedServerAMS,
new SessionV2GameSessionCreateRequest()
{
type = SessionConfigurationTemplateType.DS,
joinability = SessionV2Joinability.OPEN,
configurationName = EliminationDSAMSSessionTemplateName,
matchPool = EliminationDSAMSSessionTemplateName,
clientVersion = ClientVersion
}
}
}
},
{
InGameMode.MatchmakingTeamDeathmatch, new()
{
...
{
GameSessionServerType.DedicatedServerAMS,
new SessionV2GameSessionCreateRequest()
{
type = SessionConfigurationTemplateType.DS,
joinability = SessionV2Joinability.OPEN,
configurationName = TeamDeathmatchDSAMSSessionTemplateName,
matchPool = TeamDeathmatchDSAMSSessionTemplateName,
clientVersion = ClientVersion
}
}
}
},
...
}; -
ゲーム内モードでセッションリクエストモデルを取得するヘルパー関数:
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;
} -
マッチチケットパラメータに追加の属性を追加するための文字列定数。
public static readonly string ServerNameAttributeKey = "server_name";
public static readonly string ClientVersionAttributeKey = "client_version";
MatchmakingEssentialsModels ファイルには、以下を含むいくつかの事前定義されたヘルパーコンポーネントが含まれています。
-
マッチメイキング開始時にマッチチケット結果を解析するための文字列定数。これを使用して、マッチメイキングの開始を再試行するかどうかを決定します。
public static readonly string MatchTicketIdAttributeKey = "ticketID";
マッチメイキングを開始する
このセクションでは、AGS SDK を使用してマッチメイキングを開始する方法について説明します。
-
MatchmakingDSWrapper_Starterクラスを開き、以下の関数を作成します。この関数は、マッチメイキングを開始する前に、アクティブなセッション(存在する場合)から退出します。マッチメイキングが正常に開始されると、バックエンドは新しいマッチチケットとマッチチケット ID を作成します。これを使用してマッチメイキングをキャンセルできます。プレイヤーは 1 つのマッチチケット ID しか持つことができないため、マッチチケット ID の競合によりマッチメイキングの開始に失敗した場合、この関数は最初に既存のチケットをキャンセルしてから、マッチメイキングの開始を再試行します。リクエストが完了すると、割り当てられたデリゲートを呼び出します。以下のコードから、MatchmakingV2CreateTicketRequestOptionalParamsのオプションパラメータにいくつかの値を追加することでリクエストを強化していることがわかります。これらのパラメータの機能は次のとおりです。latencies: リクエストされたリージョンを設定して、セッションが特定のリージョンの AMS 専用サーバーを使用し、レイテンシに基づいて最も近いものを選択するようにします。sessionId: パーティーメンバーがマッチメイキングに参加できるように、パーティーセッション ID を追加します。attributes: マッチチケットに追加できる追加の属性。この場合、以下の値が追加されます。server_name:ServerNameAttributeKey変数からの値。この属性は、ローカル専用サーバーでゲームをテストするために、リクエストされたローカルサーバー名を設定します。client_version:ClientVersionAttributeKey変数からの値。この属性は、AMS 開発フリートを使用してテストするためのビルド設定名を選択します。
public override void StartMatchmaking(
string matchPool,
ResultCallback<MatchmakingV2CreateTicketResponse> onComplete)
{
// Leave the existing session before starting matchmaking.
if (CachedSession != null)
{
LeaveGameSession(CachedSession.id, (leaveResult) =>
{
// Abort only if there's an error and it's not due to a missing session.
if (leaveResult.IsError && leaveResult.Error.Code != ErrorCode.SessionIdNotFound)
{
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 local server name.
if (!string.IsNullOrEmpty(ConnectionHandler.LocalServerName))
{
optionalParams.attributes.Add(ServerNameAttributeKey, ConnectionHandler.LocalServerName);
}
// Add client version.
optionalParams.attributes.Add(ClientVersionAttributeKey, ClientVersion);
// Add preferred regions.
Dictionary<string, int> preferredRegions =
RegionPreferencesModels.GetEnabledRegions().ToDictionary(x => x.RegionCode, y => (int)y.Latency);
if (preferredRegions.Count > 0)
{
optionalParams.latencies = preferredRegions;
}
// 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($"Successfully started 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 variables are null.
string messageVariablesJson = startResult.Error.messageVariables?.ToJsonString();
if (string.IsNullOrEmpty(messageVariablesJson))
{
OnMatchmakingStarted?.Invoke(startResult);
onComplete?.Invoke(startResult);
return;
}
// Abort if the message variables do not contain the 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 を使用してマッチメイキングをキャンセルする方法について説明します。
-
MatchmakingDSWrapper_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($"Successfully canceled matchmaking. Match ticket ID: {matchTicketId}");
}
OnMatchmakingCanceled?.Invoke(result);
onComplete?.Invoke(result);
});
}
マッチメイキングイベントをリッスンする
このセクションでは、マッチメイキングイベントをリッスンする方法について説明します。
-
MatchmakingDSWrapper_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);
Lobby.SessionV2DsStatusChanged += (result) => OnDSStatusChanged?.Invoke(result);
} -
以下の関数を作成します。この関数は、親クラスの関数を呼び出してセッション参加リクエストを送信します。これは、セッション入門モジュールで学習した内容です。リクエストが完了すると、専用サーバーが移動する準備ができているかどうかを確認します。
public override void JoinGameSession(
string sessionId,
ResultCallback<SessionV2GameSession> onComplete)
{
// Reregister delegate to listen for dedicated server status changed event.
OnDSStatusChanged -= OnDSStatusChangedReceived;
OnDSStatusChanged += OnDSStatusChangedReceived;
base.JoinGameSession(sessionId, (result) =>
{
// If dedicated server is ready, broadcast the dedicated server status changed event.
if (!result.IsError && result.Value.dsInformation.StatusV2 == SessionV2DsStatus.AVAILABLE)
{
OnDSStatusChanged?.Invoke(Result<SessionV2DsStatusUpdatedNotification>.CreateOk(new()
{
session = result.Value,
sessionId = result.Value.id,
error = string.Empty
}));
}
onComplete?.Invoke(result);
});
} -
次に、専用サーバーのステータスが受信されたときに処理する新しい関数を作成します。専用サーバーの準備ができている場合、ゲームクライアントはそこに移動します。それ以外の場合は、ステータスタイプに基づいてエラーをスローします。
private void OnDSStatusChangedReceived(Result<SessionV2DsStatusUpdatedNotification> result)
{
if (result.IsError)
{
OnDSStatusChanged -= OnDSStatusChangedReceived;
BytewarsLogger.LogWarning(
$"Failed to handle dedicated server status changed event. " +
$"Error {result.Error.Code}: {result.Error.Message}");
return;
}
SessionV2GameSession session = result.Value.session;
SessionV2DsInformation dsInfo = session.dsInformation;
// Check if the requested game mode is supported.
InGameMode requestedGameMode = GetGameSessionGameMode(session);
if (requestedGameMode == InGameMode.None)
{
BytewarsLogger.LogWarning(
$"Failed to handle dedicated server status changed event. " +
$"Session's game mode is not supported by the game.");
OnDSStatusChanged -= OnDSStatusChangedReceived;
OnDSStatusChanged?.Invoke(
Result<SessionV2DsStatusUpdatedNotification>.
CreateError(ErrorCode.NotAcceptable, InvalidSessionTypeMessage));
return;
}
// Check the dedicated server status.
switch (dsInfo.StatusV2)
{
case SessionV2DsStatus.AVAILABLE:
OnDSStatusChanged -= OnDSStatusChangedReceived;
TravelToDS(session, requestedGameMode);
break;
case SessionV2DsStatus.FAILED_TO_REQUEST:
case SessionV2DsStatus.ENDED:
case SessionV2DsStatus.UNKNOWN:
OnDSStatusChanged -= OnDSStatusChangedReceived;
BytewarsLogger.LogWarning(
$"Failed to handle dedicated server status changed event. " +
$"Session failed to request for dedicated server due to unknown reason.");
break;
default:
BytewarsLogger.Log($"Received dedicated server status change. Status: {dsInfo.StatusV2}");
break;
}
}
バックフィルを処理する
マッチメイキング時に、ゲームサーバーがセッションによって要求されている可能性がありますが、セッションテンプレートで設定した最大プレイヤー数に基づいて、他のプレイヤーが参加するための空きスロットがまだある場合があります。これらの空きスロットを埋めるために、ゲームサーバーのバックフィルを実装できます。このセクションでは、ゲームサーバーのバックフィルを実装する方法について説明します。
-
MatchmakingDSServerWrapper_Starterクラスを開き、以下のコードを追加してバックフィルを受け入れる関数を作成します。private void AcceptBackfillProposal(MatchmakingV2BackfillProposalNotification proposal)
{
ServerMatchmaking.AcceptBackfillProposal(proposal, (result) =>
{
if (result.IsError)
{
BytewarsLogger.LogWarning($"Failed to accept backfill proposal. Error {result.Error.Code}: {result.Error.Message}");
}
else
{
BytewarsLogger.Log($"Successfully accepted backfill proposal. Session ID: {result.Value.id}. Backfill Ticket ID: {result.Value.backfillTicketId}");
}
});
} -
次に、以下のコードを追加してバックフィルを拒否する関数を作成します。
private void RejectBackfillProposal(MatchmakingV2BackfillProposalNotification proposal, bool stopBackfilling)
{
ServerMatchmaking.RejectBackfillProposal(proposal, stopBackfilling, (result) =>
{
if (result.IsError)
{
BytewarsLogger.LogWarning($"Failed to reject backfill proposal. Error {result.Error.Code}: {result.Error.Message}");
}
else
{
BytewarsLogger.Log($"Successfully rejected backfill proposal. Stop backfilling: {stopBackfilling}");
}
});
} -
サーバーにプレイヤーが参加するための空きスロットがまだある場合、バックエンドはゲームサーバーにバックフィル提案を送信します。以下のコードを追加して、この提案を処理する関数を作成します。Byte Wars では、ゲームがまだ開始されていない場合はバックフィルを受け入れます。それ以外の場合は、提案を拒否し、バックエンドにゲームサーバーのバックフィルを停止するよう要求します。
private void OnBackfillProposalReceived(Result<MatchmakingV2BackfillProposalNotification> result)
{
if (result.IsError)
{
BytewarsLogger.LogWarning($"Failed to handle backfill proposal. Error {result.Error.Code}: {result.Error.Message}");
return;
}
// If gameplay has not yet started, accept the backfill proposal.
if (GameManager.Instance.InGameState == InGameState.None)
{
AcceptBackfillProposal(proposal: result.Value);
}
// Otherwise, reject the proposal.
else
{
RejectBackfillProposal(proposal: result.Value, stopBackfilling: true);
}
} -
最後に、事前定義された
Awake()関数を以下のコードに置き換えて、上記の関数をバインドし、バックフィル提案イベントをリッスンします。protected override void Awake()
{
base.Awake();
ServerDSHub.MatchmakingV2BackfillProposalReceived += OnBackfillProposalReceived;
}
リソース
-
このチュートリアルで使用されているファイルは、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/MatchmakingDS/Scripts/MatchmakingDSWrapper_Starter.cs
- Assets/Resources/Modules/Play/MatchmakingDS/Scripts/MatchmakingDSServerWrapper_Starter.cs