【GS2】GS2を使用して詰まった点と解決方法
こんにちは。情熱開発部プログラム3課の角谷瑠晟です!
普段ゲームをプレイしていると、ランキング、フレンド機能、プレゼントボックスなどを目にする機会があると思います。
プレイヤー側としては何気なく使っている機能ですが、実装する側になるとアイテムの所持数管理やフレンドの扱いなど考えることが多くあります。
今回は、そういったゲーム向けのバックエンド機能を提供しているGS2を使用する機会があったので、実際に使って詰まった点と対応方法について紹介しようと思います。
※この記事はGS2の各機能を網羅的に解説するものではなく自分が実装中に詰まった点とその時に調べた点と解決方法をまとめたものになります。
同じような挙動に当たった時に「そういう問題があるのか」「ここを確認するとよさそう」と調べるきっかけになればと思っています。
※今回の情報は2026/7/1時点の情報です。
GS2の仕様やSDKのバージョンによって挙動が変わる可能性があるため実際に使用する際は公式ドキュメントも確認してください。
目次
GS2とは
GS2とはGame Server Services 株式会社が提供しているバックエンドサービスです。
公式サイトのトップページ
ゲームを作っていると以下のような機能が必要になることがあります。
- アイテムの所持数管理
- プレゼントボックス
- ランキング
- フレンド、フォロー機能
- ログインボーナス
- 課金通貨の管理
- イベント期間の管理
これらをすべて自前でサーバー実装しようとすると、実装コストも運用コストも大きくなります。
GS2を使用すると、こういったゲームで必要になりやすいバックエンド機能をサービスとして利用できます。
自前でサーバーを用意しなくても、GS2の各サービスを組み合わせることでゲーム側に必要な機能を実装できるのが大きなメリットです。
GS2のSDKについて
GS2にはUnityやUnreal Engineなどのゲームエンジン上で使用する Game Engine SDK とC#、Python、TypeScriptなどからGS2のAPIを扱う 各種言語向けSDK があります。
今回の実装ではゲームクライアント側では主にGame Engine SDKを使用し一部の処理では別途サーバーサイドの環境を作成し各種言語向けSDKを使ってInventoryの状態確認や既存ユーザーデータの更新を行いました。
この記事では以降ゲームエンジン上で使用するSDKを Game Engine SDKサーバーサイド処理やツールなどから使用するSDKを 各種言語向けSDK と呼んで説明します。
今回の記事に出てくるGS2の機能
今回の記事で主に扱うGS2の機能は以下です。
各機能の詳細な使い方や実際に使用した関数名は、後ほど各詰まったポイントの中で説明します。
GS2-Inventory
GS2-Inventoryは、プレイヤーの所持アイテムやインベントリ容量を管理するための機能です。
参考ドキュメント:
GS2-Friend
GS2-Friendは、フレンド申請やフォローなど、ユーザー同士のつながりを扱うための機能です。
参考ドキュメント:
GS2-Inbox
GS2-Inboxは、プレゼントボックスやメッセージのような機能を実装するためのサービスです。
今回の実装では、フォローやアンフォローされたことを相手に通知する用途でも使用しました。
参考ドキュメント:
GS2-Exchange
GS2-Exchangeは、ゲーム内リソースの交換を実装するための機能です。
GS2-Exchangeの交換処理では、検証アクション、消費アクション、入手アクションを組み合わせて、複数サービスをまたいだ処理を行います。
参考ドキュメント:
詰まったポイント1:Inventory同期ではSDKキャッシュの考慮が必要だった
まず最初に詰まったのがGS2-Inventoryの同期まわりです。
GS2-Inventoryではプレイヤーの所持アイテムやインベントリ容量を管理できます。
今回の実装ではGS2-Exchangeでアイテム交換を行った後にInventoryの所持アイテム一覧をクライアント側でも更新したい場面がありました。
最初はExchange後にGS2の公式ドキュメントにもあるように以下のようなコードを呼び出せば交換後のInventoryをそのまま取得できると思っていました。
var domain = gs2.Inventory.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Inventory(
inventoryName: "item"
);
var items = await domain.ItemSetsAsync(
).ToListAsync();しかし実際にはサーバー側のInventoryは更新されているのにクライアント側では更新後の値を取得できないことがありました。
原因:Game Engine SDKのキャッシュと更新経路を考慮できていなかった
最初は以下の図のように常にサーバーの情報を取得できると考えていました。

