はじめに
ゲームな皆さんこんにちは、Game Solutions Architect の Leng (@msian.in.japan) と Game Developer Relations の Taka (@takahiroishii_) です。
この投稿ではゲームのバックエンドを初めてクラウド化させようと考えていて、オンラインゲームを作成する上で Amazon DynamoDB のデータ設計の基礎を学んでみたい、もしくは DynamoDB をゲームから学びたい、そんな皆さんへ向けて、初めてでもすぐにゲームデザインに合ったテーブル設計ができる事を目指す冒険を記した記事になります。
builders.flash メールメンバー登録
AWS for Games
AWS for Games ではより早い開発、よりスマートな運営、そしてより楽しいゲームへの成長という Build、Run、Grow の 3 つの柱に沿ってサポートします。本日は Run の柱、そして ゲーム向けデータベース のお話になります。

Amazon DynamoDB とゲーム
Amazon DynamoDB はハイパフォーマンスなアプリケーションを様々な規模で実行できる、フルマネージドかつサーバーレスの Key-Value 型 NoSQL データベースです。そんな DynamoDB とゲームの相性を考える上で、まずは DynamoDB の代表的な特徴をあげます
- Key-Value データベース : キーと値というシンプルな管理方法でデータを格納
- 高いスケーラビリティ : 一貫性のある高速パフォーマンスを自動でスケールしながら実現
- 低い管理負担 : バージョンアップやメンテナンスのような作業が不必要
次にそれぞれの特徴とゲームの相性を見ていきましょう
- Key-Value データベース : プレーヤー主体であるゲームのデータベースのキーはほとんどがプレーヤー ID
- 高いスケーラビリティ : 突然人気が出たり、時間帯で総プレーヤー数が著しい変化するのがゲーム
- 低い管理負担 : 管理負担を減らす事でゲーム本来の面白さを作る事に集中できる
どの DynamoDB の特徴もゲームと非常にマッチしていて高いシナジーがある事がわかります。
Amazon DynamoDB のコンポーネント
この記事では DynamoDB の基本的な機能にフォーカスし、テーブル設計をより簡単にイメージできる事を目指します。その中で出てくるいくつかの DynamoDB のコアコンポーネント があるので先に確認しておきましょう !
テーブル (Table) : RDB や他のデータベース同様、データのコレクションをテーブルと呼びます
項目 (Item) : 各テーブルに入っている識別可能な属性のグループです。RDB でいう行にあたります
属性 (Attribute) : 各項目は1つ以上の属性で構成されます。RDB でいう列にあたります
次に Key-Value 型である DynamoDB の Key の部分に当てはまるコンポーネントです。各テーブルはキーを指定して作る必要があり、そのテーブル内の 2 つの項目が同じキーを持つことはありません。このキーをプライマリキー (Primary Key) と呼びます。
更に、DynamoDB は 2 種類の異なるプライマリキーをサポートします。
パーティションキー (Partition Key) : パーティションキー、通称 PK という 1 つの属性で構成されたシンプルなプライマリキー
パーティションキーとソートキー (Sort Key) の組み合わせ : PK とソートキー、通称 SK という 2 つの属性で構成された複合プライマリキー
シンプルな Key-Value として 1 つの属性でプライマリキーを作り管理することも多いですが、2 つのキーである PK と SK を組み合わせることで同じ PK に複数の SK を紐付け複数の項目を持たせるケースも様々場面で便利になります。
最後に代替キーを使用してテーブル内のデータのクエリを行うことができるセカンダリインデックスです。DynamoDB では 2 種類のインデックスをサポートしていますが、今日はその中の 1 つにフォーカスします。
グローバルセカンダリインデックス : 通称 GSI という独自の PK 及び SK を持つインデックス
GSI では元のテーブルの PK 及び SK の組み合わせと異なるキーを新たに設定することで、本来のテーブルから行う場合と同じように別のキーでデータを読み取ることができます。
この他にも Amazon DynamoDB は沢山の機能を持ち合わせています。詳しくは 公式ドキュメント を参照してください !
クエスト手帳の定義
では早速冒険に出る準備をしたいと思います。
まず今日作るゲームのクエスト手帳とは何を指すのか、クエストを管理するとはどういう事か確認していきます。

