GS2-SeasonRating

Season rating feature

This feature classifies players by player skill and enables matchmaking with players of similar skill. GS2-Matchmaking has a matchmaking function based on rating values, but it does not support the recently increasing system of ranking players over the course of a season, which can last from a few weeks to a few months in real time.

This microservice can be used to realize a game cycle in which players repeatedly play against each other to achieve a high tier during a season, considering a defined period of time as a season. Therefore, the indicator of strength in this feature is not a rate value, but is expressed by which tier the player belongs to.

graph TD
  Match["GS2-Matchmaking<br/>match established"] --> Session["Create MatchSession"]
  Session --> Ballot["Each player obtains a ballot<br/>(Ballot / SignedBallot)"]
  Ballot --> GamePlay["Gameplay"]
  GamePlay --> Vote["Vote / VoteMultiple<br/>Submit rankings"]
  Vote --> Aggregate{"Result determined by majority"}
  Aggregate -- determined --> Calc["Calculate point change<br/>(based on TierModel)"]
  Calc --> Experience["Apply points / rank<br/>to GS2-Experience"]
  Aggregate -- split --> Skip["End without aggregation"]

Tiers

Tiers generally start at Bronze and go up through Silver, Gold, Platinum, and so on as you win more games. To move up a tier, you play against other players in the same tier, and if your points, which vary depending on your ranking, exceed a threshold, you move up to the next tier. If you lose the match, your points may decrease, and if your points fall below the threshold, you may move down to the previous tier.

Tier model configuration items

Each tier is defined as a TierModel, and the following parameters control point variation and behavior on promotion.

Item Description
metadata Free description such as tier name (Bronze/Silver/Gold, etc.)
entryFee Points consumed when joining a game. Used for designing difficulty in which players cannot be promoted without consecutive wins
minimumChangePoint Amount of points subtracted when finishing in last place
maximumChangePoint Amount of points added when finishing in first place
raiseRankBonus Bonus points granted when ranking up. Useful for preventing chattering (immediate demotion right after promotion)

Matches across tiers

In rating matches, matchmaking rules should in principle consist of the same tier. However, when matchmaking includes players from the previous and next tiers due to a lack of players or other reasons, the amount of point fluctuation is determined based on the minimum and maximum fluctuation and ranking of the tier to which each player belongs. There is no mechanism to grant bonus points when a player in a lower tier beats a player in a higher tier, or vice versa. Rather than trying to craft adjustments to pit players of fundamentally different strengths against each other, it is recommended to design tier thresholds so that each tier retains at least enough players to make gameplay viable.

Points

Range of variation

For each tier, you can set the amount of points subtracted when finishing last and the amount of points added when finishing first. For intermediate ranks, the amount of points added or subtracted is determined by dividing equally by the number of patterns in the reported rankings.

Entry Fee

Depending on the tier, you can configure points to be consumed in order to participate in a game. This allows you to express conditions in which consecutive wins are required to advance up a tier. The entry fee is paid when obtaining the ballot used to report the game results to the server.

Rank Up Bonus

Bonus points can be added when a player is promoted to a higher rank by adding points. This prevents chattering where the player is demoted again immediately after promotion.

User Data Management

GS2-SeasonRating does not manage points and ranks itself. Actual user data management is done using GS2-Experience.

As season master data, specify the GS2-Experience experience model that manages the season’s points, and specify the season model ID as the property ID; the experience value manages the points and the rank manages which tier the player belongs to. In other words, the amount of point variation is managed by GS2-SeasonRating master data, but the thresholds that determine rank based on points, and the user data of which tier a player belongs to, are managed by GS2-Experience. This allows you to use advanced features such as the rank value verification provided by GS2-Experience for exchange processes that grant items to players of a specific rank at the end of the season.

graph LR
  SeasonModel["SeasonModel<br/>(GS2-SeasonRating)"] -- "experienceModelId" --> ExpModel["ExperienceModel<br/>(GS2-Experience)"]
  ExpModel -- "rank thresholds" --> Status["Player rank status<br/>(stored in GS2-Experience)"]
  Vote["Vote result determined"] -- "point add/subtract" --> Status