しかし、GS2のGame Engine SDKではInventoryの情報をSDK側でキャッシュしています。
そのためサーバー側の値が変わったからといってクライアント側で参照している値が必ず即時に最新になるわけではありませんでした。

特にGS2-Exchangeを経由してInventoryを変更する場合はInventoryだけでなくExchange側のトランザクション設定も関係していました。
また、今回触っていた環境ではGame Engine SDKを使った処理だけでなく各種言語向けSDKを使って直接Inventoryにアイテムを付与する処理もありました。
このようにGame Engine SDKの外側でInventoryが更新される場合その変更はGame Engine SDK側のキャッシュ更新としては検知できない場合があります。
つまり、今回の問題では以下の2つの問題が発生していました。
- Exchange経由でInventoryを更新した場合のキャッシュ反映が上手く行っていない
- 各種言語向けSDKなど、Game Engine SDKの外側から直接Inventoryを更新した場合の同期
解決方法:1.トランザクション設定の見直し
この問題に対しては、まずGS2-Exchange側のトランザクション設定を見直しました。
具体的には以下の設定を有効にしました。
- enableAtomicCommit
- トランザクションの結果をアトミックに反映するための設定です。
- acquireActionUseJobQueue
- 入手アクションを実行する際にGS2-JobQueueを使用するための設定です。
この設定を見直したことでExchangeでInventoryが変更された場合はJobQueueやGateway経由の処理が完了した後にSDK側のキャッシュも更新され更新後のInventoryを取得できるようになりました。
解決方法:2.各種言語向けSDKを使用した同期処理
ただし、これですべてのInventoryの更新を拾えるようになったわけではありませんでした。
各種言語向けSDKを使ってInventoryを直接更新する経路についてはGame Engine SDKのキャッシュ更新として自動で追随しない場合があるため別途各種言語向けSDKで同期を呼び出すようにしました。
const result = await client.describeItemSets(
new Gs2Inventory.DescribeItemSetsRequest()
.withNamespaceName("namespace-0001")
.withInventoryName("item")
.withAccessToken("accessToken-0001")
.withPageToken(null)
.withLimit(null)
);
const items = result.getItems();
const nextPageToken = result.getNextPageToken();詰まったポイント2:フォロー一覧は取得できるがフォロワー一覧は別途用意する必要があった
次に詰まったのがGS2-Friendのフォロー機能を使ったフォロワー一覧の扱いです。
今回の実装ではSNSのようなフォロー機能を作りたい場面がありました。
フォローする場合は FollowAsync()、アンフォローする場合は UnfollowAsync()、自分がフォローしているユーザー一覧を取得する場合は FollowsAsync() を使用することで取得することが可能です。
最初はGS2-Friendのフォロー機能を使えば以下の情報をどちらも取得できると思っていました。
- 自分がフォローしているユーザー一覧
- 自分をフォローしているユーザー一覧
しかし、実際にはGS2の機能で取得出来るのは1.の自分がフォローしているユーザー一覧のみでした。
原因:フォロー一覧とフォロワー一覧を同じように取得できると思っていた
誰が自分をフォローしているかを扱いたい場合は自分でフォロワー情報を管理するか、要件によっては最初からフレンド機能として扱う設計にする必要がありました。
ただ、今回の実装では仕様を大きく変えづらい段階だったためフレンド機能として作り直すのではなく、フォロワー一覧を自前で管理する方向にしました。
解決方法:GS2-Inboxで通知を送りセーブデータでフォロワー情報を管理する
この問題に対しては、GS2-Inboxを使ってフォロー/アンフォローの通知を送り、独自にフォロワー情報を管理することで対応しました。
具体的にはユーザーが誰かを FollowAsync() でフォローしたタイミングでフォローされた側にGS2-Inbox経由で通知を送ります。
フォローされた側はその通知を受け取ったら自分のセーブデータにフォロワー情報を保存します。

また、アンフォローした場合も同じようにフォローされていた側にGS2-Inbox経由で通知を送り、セーブデータのフォロワー一覧から削除します。

