目次
はじめに
この記事では、コードを書く前に考える必要があるマスターデータの扱い方を解説していきます。
- マスターデータとはなんなのか
- マスターデータの設計
#2 では、この記事で作成したマスターデータをコード側でどう扱うのかをPHPを使用して解説します。(https://developers.10antz.co.jp/archives/1925)
マスターデータとはなんなのか
IT用語辞典ではこう書かれています。
マスターデータとは、企業内データベースなどで、業務を遂行する際の基礎情報となるデータのこと。また、それらを集約したファイルやデータベースのテーブルなど。単に「マスタ」と省略するのが一般的である。
ゲームで例えると以下のような情報を持った、データはマスターデータと言われます。
- NPCキャラクターの情報(名前、レベル、HP、攻撃力、スキル、etc.)
- クエストの情報 (出現場所、出現条件、報酬、etc.)
- ダンジョンの情報 (出現場所、出現条件、探索できる階層、階層毎の報酬、etc.)
- ガチャの情報 (排出キャラクター、排出率、イベント/キャンペーン期間、etc. )
同じゲームをする全てのユーザから見て、共通のデータなのが「マスターデータ」です。
プレイヤー毎に、数値や条件が変化するデータは「ユーザデータ、トランザクションデータ」と言います。(所持品、経験値、レベル、スタミナ、etc.)
なぜマスターデータとユーザデータを分けて考える必要があるのでしょうか。
大きな理由としては、以下2つの目的があると個人的に思います。
1つのテーブルで持つデータ量の削減
ユーザデータにマスターデータを付与していれば、複数のデータを参照する必要がないため、使い勝手がよくなります。
しかし、ユーザデータはユーザが行動した分のデータ作成/更新が発生します。
ヒットしているタイトルであればそのデータ量は膨大となり、参照するだけでも時間がかかる処理となってきます。
これらをストレスなく処理するには、強力なスペックを持ったマシンが必要になるのがわかるかと思います。
このような事態が発生しないよう、ユーザデータとマスターデータは分けて持ち、データの増加を抑えることで運用コストを下げることができます。
マスターデータを更新する際の利便性向上
もし、ユーザデータとマスターデータが一緒になっていた場合、マスターデータの更新が大掛かりになります。
例えば、ダンジョン情報のマスターデータに変更を行いたい場合、このダンジョン情報を含んだ全てのユーザデータを更新する必要があります。
ゲームの運用上、これ程多くのユーザデータを1度に直接操作する場合、メンテナンス対応が必須です。
マスターデータとユーザデータを分けて管理することで、マスターデータを更新したときの安全性の向上と即時反映が可能になります。
マスターデータの管理方法
Webベースのゲーム
サーバー内でプログラムが全て動作する為、マスターデータは基本的にサーバーにだけ設置します。
その為、常に同じ状態のマスターデータをクライアントとサーバーの両方から参照できます。
デバック/テストを行なった後にデプロイするだけでマスターデータの追加/修正が本番に即時反映される感じです。
Nativeベースのゲーム
クライアントとサーバーでプログラムを動作させる環境が異なります。
Webベースのようにサーバーにだけマスターデータが存在すると、クライアントがマスターデータを参照したい時にサーバーとの通信がその都度発生します。
クライアントにマスターデータを持たせると、サーバーへの通信を減らすことができるので、マスターデータはクライアントとサーバーどちらにも設置します。
こうした場合、クライアントとサーバーの両方にあるマスターデータの状態は常に同じにしなければなりません。
クライアントとサーバーのマスターデータに差分があると以下のような問題が発生する可能性があります。
例:
アイテム購入に必要なゲーム内通貨消費数のマスターデータが、サーバーで200個、クライアントで100個と設定されていた場合。
ユーザは100個で購入しているが、実際に差し引かれたゲーム内通貨は200個になってしまった。
解決策として、マスターデータのバージョン管理を行い、両方を同じバージョンにすることで不一致を回避します。
マスターデータの設計
マスターデータをどのデータ形式として持つかは、会社やプロダクトによって様々だと思いますが、この記事ではCSVで編集することを想定して記述していきます。
以下の仕様を元にダンジョン探索のマスターデータを設計してみます。
- ダンジョンにはエリアが3つ存在する
- ダンジョンにはエリア毎に4つの階層が存在する
- 各階層毎に1体モンスターを設置する
- 階層クリア毎に報酬が貰え、エリアクリア時にはより豪華な報酬を貰える
- 各エリアは運営が決めた日付が過ぎるまで探索できないようにしたい
まずはざっくりと、これらの仕様を満たせそうなCSVのカラムを考えます。
id(主キー)
area(エリア)
floor(階層)
bossMonster(ボス)
areaClearReward(エリアクリア報酬)
areaClearRewardNum(エリアクリア報酬の付与個数)
floorClearReward(階層クリア報酬)
floorClearRewardNum(階層クリア報酬の付与個数)
areaReleasedAt(エリア開放時間)
データを入力したのがこちらになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
id,area,floor,bossMonster,areaClearReward,areaClearRewardNum,floorClearReward,floorClearRewardNum,areaReleasedAt 1,1,1,"Alice","ガチャチケット",1,"スタミナ回復薬",10,"2021/01/01 00:00:00" 1,1,2,"Bob","ガチャチケット",1,"スタミナ回復薬",10,"2021/01/01 00:00:00" 1,1,3,"Carol","ガチャチケット",1,"強化素材★1",5,"2021/01/01 00:00:00" 1,1,4,"Eve","ガチャチケット",1,"虹石",5,"2021/01/01 00:00:00" 1,2,1,"Alice","ガチャチケット",1,"スタミナ回復薬",10,"2021/02/01 00:00:00" 1,2,2,"Isaac","ガチャチケット",1,"スタミナ回復薬",10,"2021/02/01 00:00:00" 1,2,3,"Ivan","ガチャチケット",1,"強化素材★1",5,"2021/02/01 00:00:00" 1,2,4,"Zoe","ガチャチケット",1,"虹石",5,"2021/02/01 00:00:00" 1,3,1,"Alice","ガチャチケット",1,"スタミナ回復薬",10,"2021/03/01 00:00:00" 1,3,2,"Carol","ガチャチケット",1,"スタミナ回復薬",10,"2021/03/01 00:00:00" 1,3,3,"Isaac","ガチャチケット",1,"スタミナ回復薬",10,"2021/03/01 00:00:00" 1,3,4,"Eve","ガチャチケット",1,"強化素材★1",5,"2021/03/01 00:00:00" 1,3,5,"Zoe","ガチャチケット",1,"虹石",5,"2021/03/01 00:00:00" |
ここから、このデータを正規化します。
正規化とは(wiki引用)
データ等々を一定のルール(規則)に基づいて変形し、利用しやすくすること。 別の言い方をするならば、正規形でないものを正規形(比較・演算などの操作のために望ましい性質を持った一定の形)に変形することをいう。
まずは、なぜ正規化が必要なのかを考えてみましょう。
開発を進行していく過程で、仕様の変更は結構頻繁に行われます。
上記CSVでエリア報酬とエリアの開放時間に変更を入れたい場合、エリアに紐づいた各階層分のレコードを修正する必要があるのがわかるかと思います。
以下のように仕様を変更
エリア1のクリア報酬を 虹石 × 100
エリア1の開放時間を 2021/02/01 00:00:00
1 2 3 4 |
1,1,1,"Alice","虹石",100,"スタミナ回復薬",10,"2021/02/01 00:00:00" 1,1,2,"Bob","虹石",100,"スタミナ回復薬",10,"2021/02/01 00:00:00" 1,1,3,"Carol","虹石",100,"強化素材★1",5,"2021/02/01 00:00:00" 1,1,4,"Eve","虹石",100,"虹石",5,"2021/02/01 00:00:00" |
※ areaClearReward、areaClearRewardNum、areaReleasedAt を修正
整合性を保つために、エリア1に紐づいた4階層分のレコードを修正する必要が出てきました。
今回のケースだと4件のレコードを修正するだけで大丈夫ですが、もしこれが50件、100件だった場合、本当に全て修正できているかどうか不安ですよね。
実際、業務でこのような修正を行った後にバグが発生するケースは少なくないと思います。
正規化を行うことで、これらの重複データを排除できるためデータの不整合が起きるリスクを減らすことができます。
それでは、実際に正規化していきます。
まず、主キーになり得そうなカラムを探します。
今回のケースだと以下が該当します。
area(エリアが特定できる)
area と floor(エリアの階層が特定できる)
これを元にCSVを再度設計し直します。
1 2 3 4 |
area,areaClearReward,areaClearRewardNum,areaReleasedAt 1,"虹石",100,"2021/02/01 00:00:00" 2,"ガチャチケット",1,"2021/02/01 00:00:00" 3,"ガチャチケット",1,"2021/03/01 00:00:00" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
area,floor,bossMonster,floorClearReward,floorClearRewardNum 1,1,"Alice","スタミナ回復薬",10 1,2,"Bob","スタミナ回復薬",10 1,3,"Carol","強化素材★1",5 1,4,"Eve","虹石",5 2,1,"Alice","スタミナ回復薬",10 2,2,"Isaac","スタミナ回復薬",10 2,3,"Ivan","強化素材★1",5 2,4,"Zoe","虹石",5 3,1,"Alice","スタミナ回復薬",10 3,2,"Carol","スタミナ回復薬",10 3,3,"Isaac","スタミナ回復薬",10 3,4,"Eve","強化素材★1",5 3,5,"Zoe","虹石",5 |
これでエリア毎の設定とフロア毎の設定を分離することができました。
エリアに対する仕様変更/修正が入っても1件のレコード修正で良くなったことがわかると思います。
CSVでマスターデータを持つ時のポイントとしては、RDBMSのテーブル設計のように詳細な正規化はやらない方が良いなと個人的に思います。
プログラム側で使用する言語やフレームワーク/ライブラリにもよるとは思いますが、CSVのデータはDBのデータと違い自由度の高いクエリを投げて簡潔にデータを持ってこれないことが多いので、参照するファイル量をできるだけ減らしプログラム側でデータを整形した方が楽だと思います。
まとめ
今回解説した内容は、エンジニアだけでなくプランナーの方も知っておいた方が良い知識になってくると思ったので記事にしてみました。
ゲームの実装をしていて仕様変更は本当によく起こることなので、修正の少ないマスターデータの設計は必須になってくると思います。