> For the complete documentation index, see [llms.txt](/llms.txt)

# GS2-Ranking2

ランキング機能



{{% alert title="Tip" color="info" %}}
GS2-Ranking2 は名前の通りバージョン2に相当するマイクロサービスです。

以前のバージョンに関するドキュメントは [GS2-Ranking]() を参照してください。
{{% /alert %}}

ゲームのスコアやクリアタイムを競うランキング機能を実現します。

GS2-Ranking2 は以下の3種類のモードを提供します。

- グローバルランキング
- クラスターランキング
- 購読ランキング

ゲームが必要とするランキング機能の多くはこのいずれかのモードで要件を満たせるはずです。

## ランキングモード

### グローバルランキング

グローバルランキングは全てのプレイヤーと競い合うためのランキング機能を提供します。
GS2-Ranking2 では上位1000位のプレイヤーのみランキングに参加可能で、1000位以下のプレイヤーのスコアは送信したとしてもランキングへは登録されません。

以前のバージョンである GS2-Ranking が1億を超えるプレイヤーの正確な順位を返す能力を有する形で提供していましたので、以前のバージョンと比較して大きく仕様が変化しています。
GS2-Ranking では、多くのスコアを扱える代わりに、15分〜24時間の範囲で指定した集計間隔で集計が行われるまでランキングへの登録が行われない仕様で、集計の度に参加人数に応じたコストが生じていました。

GS2-Ranking を利用した開発者からのフィードバックを受けて GS2-Ranking2 は再設計されました。
具体的に以下の点を重要なフィードバックと捉えて再設計しています。
- 多くのユースケースにおいて上位プレイヤーのみ表示できればよい
- 順位の変化を即座にランキングへと反映したい
- プレイヤーの増加によるランキング集計コストの増加は望ましくない

結果として、前述のように上位1000人のプレイヤーのみがランキングに参加でき、1001位以下のプレイヤーは「圏外」として処理する仕組みになりました。
その代わりスコアの登録直後にランキングに反映され、通常のAPIリクエストコスト以上の追加コストは発生しません。

### クラスターランキング

概ねグローバルランキングと同じ仕様ですが、唯一異なるのはクラスターIDごとに異なるランキングが作成されることです。

クラスターIDに GS2-Guild のギルドIDを指定することで、ギルドメンバー同士で競うようなランキングを実現するために利用できます。

### クラスター参加判定

クラスターランキングでは、クラスターの種類を定義しておくことでスコア登録時にクラスターに参加しているか確認してからスコア登録を実行できます。

たとえば、GS2-Guild のギルドをクラスターとしたランキングを実現する場合、ランキングモードの設定でクラスターの種類に「Gs2Guild::Guild」を指定することで
スコア登録時にクラスターIDで指定されたギルドにスコアを登録しようとしているプレイヤーがメンバーとして登録されていることを確認した上でスコアを登録するようにできます。

### 購読ランキング

GS2-Ranking のスコープランキングに類似する仕様のランキング機能です。
他プレイヤーを購読することで、自分のランキングボードに他プレイヤーの最新のスコアを含めることが可能となります。

フレンド内ランキングのようなプレイヤー間で非対称性が強いランキングを実現するために使用します。

### 購読ランキングにおける反映遅延

スコアの登録を実行すると、プレイヤーを購読しているプレイヤーのランキングに非同期でスコア登録が実行されます。
この処理は通常1秒以内に実行されますが、非同期処理のためスコアが反映されるまで若干の遅延が生じます。

## シーズン

各ランキングにはスコアの登録を受け付ける期間として GS2-Schedule のイベントを関連づけることが可能です。
GS2-Schedule のイベントには繰り返し設定が可能で、各ランキングはイベントが繰り返す度にリセットされます。

この機能を実現するために、GS2-Ranking2 は各ランキングに `シーズン` というプロパティを持っています。
ランキングの結果はシーズンごとに格納され、過去のシーズンの結果をいつでも参照することができます。

## ランキング報酬

グローバルランキング・クラスターランキングではランキングの順位報酬を設定できます。
報酬を設定するには `順位閾値` と `報酬の内容` を設定します。

閾値に 3 を指定すると、1,2,3位のプレイヤーへの報酬
続けて 10 を指定すると、4, 5, 6, 7, 8, 9, 10位プレイヤーへの報酬を設定できます。

### ランキング圏外のプレイヤーへの報酬

1001 を閾値に指定すると、ランキング圏外のプレイヤーへの報酬を設定できます。
1001 を閾値とするランキング報酬の設定は任意で、未指定の場合は圏外のプレイヤーは報酬を受け取ることはできません。

### 過去シーズンの報酬受け取り

過去シーズンのランキング報酬は、報酬受け取りAPIに過去のシーズン番号を指定して呼び出すことで、いつでも受け取ることができます。

## スコアの有効範囲

スコアとして登録を受け付ける値の範囲を設定できます。
これによって、明らかに不適切なスコアの登録があった時に登録処理を行わずに捨てることができます。