クエスト
一般的にゲーム内の「クエスト」とは、プレーヤーがゲームの中で冒険に出る際に達成できる課題や目標の事を指します。例えば RPG のようなゲームの場合、シナリオに沿ったクエストが準備されていて、プレーヤーは順番にクエストを達成する事で物語が進み、また新たなクエストそしてシナリオを解放して行くといった感じです。
クエストの中身は前もってゲームのデザインの段階で細かく設定されており、プレーヤーは新しいクエストを解放する事で、初めて達成の条件や報酬を知るといった仕組みも少なくないと思います。1 つのクエストに対していくつかの一般的な要素があるので確認しておきます。
クエストのタイトル : クエストの短いサマリーのような役割を果たし、時には物語を盛り上げる大事なコンポーネントです。
クエストの内容 : ゲームによって内容は異なります。あるゲームでは特定のモンスターの討伐だったり、はたまたアイテムを村人に届けるなどの所謂おつかいのような物もあります。
クエストの報酬 : ゴールド 10 個とシルバー 5 個など、プレーヤーからすると最も大切なのがクエストの報酬です、これを見てクエストに挑むかどうか決めるプレーヤーも少なくは無いと思います。
クエストの状態 : 達成や未達成という状態です。ゲームによっては達成したものでも再び挑めるクエストもあるでしょう。
他にもクエストには解放条件であったり、制限時間などゲームによって様々な要素を持ったクエストがあるでしょう。今回はよりシンプルに分かりやすくするために上記の 4 つの要素を中心にデータとして管理するクエスト手帳を作ります。
クエスト手帳で管理
クエスト手帳で見られるものもゲームによっては様々です。解放していないクエストは見えない手帳、達成途中にあるクエストはハイライトされている手帳、チャプターや物語の節目に合わせて分けられているものもあります。
今回はクエストの定義に合わせて以下の 4 つのアクションが出来る手帳を作っていきます。
全クエストの一覧が見られる
チャプターによって分かれたクエスト一覧が見られる
特定のクエストを選ぶ事で、そのクエストの報酬を含む詳細が見られる
ゲームのトップ画面で進行中のクエスト一覧が見られる
最後の機能に関してはクエスト手帳からは少し離れますが、プレーヤーがゲームに戻った時にすぐ進行中のクエストの一覧が見えると冒険の意欲が上がるので追加しました。
オンライン対戦機能のついたゲームの場合、クラウド上にサーバーを置くことは必然となり、またオフラインメインのゲームでもクエスト手帳をクラウド上に保存し管理することでプレーヤーのデータが守られ、マルチプラットフォームな環境でも簡単にプレーヤーは遊ぶ媒体を変更することができます。
このように様々な理由でゲームのクラウド化を考える方は少なくないでしょう。管理の少ない DynamoDB だからこそ、普段は手をかけることなくプレーヤー体験を向上させることができるというのも大きな利点になります。皆さんもそんな DynamoDB という武器を持った冒険者になって、ゲームをあっという間に作っちゃいましょう !
ゲームスタート !
前置きが少し長くなりましたが、いよいよ作業を開始したいと思います !
DynamoDB のテーブル設計をしていく際、"アクセスパターンからテーブルは設計する” といったアドバイスを聞いたことのある方も多いでしょう、実際に公式ドキュメントにも 2 つの重要な概念 の 1 つとして紹介されています。いまいちこの概念の意味が理解できない方や初めて聞いたという方も安心して下さい ! ここからは皆さんが慣れているゲーム開発のフローに合わせて紹介させていただきます。
テーブル設計までの流れ
ゲームに置けるアクセスパターンとはずばり、ゲームデザインです。ゲームデザインとはゲームを面白くさせるために様々なアイディアを出しどんなゲームになるか設計していく作業のことです。テーブルを設計していく前にまずは、何を管理するのか、どんな情報が必要か、その情報を取得したり付与するゲームの UI/UX はどんなものになるのか、その UI/UX を支えるのに必要な API はどんなものになるのか、といった内容を考え決定した後にテーブル設計に取り組みます。

ゲームデザインを決める
テーブル設計
テーブル完成
これらを踏まえて、以下のようなクエスト手帳のテーブル Quests が完成します ! 例となるデータも入れてみました。

GSI の確認
2 つ GSI を設定したので、それぞれがゲームデザインを満たしているか確認します。
まずは 1 つ目の GSI である ChapterIndex です。ChapterIndex では PK がプレーヤーの ID、SK がチャプターとし、属性の射影 は INCLUDE とし今回のチャプター詳細画面に必要な title と status を射影する属性として追加します。なお、元テーブルのパーティションキーとソートキーは常にインデックスに射影されるため、以下の図には記載がありません。
完成したインデックスは以下のようになります。今回の例では射影する属性を減らしストレージのコストを抑えたやり方を選んでいますが、ゲームデザインが定まらない内は ALL に設定しクエリ時に ProjectionExpression 側で属性を絞ることも可能です。この属性を絞る ProjectionExpression は SQL でいう SELECT 句に似たものになります。

