GS2-Ranking

Leaderboard function

It implements a ranking function to compete for game scores and clear times.

There are two types of rankings: one where all participants compete against each other on the same board, and the other where participants compete against the scores of subscribed players. The former is called Global Ranking and the latter is called Scope Ranking.

Global Ranking

Global Ranking provides a ranking function to compete with all players.

GS2-Ranking can realize large-scale ranking where more than hundreds of millions of players participate.

Instead, the ranking is not calculated in real time, but is calculated in a predefined cycle, and the ranking is calculated based on the results.

Category

Specify the type of ranking. In ranking, it is necessary to specify whether a higher score is superior or a lower score is superior.

Aggregation interval

Set the interval at which the ranking will be counted. You can set a minimum of 15 minutes and a maximum of 24 hours.

Be careful when setting the tally interval, because the interval set here is not the interval from the previous tally start time, but the interval from the previous tally end time. Consider the case where the first tally is run at 00:00 and the tally takes 5 minutes to complete.

Start time of tallyEnd time of tally
00:0000:05
00:2000:25
00:4000:45

Fixed Aggregate Time

When the aggregation interval is set to 24 hours, there is a need to fix the time at which the data is aggregated. To meet this need, we provide a function that performs the summarization at a specified time even if it is not in the summarization cycle.

For example, suppose that 24 hours is set as the count interval and 5 a.m. is set as the fixed count time.

Aggregation start timeAggregation end time
2020-01-01 05:002020-01-01 05:05
2020-01-02 05:002020-01-02 05:05← Only 23 hours and 55 minutes have elapsed, but the fixed tally time has been reached.
2020-01-03 05:002020-01-03 05:05← Only 23 hours and 55 minutes have elapsed, but the fixed tally time has been reached.

Score valid range

You can specify a range of values that will be accepted as scores for registration. This allows you to discard a registration without processing it if a clearly inappropriate score is registered.

In this case, no error is returned to the client, which makes it difficult to check the limits of an incorrect score.

Period during which scores can be registered

You can associate a GS2 Schedule event as the setting for the period during which scores can be accepted for registration. Scores will be discarded if submitted outside of the score acceptance period.

Aggregation processing does not occur outside of the registered period. However, GS2 Schedule API calls are still required to determine the schedule. Therefore, it is recommended that rankings that will obviously never be aggregated/viewed again be deleted from the master data.

Access Period for Ranking Data

You can associate a GS2 Schedule event as the period during which ranking data can be accessed. This can be used to disable score references after the event.

Ranking Generations

Generations can be set for each category. By changing the generation, you can reset the registered content of the ranking without changing the category name.

Updating scores

When submitting scores, the server treats the last registered score as the valid score. Therefore, if you want to record the best score as a ranking, you must judge the superiority of the score on the client and decide whether to send the score or not.

Obtaining Rankings

You can retrieve the ranking of a player with a given user ID. This process tries to respond with a ranking that is as close as possible to the most recent status.

It calculates the rank of the player with the latest score in the pre-calculated total results and responds with that rank. Therefore, even if the ranking is obtained immediately after the score is updated, a value equivalent to the ranking using the latest score can be obtained even if the aggregation time has not yet arrived.

Obtaining rankings around a specified score

You can specify a score and get the ranking around it. If there are a large number of identical scores, the specified score may not be placed in the center of the list.

Scope Ranking

Scope Ranking implements ranking within a very small group of players, such as ranking among friends.

To achieve this functionality, a score bucket is created for each player. When a player updates his score, the data in the bucket of the player who subscribed to his score is also updated. Each player achieves this by calculating his or her ranking using the scores in his or her bucket.

Example Implementation

Put a score

This API can be called in ApplicationAccess for convenience. However, the ability to send arbitrary scores is a vulnerability.

Therefore, if possible, this API should be configured so that it cannot be called by clients, and scores should only be accepted from trusted sources.

For example, if you want to implement a ranking of the number of items owned, it is more secure to register the number of items owned as a score in a script that is triggered when an item is acquired in GS2 Inventory.

    var result = await gs2.Ranking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Ranking(
        categoryName: "category-0001"
    ).PutScoreAsync(
        score: 1000L,
        metadata: null
    );
    var item = await result.ModelAsync();
    const auto Domain = Gs2->Ranking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Ranking(
        "category-0001" // categoryName
    );
    const auto Future = Domain->PutScore(
        1000L,
        nullptr // metadata
    );
    Future->StartSynchronousTask();
    if (!TestFalse(WHAT, Future->GetTask().IsError())) return false;

Get Rank (Global)

    var item = await gs2.Ranking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Ranking(
        categoryName: "category-0001"
    ).ModelAsync(
        scorerUserId : "user-0001"
    );
    const auto Domain = Gs2->Ranking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Ranking(
        "category-0001" // categoryName
    );
    const auto item = Domain.ModelAsync(
        "user-0001" // scorerUserId
    );

Get Rankings

    var items = await gs2.Ranking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).RankingsAsync(
    ).ToListAsync();
    const auto Domain = Gs2->Ranking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto It = Domain->Rankings( // categoryName
    );
    for (auto Item : *It)
    {

    }

Subscribe to a player (Scope)

    var result = await gs2.Ranking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SubscribeAsync(
        categoryName: "category-0001",
        targetUserId: "user-0002"
    );
    var item = await result.ModelAsync();
    const auto Domain = Gs2->Ranking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto Future = Domain->Subscribe(
        "category-0001",
        "user-0002"
    );
    Future->StartSynchronousTask();
    if (!TestFalse(WHAT, Future->GetTask().IsError())) return false;

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (!TestFalse(WHAT, Future2->GetTask().IsError())) return false;
    const auto Result = Future2->GetTask().Result();

Unsubscribe from a player (Scope)

    var result = await gs2.Ranking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SubscribeUser(
        categoryName: "category-0001",
        targetUserId: "user-0002"
    ).UnsubscribeAsync(
    );
    const auto Domain = Gs2->Ranking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->SubscribeUser(
        "category-0001", // categoryName
        "user-0002" // targetUserId
    );
    const auto Future = Domain->Unsubscribe(
    );
    Future->StartSynchronousTask();
    if (!TestFalse(WHAT, Future->GetTask().IsError())) return false;

Get a list of subscribed players

    var items = await gs2.Ranking.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SubscribeUsersAsync(
    ).ToListAsync();
    const auto Domain = Gs2->Ranking->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto It = Domain->SubscribeUsers( // categoryName
    );
    for (auto Item : *It)
    {

    }

Detailed reference