## 期間設定

### スコアの登録可能期間

スコアの登録を受け付ける期間の設定として GS2-Schedule のイベントを関連づけることができます。
スコア受け付け期間外にスコアを送信してもスコアは捨てられます。

### ランキングデータへのアクセス可能期間

ランキングデータへのアクセス可能期間として GS2-Schedule のイベントを関連づけることができます。
イベント終了後はスコアの参照もできなくする場合などに活用できます。

## トランザクションアクション

GS2-Ranking2 では以下のトランザクションアクションを提供しています。

- 検証アクション: グローバル・クラスター・購読 各ランキングのスコア検証
- 消費アクション: ランキング報酬受け取り履歴の記録

「ランキングのスコア検証」を検証アクションとして利用することで、特定のスコア以上を記録しているプレイヤーのみが購入できる商品や、挑戦できるクエストといった制限を、トランザクション内で安全に設けることが可能になります。これにより、上位ランカー限定の特別な報酬獲得イベントなどを確実に実現できます。

## マスターデータ運用
マスターデータを登録することでマイクロサービスで利用可能なデータや振る舞いを設定できます。

マスターデータの種類には以下があります。

- `RankingModel`: ランキングモードや報酬閾値の設定

マスターデータの登録はマネージメントコンソールから登録する他、GitHubからデータを反映したり、GS2-Deployを使ってCIから登録するようなワークフローを組むことが可能です。

## 実装例

### スコアを登録

このAPIは、利便性の観点から ApplicationAccess で呼び出せるようになっています。
しかし、任意のスコアで送信できるのは脆弱性となります。

そのため、可能であればこのAPIをクライアントから呼び出せないように設定し、信頼できる送信元からのみスコアの登録を受け付けられるようにするべきです。

たとえば、アイテムの所持数量のランキングを実現したいのであれば、GS2-Inventory のアイテム入手時にトリガーされるスクリプトでスコアとしてアイテムの所持数量を登録する方が安全に処理できます。

#### グローバルランキング



**Unity**
```csharp

    var result = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).GlobalRankingModel(
        rankingName: "ranking-0001"
    ).GlobalRankingSeason(
        gameSession: GameSession,
        season: null // current season
    ).PutGlobalRankingAsync(
        score: 100L,
        metadata: null
    );
    var item = await result.ModelAsync();
```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->GlobalRankingModel(
        "ranking-0001" // rankingName
    )->GlobalRankingSeason(
        GameSession,
        TOptional<int64>() // current season
    )->PutGlobalRanking(
        100L, // score
        TOptional<FString>() // metadata
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();
```


#### クラスターランキング



**Unity**
```csharp

    var result = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).ClusterRankingModel(
        rankingName: "ranking-0001"
    ).ClusterRankingSeason(
        clusterName: "cluster-0001",
        gameSession: GameSession,
        season: null // current season
    ).PutClusterRankingAsync(
        score: 100L,
        metadata: null
    );
    var item = await result.ModelAsync();

```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->ClusterRankingModel(
        "ranking-0001" // rankingName
    )->ClusterRankingSeason(
        "cluster-0001", // clusterName
        GameSession,
        TOptional<int64>() // current season
    )->PutClusterRanking(
        100L, // score
        TOptional<FString>() // metadata
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();

```


#### 購読ランキング



**Unity**
```csharp

    var result = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).SubscribeRankingModel(
        rankingName: "ranking-0001"
    ).SubscribeRankingSeason(
        gameSession: GameSession,
        season: null // current season
    ).PutSubscribeRankingAsync(
        score: 100L,
        metadata: null
    );
    var item = await result.ModelAsync();

```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->SubscribeRankingModel(
        "ranking-0001" // rankingName
    )->SubscribeRankingSeason(
        GameSession,
        TOptional<int64>() // current season
    )->PutSubscribeRanking(
        100L, // score
        TOptional<FString>() // metadata
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();

```


### 順位を取得

#### グローバルランキング



**Unity**
```csharp

    var result = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).GlobalRankingModel(
        rankingName: "ranking-0001"
    ).GlobalRankingSeason(
        gameSession: GameSession,
        season: null // current season
    ).GetGlobalRankingRankAsync(
    );
    var item = await result.ModelAsync();

```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->GlobalRankingModel(
        "ranking-0001" // rankingName
    )->GlobalRankingSeason(
        GameSession,
        TOptional<int64>() // current season
    )->GetGlobalRankingRank(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();

```


#### クラスターランキング