操作ごとの処理は以下です。
- フォローした場合
Inboxでfollow通知を送り受け取った側はセーブデータのフォロワー一覧に追加します。 - アンフォローした場合
Inboxでunfollow通知を送り受け取った側はセーブデータのフォロワー一覧から削除します。
通知の中には最低限以下のような情報を入れました。
- 通知種別:
follow/unfollow - 送信元ユーザーID:フォロー、またはアンフォローしたユーザーID
{
"type": "<follow | unfollow>",
"sendUserId": "user-a"
}これによりFollowsAsync() の用途とは別に必要だった自分をフォローしているユーザー一覧をゲーム側のセーブデータとしてから取得出来るようになりました。
詰まったポイント3:Inventoryの最大サイズを変更した際、別途既存ユーザーのInventoryも更新が必要だった
最後に詰まったのがGS2-Inventoryの最大サイズの変更です。
GS2-Inventoryではインベントリの最大サイズをマスターデータで設定できます。
自分がマスターデータを設定した際、InventoryのMaxCapacityを意図した値と違う値で設定してしまっていました。
途中でそのことに気づいたためInventoryのマスターデータにあるMaxCapacityの設定を修正しました。
{
"version": "2019-02-05",
"inventoryModels": [
{
"name": "[string]インベントリモデル名",
"metadata": "[string?]メタデータ",
"initialCapacity": "[int]初期サイズ",
"maxCapacity": "[int]最大サイズ",
"protectReferencedItem": "[bool?]参照元保護",
"itemModels": [
{
"name": "[string]アイテムモデル名",
"metadata": "[string?]メタデータ",
"stackingLimit": "[long]スタック可能な最大数量",
"allowMultipleStacks": "[bool]複数スタック許可",
"sortValue": "[int]表示順番"
}
]
}
],
}最初はマスターデータを修正すれば既に作成されているユーザーのInventoryのMaxCapacityも同じように更新されると思っていました。
しかし、実際にはマスターデータの変更が反映されるのはこれから新規に作成されるInventoryのみでした。
既存ユーザーがすでに持っているInventoryのMaxCapacityはマスターデータを変更しただけでは変更されませんでした。
原因:マスターデータの変更と既存ユーザーデータ変更は別処理だった
最初は以下のように考えていました。
InventoryのマスターデータにあるMaxCapacityの設定を変更する
↓
全てのユーザーのInventoryの容量も変わる
しかし、既存ユーザーのInventoryはすでに作成済みのです。
そのためマスターデータのMaxCapacityの設定を変更しても既存ユーザーのInventoryMaxCapacityが自動で書き換わるわけではありませんでした。
既にInventoryを持っているユーザーのMaxCapacityを変えたい場合は既存ユーザーデータに対して別途更新処理を行う必要がありました。
解決方法:既存ユーザーにはsetCapacityByUserIdを呼び出す
この問題に対しては各種言語向けSDKの setCapacityByUserId を使用しました。
※setCapacityByUserId は指定したユーザーのInventory容量を設定するためのAPIです。
今回、すでにInventoryが作成されているユーザーに対して setCapacityByUserId を呼び出し、Capacityサイズを意図した値に変更する必要がありました。
const result = await client.setCapacityByUserId(
new Gs2Inventory.SetCapacityByUserIdRequest()
.withNamespaceName("namespace-0001")
.withInventoryName("inventory-0001")
.withUserId("user-0001")
.withNewCapacityValue(10)
.withTimeOffsetToken(null)
);この対応を入れることで新しく作成されるInventoryだけでなく、既存ユーザーのInventoryについても意図した容量に揃えられるようになりました。
今回の注意点をまとめると以下になります。
- Inventoryの容量が実際にどれくらい必要か分かっている場合は事前にInventoryのマスターデータに適切なMaxCapacityを設定しておく。
- 運営途中等でMaxCapacityを変更した場合、既存ユーザーにも変更を反映する必要がある場合は
setCapacityByUserIdを別途呼び出す。
終わりに
GS2はゲームに必要なバックエンド機能を簡単に利用できる便利なサービスです。
一方でGS2の各サービスには想定された使い方がありSDKのキャッシュやトランザクション設定、JobQueueなど理解しておくべき仕組みもあります。
実装してみてGS2を使う場合はAPIを呼ぶだけではなくSDKの挙動や使える機能も含めて以下を事前に確認しておくことが大事だと感じました。
- 事前にGS2がどうやって動作しているのかを理解しておく(キャッシュの仕組みなど)
- SDKにはどのような機能が用意されているのか
- 用意されている機能の組み合わせで要件を満たせるのか
- 運用後に設定変更した場合、既存ユーザーはどう影響するのか
今後GS2を使用する方の参考になれば幸いです。
【免責事項】
本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。