Momento Leaderboardでゲームのスコアを保存・取得できるか試してみた
Momento Advent Calendar 2023の14日目の記事では、MomentoのLeaderboardについての説明があります。Leaderboardはゲームなどでユーザーのスコアをランキング形式で表示するために使用されます。記事では、Leaderboardを作成し、スコアの保存や取得方法について詳しく解説しています。さらに、スコアの範囲や順位での取得方法も紹介されています。Leaderboardを使ったアプリケーションを開発する際に役立つ情報が得られます。参考記事:[https://docs.momentohq.com/ja/leaderboards](https://docs.momentohq.com/ja/leaderboards)
目次
この記事は「Momento Advent Calendar 2023」14日目の記事です。
Momentoの製品一覧を見ていてずっと気になっていたのが、このLeaderboardでした。ドキュメントをみる限り、ゲームなどでユーザーのスコアをランキング形式で出したりするのに使える様子です。
NodeでLeaderboardを作ってみる
何はともあれ作って動かしてみましょう。
Learderboardを操作するクライアントを立ち上げる
SDKを利用してクライアントを作成します。2023/12時点ではプレビューの機能らしく、クラス名にPreview
がついていました。
const client = new PreviewLeaderboardClient({
configuration: Configurations.Laptop.latest(),
credentialProvider: CredentialProvider.fromString({
apiKey: process.env.MOMENT_API_KEY
}),
});
LeaderboardはMomento Cacheを使う様子です。client.leaderboard
の第一引数で利用するキャッシュの名前を指定しましょう。第二引数がLeaderboardの名前で、複数設定できるみたいです。
const leaderboard = client.leaderboard('cache', 'my-leaderboard');
スコアをLeaderboardに保存する
Leaderboardへの保存は、まとめて行うこともできるようにMap
で投入できます。配列の1番目がユーザーID(数字)、2番目がスコアになる様子です。また、更新についても同じ処理で行えました。
const elements1: Map<number, number> = new Map([
[123, 100.0],
[234, 200.0],
[345, 300.0],
[456, 400.0],
]);
const result1 = await leaderboard.upsert(elements1);
const elements2: Map<number, number> = new Map([
[567, 500],
[678, 600],
[789, 700],
[890, 800],
]);
const result2 = await leaderboard.upsert(elements2);
ネストした配列が少し苦手という方は、Record
を使ってオブジェクト形式にすることもできます。この場合はキーがユーザーID、値がスコアになる様子でした。
const elements2: Record<number, number> = {
567: 500,
678: 600,
789: 700,
890: 800,
};
Leaderboardからスコアを取得する
スコアの取得は、fetchByScore
とfetchByRank
の2つで行います。
const result = await leaderboard.fetchByScore();
ただしこれらのレスポンスは次の例のように生データで届きます。
{
"_elements": [
{
"wrappers_": null,
"arrayIndexOffset_": -1,
"array": [
123,
100
],
"pivot_": 1.7976931348623157e+308,
"convertedPrimitiveFields_": {}
},
{
"wrappers_": null,
"arrayIndexOffset_": -1,
"array": [
234,
200,
1
],
"pivot_": 1.7976931348623157e+308,
"convertedPrimitiveFields_": {}
},
アプリケーションコードで扱いやすくするには、次のようにvalues()
でデータを取得して処理をかけるようにしましょう。TypeScriptの場合は、instanceof
で取得に成功したときの型であることを確認することで、values()
を利用できます。
if (!(result instanceof LeaderboardFetch.Success)) {
throw new Error("Failed to load the leaderboard");
}
const scores = result.values();
values()
を通すことで、読みやすいデータに変わりました。
[
{
"id": 123,
"score": 100,
"rank": 0
},
{
"id": 234,
"score": 200,
"rank": 1
},
...
スコアのMIN / MAXや並び順、件数も取ることができます。例えば「500点以下の上位3名を昇順で」だとこのように書きます。
const result = await leaderboard.fetchByScore({
maxScore: 500,
order: LeaderboardOrder.Descending,
count: 3
});
指定したスコアより小さい値で、件数や並び順の指定も効いていることがわかります。
[
{
"id": 456,
"score": 400,
"rank": 4
},
{
"id": 345,
"score": 300,
"rank": 5
},
{
"id": 234,
"score": 200,
"rank": 6
}
]
順位で取得することもできる
「1位から3位を昇順で」のような指定をしたい場合は、fetchByRank
側を使いましょう。
const result = await leaderboard.fetchByRank(0, 3, {
order: LeaderboardOrder.Descending
});
const scores = result.values();
レスポンスはどちらも同じような形です。
[
{
"id": 789,
"score": 700,
"rank": 1
},
{
"id": 678,
"score": 600,
"rank": 2
}
]
特定のユーザーのスコアを見る方法
「あなたのスコアはx点です」「あなたのチームのスコアは・・・」などのデータを取得したい場合、getRank
を使います。第一引数で取得したいユーザーのIDを配列にて渡しましょう。
const result = await leaderboard.getRank([123, 234], {
order: LeaderboardOrder.Descending
})
ユーザーIDは数字にする必要がある点に注意
Momento Leaderboard (2023/12時点)を使う場合、Leaderboardに投入するユーザーIDを数字にする必要があるみたいでした。下のサンプルでは、文字列を渡してみようとしています。
const elements1: Record<string, number> = {
'user1': 100,
'user2': 200,
}
const result1 = await leaderboard.upsert(elements1);
const elements2: Map<number, number> = new Map([
[567, 500],
[678, 600],
[789, 700],
[890, 800],
]);
const result2 = await leaderboard.upsert(elements2);
実行結果はこちらで、文字列を使ったINSERTはエラーになっています。
{
"result1": {
"_innerException": {
"_transportDetails": {
"grpc": {
"code": 13,
"details": "13 INTERNAL: Request message serialization failure: Assertion failed",
"metadata": {}
}
},
"_errorCode": "INTERNAL_SERVER_ERROR",
"_messageWrapper": "An unexpected error occurred while trying to fulfill the request; please contact us at [email protected]"
}
},
"result2": {
"is_success": true
}
}
多くの場合、ユーザーIDやSIDは文字列ではないかと思います。そのため、IDPのユーザーIDとゲームのユーザーiDをマップするKVSや DBテーブルが必要になるかもしれません。
終わりに
ゲーム系の開発はあまりする機会がないのですが、こういうAPIを知っているといざという時に捗りそうな予感がします。これを使うために何かアプリを作ってもいいのかも・・・?と思えるくらいにはシンプルなAPIでしたので、来年の個人開発目標になるかもしれません。