**Unity**
```csharp

    var result = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).ClusterRankingModel(
        rankingName: "ranking-0001"
    ).ClusterRankingSeason(
        clusterName: "cluster-0001",
        gameSession: GameSession,
        season: null // current season
    ).GetClusterRankingRankAsync(
    );
    var item = await result.ModelAsync();

```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->ClusterRankingModel(
        "ranking-0001" // rankingName
    )->ClusterRankingSeason(
        "cluster-0001", // clusterName
        GameSession,
        TOptional<int64>() // current season
    )->GetClusterRankingRank(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();

```


#### 購読ランキング



**Unity**
```csharp

    var result = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).SubscribeRankingModel(
        rankingName: "ranking-0001"
    ).SubscribeRankingSeason(
        gameSession: GameSession,
        season: null // current season
    ).SubscribeRankingData(
        scorerUserId: "user-0001"
    ).GetSubscribeRankingRankAsync(
    );
    var item = await result.ModelAsync();

```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->SubscribeRankingModel(
        "ranking-0001" // rankingName
    )->SubscribeRankingSeason(
        GameSession,
        TOptional<int64>() // current season
    )->SubscribeRankingData(
        "user-0001" // scorerUserId
    )->GetSubscribeRankingRank(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();

```


### ランキングを取得

#### グローバルランキング



**Unity**
```csharp

    var items = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).GlobalRankingModel(
        rankingName: "ranking-0001"
    ).GlobalRankingSeason(
        gameSession: GameSession,
        season: null // current season
    ).GlobalRankingsAsync(
    ).ToListAsync();

```
**Unreal Engine**
```cpp

    const auto It = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->GlobalRankingModel(
        "ranking-0001" // rankingName
    )->GlobalRankingSeason(
        GameSession,
        TOptional<int64>() // current season
    )->GlobalRankings(
    );
    TArray<Gs2::UE5::Ranking2::Model::FEzGlobalRankingDataPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

```


#### クラスターランキング



**Unity**
```csharp

    var items = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).ClusterRankingModel(
        rankingName: "ranking-0001"
    ).ClusterRankingSeason(
        clusterName: "cluster-0001",
        gameSession: GameSession,
        season: null // current season
    ).ClusterRankingsAsync(
    ).ToListAsync();


```
**Unreal Engine**
```cpp

    const auto It = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->ClusterRankingModel(
        "ranking-0001" // rankingName
    )->ClusterRankingSeason(
        "cluster-0001", // clusterName
        GameSession,
        TOptional<int64>() // current season
    )->ClusterRankings(
    );
    TArray<Gs2::UE5::Ranking2::Model::FEzClusterRankingDataPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }


```


#### 購読ランキング



**Unity**
```csharp

    var items = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).SubscribeRankingModel(
        rankingName: "ranking-0001"
    ).SubscribeRankingSeason(
        gameSession: GameSession,
        season: null // current season
    ).SubscribeRankingsAsync(
    ).ToListAsync();


```
**Unreal Engine**
```cpp

    const auto It = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->SubscribeRankingModel(
        "ranking-0001" // rankingName
    )->SubscribeRankingSeason(
        GameSession,
        TOptional<int64>() // current season
    )->SubscribeRankings(
    );
    TArray<Gs2::UE5::Ranking2::Model::FEzSubscribeRankingDataPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }


```


### ランキング報酬を受け取る

ランキング報酬は、報酬閾値に該当するプレイヤーがAPIを呼び出すことで受け取れます。
受け取り処理はトランザクションとして実行され、設定された入手アクションが順に評価されます。

過去シーズンの報酬を受け取る場合は `season` に対象シーズン番号を指定します。

#### グローバルランキング



**Unity**
```csharp

    var transaction = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).GlobalRankingModel(
        rankingName: "ranking-0001"
    ).GlobalRankingSeason(
        gameSession: GameSession,
        season: null // current season
    ).GlobalRankingReceivedReward(
    ).ReceiveGlobalRankingRewardAsync(
    );
    await transaction.WaitAsync();
```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->GlobalRankingModel(
        "ranking-0001" // rankingName
    )->GlobalRankingSeason(
        GameSession,
        TOptional<int64>() // current season
    )->GlobalRankingReceivedReward(
    )->ReceiveGlobalRankingReward(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    const auto Transaction = Future->GetTask().Result();
    const auto Future2 = Transaction->Wait();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
```


#### クラスターランキング



**Unity**
```csharp

    var transaction = await gs2.Ranking2.Namespace(
        namespaceName: "namespace-0001"
    ).ClusterRankingModel(
        rankingName: "ranking-0001"
    ).ClusterRankingSeason(
        clusterName: "cluster-0001",
        gameSession: GameSession,
        season: null // current season
    ).ClusterRankingReceivedReward(
    ).ReceiveClusterRankingRewardAsync(
    );
    await transaction.WaitAsync();
```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Ranking2->Namespace(
        "namespace-0001" // namespaceName
    )->ClusterRankingModel(
        "ranking-0001" // rankingName
    )->ClusterRankingSeason(
        "cluster-0001", // clusterName
        GameSession,
        TOptional<int64>() // current season
    )->ClusterRankingReceivedReward(
    )->ReceiveClusterRankingReward(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    const auto Transaction = Future->GetTask().Result();
    const auto Future2 = Transaction->Wait();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
```


## 詳細なリファレンス

[GS2-Ranking2 API リファレンス](../../api_reference/ranking2)



