ソーシャルゲームにおけるマスタ設計～ガチャ編～

本記事は[サムザップ #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/sumzap1) の12/25の記事となります。

##はじめに
Sumzapでサーバエンジニアをしています@chrno001です。

ソーシャルゲームにはゲームを動かす為に様々なマスターデータが存在します（キャラクターや敵の基本となるステータスの値だったり、武器の名前や攻撃力、防御力だったり）これらのマスターデータはゲームの内容によって色々と変化しますが、中には大きく変わることなく流用できる（できそうな）マスターデータも多くあるのが実情かと思います。

今回はそのようなマスターデータの１つであると思われるガチャのマスタ設計について考えてみたいと思います。

##ガチャ関連のマスタ設計
![gachagacha.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104363/c84155f0-60db-ab78-dc07-9c99f7c30c25.png)
サムザップではマスターデータをcsvで扱うプロジェクトが多いので、一般的なキャラクターを排出するようなガチャを実装するにあたって普段自分がどのようなcsvを定義しているかを紹介したいと思います。（あくまでも自分が設計する際の1例であって、プロジェクト毎で差異はあるかと思います）

今回想定するガチャの内容としては

- <font color="Red">**排出されるキャラクターのレアリティは★1～★4とする</font>**
- <font color="Red">**有償ガチャ、無償ガチャ、チケットガチャが存在する</font>**
- <font color="Red">**1日にそのガチャを購入できる上限回数が設定できる</font>**
- <font color="Red">**レアリティ保障（たとえば10連中1回★3以上確定など）が設定できる</font>**
- <font color="Red">**ノーメンテでガチャを更新できる</font>**
- <font color="Red">**将来的にローカライズも想定しておく</font>**


上記のようなガチャを構成するマスタデータとして下記のような4種類を定義してみます。


- **ガチャマスタ（gacha.csv)**
- **ガチャ詳細マスタ（gacha_detail.csv）**
- **ガチャ抽選マスタ（gacha_draw_(type)_(0-9).csv)**
- **ガチャテキストマスタ（gacha_text.csv)**


---
**ガチャマスタ（gacha.csv)**
ガチャの種類を管理するマスタ


| フィールド         	| 型     	| PK 	| 必須か 	| 説明                                                                                                                                                                                                                                                                        	|
|--------------------	|--------	|----	|--------	|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------	|
| id                 	| 数値   	| ○  	| ○      	| ガチャID                                                                                                                                                                                                                                                                    	|
| name               	| 文字列 	|    	| ○      	| 表示名(gacha_text.csvのtext_id)                                                                                                                                                                                                                                                             	|
| description        	| 文字列 	|    	|        	| 説明テキスト(gacha_text.csvのtext_id)                                                                                                                                                                                                                                                       	|
| gacha_type         	| 数値   	|    	| ○      	| ガチャタイプ（1:有償ガチャ 2:無償ガチャ 3:チケットガチャなどタイプ分け）                                                                                                                                                                                                        	|
| draw_master_number 	| 数値   	|    	| ○      	| このガチャで使用するガチャ抽選マスタのマスタ名を確定させるための識別子（例）gacha_type = 1, draw_master_number = 0 の場合  → 抽選に使うマスタ名 = gacha_draw_gem_0（例）gacha_type = 3, draw_master_number = 1 の場合  → 抽選に使うマスタ名 = gacha_draw_ticket_1 	|
| rarity_weight_1    	| 数値   	|    	| ○      	| レアリティ★１の排出割合                                                                                                                                                                                                                                                     	|
| rarity_weight_2    	| 数値   	|    	| ○      	| レアリティ★２の排出割合                                                                                                                                                                                                                                                     	|
| rarity_weight_3    	| 数値   	|    	| ○      	| レアリティ★３の排出割合                                                                                                                                                                                                                                                     	|
| rarity_weight_4    	| 数値   	|    	| ○      	| レアリティ★４の排出割合                                                                                                                                                                                                                                                     	|
| sp_rarity_weight_1 	| 数値   	|    	| ○      	| 特別枠のレアリティ★1出現割合                                                                                                                                                                                                                                                	|
| sp_rarity_weight_2 	| 数値   	|    	| ○      	| 特別枠のレアリティ★2出現割合                                                                                                                                                                                                                                                	|
| sp_rarity_weight_3 	| 数値   	|    	| ○      	| 特別枠のレアリティ★3出現割合                                                                                                                                                                                                                                                	|
| sp_rarity_weight_4 	| 数値   	|    	| ○      	| 特別枠のレアリティ★4出現割合                                                                                                                                                                                                                                                	|
| start_at           	| 文字列 	|    	|        	| ガチャ公開開始日時（例：2019/12/25 14:30:00）　期限なしの場合はNULLを設定                                                                                                                                                                                                                                    	|
| end_at             	| 文字列 	|    	|        	| ガチャ公開終了日時（例：2019/12/25 15:00:00）　期限なしの場合はNULLを設定                                                                                                                                                                                                                                    	|