全てのクエストを取得
このインデックスを使い、ある 1 人のプレーヤーのあるチャプターの全てのクエストを取得する API を構築することで、ゲームデザイン内のチャプター詳細画面を完成させることができます。実際に使うクエリの中身は以下のような内容になるでしょう。以下のサンプルコードは Typescript を使用して記載しています。
const queryInput: DocumentClient.QueryInput = {
TableName: "Quests",
IndexName: "ChapterIndex",
KeyConditionExpression: "playerId = :playerId and chapter = :chapter",
ExpressionAttributeValues: {
":playerId": "player1",
":chapter": "Mountain",
},
};
const result = await dynamodb.query(queryInput).promise();
クエリの内容について
クエリの内容を説明すると、上から TableName でテーブルの指定、 IndexName を使い GSI の指定、そして KeyConditionExpression の中で PK と SK を指定する事で ChapterIndex に対して特定のプレーヤーとチャプターで情報を取得しています。
ExpressionAttributeValues とは KeyConditionExpression の文字列の中にプレースホルダー ( : で始まる値) として定義した属性値を比較する実際の値、つまりランタイムまで分からない可能性のある値の代わりに指定する際に使うものです。今回は特定の値が決まっていますが、例として記載しています。
次に 2 つ目の GSI である StatusIndex です。StatusIndex では PK がプレーヤーの ID、SK がクエストの状態を示す status とし、こちらも属性の射影は INCLUDE とします。トップ画面では title と chapter のみの表示になるので、この 2 つの属性のみ射影します。

進行中クエストを取得する API を構築
このインデックスを使い、status を inProgress に指定することで、ある 1 人のプレーヤーの全ての進行中クエストを取得する API を構築することができ、ゲームデザイン内のトップ画面を完成させることができます。以下はこのインデックスへのクエリの例です。
const queryInput: DocumentClient.QueryInput = {
TableName: "Quests",
IndexName: "StatusIndex",
KeyConditionExpression: "playerId = :playerId and #status = :status",
// status は reserved kyword なのでExpressionAttributeNames で定義する
ExpressionAttributeNames: {
"#status": "status",
},
ExpressionAttributeValues: {
":playerId": "player1",
":status": "inProgress",
},
};
const result = await dynamodb.query(queryInput).promise();
テーブル設計完了 !
2 つ目のクエリは最初のクエリととても似ています。 1 つだけ追加されている物が ExpressionAttributeNames です。コメントとして記載していますが、DynamoDB は status という言葉を reserved keyword として保持しているので、今回のような属性の名前に使ってしまっている場合は KeyConditionExpression 内で属性名用のプレースホルダー( # で始まる値) を使用し、ExpressionAttributeNames 内でそのプレースホルダーの本当の値を定義します。
ここまでレベル 1 の冒険お疲れさまでした !
これで UI/UX からデザインしたクエスト手帳を構築するのに必要な DynamoDB のテーブル設計が見事に完了しました!
拡張性
レベル 2 を目指している冒険者の方は、今回行わなかったチャプターの管理も同じ DynamoDB のテーブルで行うことに挑戦してみてください。クエリのキー条件式である begins_with を使うことで、同じテーブルの中に種類が異なる複数のデータを入れておくことができます。
キー条件式の詳しい内容がみたい方、そしてゲームを作るためにもっとレベルアップを目指している方は 魔法で作る Amazon DynamoDB の簡単ゲームインベントリ をご覧ください !
まとめ
ゲームの裏側として採用が増えている Amazon DynamoDB を使ったテーブル設計を、ゲームデザインを元にゼロからやってみました。
なお、今回ご紹介した DynamoDB のゲームでの活用例やその他のデータベースに関する様々な情報は、ゲーム開発者の皆さん向けに AWS for Games のゲーム向けデータベース のページでまとめて確認できるようになっています。是非皆さんのゲームにあったデータベースを選んで、楽しいゲーム開発にお役立てください !
筆者プロフィール

Sheng Hsia Leng
アマゾン ウェブ サービス ジャパン合同会社 ソリューションアーキテクト
ゲーム業界に特化したソリューションアーキテクトとしてお客様を支援しております。
RPG とドット絵ゲームが好きです。オフモードの時はインスタでバイリンガル漫画を投稿しています。

石井 宇大
アマゾン ウェブ サービス ジャパン合同会社
ゲームデベロッパーリレーションズ
カナダ🇨🇦 で 10 年以上生活していたゲームで遊ぶのも作るのも好きな元ゲームエンジニア。趣味は奥さんと季節を感じながら美味しい物を食べ大好きな赤🍷 を嗜む事。
普段は楽しいゲーム作りに繋がるよう、様々なソリューションを提供するお仕事をしています。
Did you find what you were looking for today?
Let us know so we can improve the quality of the content on our pages