Match Sessions

To conduct a rating match, you must first create a match session resource in GS2-SeasonRating. GS2-Matchmaking has an integration feature that creates a match session with the name of the gathering when matchmaking is established. Unless there is a particular reason not to, please create match sessions in this way.

Expiration of a match session

Match sessions can have an expiration in seconds, up to a maximum of 24 hours. Results must be voted on within this period, and if 5 minutes have passed since the first vote and not all votes have been cast, the results are aggregated at that point.

Voting on results

Once matchmaking is complete, each player retrieves a ballot from the match session. The ballot is used to vote on the results. The vote contents include a list of user IDs of the players who participated in the match and their rankings.

Ballot signing

When you obtain a Ballot, GS2 issues it as a SignedBallot with a signature. The signature is generated by the GS2-Key specified in keyId, and signature verification is performed on the server side at the time of voting, so the vote contents cannot be tampered with.

Voting multiple ballots per match (VoteMultiple)

For cases in which a single player must obtain and submit ballots for all participants together (such as a single-player game where the results against CPUs are submitted), use VoteMultiple. You can send multiple SignedBallots at once.

Vote result fragmentation

When the server tries to take a majority vote of the accepted votes, if the result is a tie and the final result cannot be determined, no rate calculation is performed. This makes it difficult to determine the correct rate value in a 1vs1 game. To solve this problem, you may need to devise a workaround such as matchmaking a third player who is not directly involved in the game behind the scenes and having that player vote from a third-party perspective.

Script Triggers

GS2-SeasonRating does not provide script triggers.

Transaction Actions

GS2-SeasonRating does not provide transaction actions. Point and rank changes are internally reflected via the transaction actions of GS2-Experience. To distribute rewards to players above a specific rank after the season ends, a common implementation is to combine the exchange process of GS2-Exchange with the VerifyRankAction of GS2-Experience.

Master Data Operation

Registering master data allows you to configure data and behaviors available to the microservice.

Master data types include:

  • SeasonModel: The tiers included in a season, and the linked GS2-Experience experience model
  • TierModel: Point variation, entry fee, and rank up bonus for each tier

Master data can be registered via the Management Console, by reflecting data from GitHub, or by setting up workflows to register via CI using GS2-Deploy.

The following is a JSON example of master data.

{
  "version": "2023-04-05",
  "seasonModels": [
    {
      "name": "season-0001",
      "metadata": "Season 1",
      "experienceModelId": "grn:gs2:{region}:{ownerId}:experience:experience-0001:model:season",
      "tiers": [
        {
          "metadata": "Bronze",
          "raiseRankBonus": 100,
          "entryFee": 0,
          "minimumChangePoint": 10,
          "maximumChangePoint": 30
        },
        {
          "metadata": "Silver",
          "raiseRankBonus": 150,
          "entryFee": 10,
          "minimumChangePoint": 20,
          "maximumChangePoint": 40
        }
      ]
    }
  ]
}

Example Implementation

Get current points and rank

Use the GS2-Experience API to obtain the status. Specify the values specified in the season master data for “NamespaceName” and “ExperienceName,” and specify the season model ID for “PropertyId.”

Create a match session

Creating a match session cannot be handled by the game engine SDK.

Please use the GS2-Matchmaking integration feature. By setting a script that invokes match session creation in GS2-SeasonRating as the script triggered when matchmaking is established in the GS2-Matchmaking namespace settings, a corresponding session can be automatically generated upon matchmaking completion.

Retrieve a ballot

    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;
    }
    const auto Result = Future->GetTask().Result();

Vote

    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;
    }

Vote multiple ballots at once

Use this when one player submits ballots on behalf of all participants, for example for CPU battle results.

    var result = await gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).VoteMultipleAsync(
        signedBallots: new [] {
            signedBallot1,
            signedBallot2,
        },
        gameResults: new [] {
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 1,
                UserId = "user-0001",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0002",
            },
        },
        keyId: "key-0001"
    );
    const auto Future = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    )->VoteMultiple(
        SignedBallots,
        GameResults,
        "key-0001" // keyId
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

Detailed Reference