このマスタではガチャの種類（有償ガチャ、無償ガチャなど）と公開日時、それとレアリティ毎の排出割合を定義します。
ガチャの表示名や説明などは将来的にローカライズすることを意識し、このマスタに直接定義せずにガチャテキストマスタ側と関連づけておきます。
draw_master_numberというフィールドの値を使う事により使用するガチャ抽選マスタを切り替えます。こうすることによって1度作った抽選マスタを別のガチャでも再利用したりガチャの中身をごそっと入れ替えたりする事が可能です。
ノーメンテでガチャを更新したい場合は、現在公開されているガチャデータを上書きするような方法だとユーザーがアクセスしてきたタイミング次第で不整合が起こる可能性が否定できません。そこで同じ内容のガチャIDを2つ定義しておいてガチャ公開・終了日時の設定により現在公開中のガチャを切り替えるような方法で対応する事が可能です。
<font color="Red">
※csvなので型という概念はそもそもないですが、バリデーションチェックなどを想定した上でテーブル設計のように型も定義してあります。
※PKはPRIMARY KEY。こちらも概念としてはありませんがこのフィールドにてユニークとなる事を意味しています。
※必須か、が〇の場合は必ず値を設定すること、〇がついてない場合は値を設定しなくてもいい事を意味しています。なお、設定しない場合はNULLという文字列を定義します。</font>


**ガチャ詳細マスタ（gacha_detail.csv）**
ガチャの排出回数など詳細情報を設定するマスタ

| フィールド      	| 型     	| PK 	| 必須か 	| 説明                                                                                                                                                                                                                                                                     	|
|-----------------	|--------	|----	|--------	|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------	|
| id              	| 数値   	| ◯  	| ◯      	| ガチャ詳細ID                                                                                                                                                                                                                                                             	|
| gacha_id        	| 数値   	|    	| ◯      	| ガチャID（gacha.csvのid）                                                                                                                                                                                                                                                                 	|
| consume_type    	| 数値   	|    	| ◯      	| ガチャを実行するために必要な通貨の種類（1：有償通貨 2:無償通貨 3:チケット）                                                                                                                                                                                                                     	|
| consume_num     	| 数値   	|    	| ◯      	| ガチャを回す為の購入費用                                                                                                                                                                                                                                                 	|
| limit_num       	| 数値   	|    	|        	| 購入可能回数（/day）1日の購入制限を設けたい場合に設定する                                                                                                                                                                                                                	|
| emit_num        	| 数値   	|    	| ◯      	| 抽選回数（1連だったら1,10連だったら10を設定）                                                                                                                                                                                                                            	|
| sp_emit_num     	| 数値   	|    	| ◯      	| 0の場合は特別枠の抽選は行わない。1以上の場合は設定された回数分、特別枠の排出確率（gachaマスタのsp_rarity_weight_n)で抽選が行われる事になる。（どのタイミングで特別枠の排出確率が適用されるかはランダム）なお、emit_numより大きな数値を設定しないこと。 	|

このマスタでは、gacha_idで紐づくガチャの詳細情報、ガチャの費用や排出回数（1連や10連など）や1日の購入可能回数を設定します。
consume_typeとconsume_numの2つのフィールドにてガチャの費用を決定します。例えば、consume_typeが3,consume_numが1ならそのガチャを1回回すのにチケットが1枚必要という形です。
sp_emit_numに1以上の値を設定することにより、レアリティ保障が設定できます。例としてはemit_numが10でsp_emit_numが2という場合は、10連ガチャ中2回gachaマスタのsp_rarity_weight_nの方の排出確率にて抽選が可能という想定です。


**ガチャ抽選マスタ（gacha_draw_(type)_(0-9).csv)**
ガチャのキャラクター毎の排出確率（重み）を定義するマスタ

| フィールド 	| 型     	| PK 	| 必須か 	| 説明                                                                                                                  	|
|------------	|--------	|----	|---------------------------------	|-----------------------------------------------------------------------------------------------------------------------	|
| id         	| 数値   	| ◯  	| ◯                               	| ガチャ抽選マスタ                                                                                                      	|
| character_id  	| 数値   	|    	| ◯                               	| キャラクターID（キャラクターマスタに定義されているid、レアリティはキャラクターマスタから取得可能）                                                                                                                	|
| weight     	| 数値   	|    	| ◯                               	| 重み                                                                                                                  	|
| start_at   	| 文字列 	|    	|                                 	| 抽選開始日時（例：2019/12/25 14:30:00）期限なしの場合はNULL                                                           	|
| end_at     	| 文字列 	|    	|                                 	| 抽選終了日時（例：2019/12/25 15:00:00）期限なしの場合はNULL                                                           	|

このマスタではweightによってキャラクターの排出の確率を制御します。
排出するキャラクターを決定するまでの流れとしては、まずガチャマスタの値にてレアリティ抽選を行い、排出するレアリティが決定したらその後にガチャ抽選マスタの重みを見て排出キャラクターを決定します。重みの数値を調整することによりピックアップガチャなど特定のキャラクターが排出しやすいガチャを作ることも可能です。
また、抽選開始、終了日時を設定する事により指定の時間から特定のキャラクターをガチャから排出されるようにしたり排出を終了する事ができます。（コラボ系のキャラクターなどに使う事が可能かと思います）



**ガチャテキストマスタ（gacha_text.csv)**
ガチャにひもづく文言をまとめるマスタ

| フィールド 	| 型     	| PK 	| 必須か 	| 説明           	|
|------------	|--------	|----	|---------------------------------	|----------------	|
| text_id    	| 文字列 	| ○  	| ○                               	|  テキストID              	|
| ja_lang    	| 文字列 	|    	| ○                               	| 文言（日本語） 	|

ガチャに関わる文言はすべてこのマスタにて管理します。
将来的にローカライズする場合はXX_langのフィールドを増やす事により他のマスタのデータを特に変更することなく対応する事が可能となります。

## さいごに
今回のマスタ設計については自分が考えるガチャの1例ですのでこれが正解という訳ではなく要件によって色々変化するものだとは思います。
ただ、車輪の再発明をするのではなく使える知見は有効に活用する事が重要かと思いますので、同じように設計される際の参考になれば幸いです（また、もっと良い設計ももちろんあると思いますので是非意見を頂ければと思います）