GS2-SeasonRating

シーズンレーティング機能

プレイヤースキルでプレイヤーを分類し、より近い実力のプレイヤーとのマッチメイキングを実現するための機能です。 レーティング値によるマッチメイキング機能は GS2-Matchmaking に備わっていますが、近年増えている現実時間で数週間〜数ヶ月程度のシーズンを通してプレイヤーをランクづけする仕組みには対応していませんでした。

このマイクロサービスは、定めた期間をシーズンと捉え、シーズン期間に高いティアーを目指して対戦を繰り返すゲームサイクルを実現するために利用できます。 そのため、この機能で強さを表す指標はレート値ではなく、どのティアーに属しているかによって表現されます。

ティアー

ティアーは一般的にブロンズから始まり、勝ち進めることでシルバー、ゴールド、プラチナ…のように上がっていく仕様が一般的です。 ティアーを上げるには、同一ティアー内のプレイヤーと対戦し、順位によって変動する入手ポイントが閾値を超えると次のティアーに上がれます。 勝負に負けるとポイントは減ることがあり、ポイントが閾値を下回ると前のティアーに下がることがあります。

ティアーをまたぐ対戦

レーティング戦において、マッチメイキングのルールは原則的に同一ティアーで構成するべきです。 しかし、プレイヤーが不足するなどの理由で前後のティアーのプレイヤーを含んだ状態でマッチメイキングをする場合も各プレイヤーが属しているティアーの最小・最大変動量と順位を元にポイントの変動量を決定します。 低いティアーのプレイヤーが高いティアーのプレイヤーに勝った場合にボーナスポイントを加算する仕組みやその逆のような仕組みはありません。 本来異なる強さのプレイヤー同士を対戦させて細工で工夫をするより、各ティアーに最低限ゲームプレイを成立させるだけのプレイヤーが滞留するようにティアーの閾値設計を行うことを推奨します。

ポイント

変動範囲

各ティアーごとに、最下位の時のポイント減算量、最上位の時のポイント加算量を設定できます。 中間の順位をとった場合には、報告された順位のパターン数で等分し、ポイントの加減算量を決定します。

参加料

ティアーによって、ゲームに参加する際にポイントを消費する必要があるように設定が可能です。 これによって、連戦連勝もティアーを上がるために必要となる条件を表現することが可能です。 参加料の支払いは、ゲーム結果をサーバーに報告するための投票用紙を取得する際に支払います。

ランクアップボーナス

ポイントを加算してランクアップした際にボーナスポイントを加算できます。 これによって昇格後にすぐに降格するようなチャタリングを防止することができます。

ユーザーデータ管理

GS2-SeasonRating はポイントとランクを管理しません。 実際のユーザーデータの管理は GS2-Experience を使用します。

シーズンのマスターデータとして、シーズンのポイントを管理する GS2-Experience の経験値モデルを指定し、 プロパティIDにシーズンモデルのIDを指定して、 経験値にポイント、ランクにどのティアーに属しているかを管理します。 つまり、ポイントの増減量は GS2-SeasonRating のマスターデータで管理しますが、ポイントによってランクを決定する閾値の管理や、プレイヤーがどのティアーに属しているかといったユーザーデータは GS2-Experience が管理します。 これによって、シーズン終了後に特定のランクであればアイテムがもらえるような交換処理に GS2-Experience が提供するランク値の検証機能のような高度な機能が利用できます。

マッチセッション

レーティング戦を行うには、まずは GS2-SeasonRating にマッチセッションリソースを作成する必要があります。 GS2-Matchmaking にはマッチメイキング成立時に、マッチメイキング成立したギャザリング名でマッチセッションを作成する連携機能があります。 特に理由がなければこの方法でマッチセッションを作成してください。

マッチセッションの有効期限

マッチセッションには有効期限を秒単位で、最大24時間の範囲で指定できます。 この期間内に結果の投票を行う必要があり、最初の投票から5分が経過して全ての投票が行われない場合はその段階で結果の集計が行われます。

結果の投票

マッチメイキングが完了したら、各プレイヤーはマッチセッションから投票用紙を取得します。 投票用紙を使用して、結果の投票を行います。 投票の内容には対戦に参加したプレイヤーのユーザーIDと順位のリストを渡します。

投票結果の分断

サーバーが受け付けた投票の多数決を取ろうとした時に、結果が同数で最終的な結果を確定できない場合はレートの計算が行われません。 そのため、1vs1 のゲームでは正しいレート値を求めるのは困難です。 この問題を解決するためには、裏で対戦には直接関わらない3人目のプレイヤーをマッチメイキングし、そのプレイヤーに第三者視点で投票してもらうといった工夫が必要となります。

実装例

現在のポイントやランクを取得

GS2-Experience のAPIを利用してステータスを取得してください。 「NamespaceName」「ExperienceName」 にはシーズンマスターデータに指定した値を 「PropertyId」 にはシーズンモデルIDを指定してください。

マッチセッションを作成

抽選を実行はゲームエンジン用の SDK では処理できません。

GS2-Matchmaking の連携機能を使用してください。

投票用紙を取得

    var item = await gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Ballot(
        seasonName: "rating-0001",
        sessionName: "gathering-0001",
        numberOfPlayer: 4,
        keyId: "key-0001"
    ).ModelAsync();
    const auto Future = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Ballot(
        "rating-0001", // seasonName
        "gathering-0001", // sessionName
        4, // numberOfPlayer
        "key-0001" // keyId
    )->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

投票を実行

    var result = await gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).VoteAsync(
        ballotBody: "ballotBody",
        ballotSignature: "ballotSignature",
        gameResults: new List<Gs2.Unity.Gs2SeasonRating.Model.EzGameResult> {
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 1,
                UserId = "user-0001",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0002",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0003",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 3,
                UserId = "user-0004",
            },
        },
        keyId: "key-0001"
    );
    const auto Future = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    )->Vote(
        "ballotBody", // ballotBody
        "ballotSignature", // ballotSignature
        []
        {
            auto v = MakeShared<TArray<TSharedPtr<Gs2::UE5::SeasonRating::Model::FEzGameResult>>>();
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(1))
                ->WithUserId(TOptional<FString>("user-0001"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(2))
                ->WithUserId(TOptional<FString>("user-0002"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(2))
                ->WithUserId(TOptional<FString>("user-0003"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(3))
                ->WithUserId(TOptional<FString>("user-0004"))
            );
            return v;
        }(), // gameResults
        "key-0001" // keyId
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

詳細なリファレンス