(1)はこちら、(2)はこちらから。
マスタデータのテスト
ゲーム運営において必要になってくるのがアイテム定義などを含むマスタデータの管理です。ここでは、マスタデータの管理やテストの手法について述べていきます。
マスタデータはゲームサーバにおいて、ゲームの挙動を決める要素の一つです。プログラムコードと違い、プログラマー以外が入力できる形式で書かれる場合があり、ときには専用の入力ツールや、Unityエディタなどのゲーム開発環境に統合された内製ツールが入力に用いられることがあります。
マスタデータの例
筆者のプロジェクトでは、マスタデータはCSV(Comma-Separated Values、カンマ区切り)の形式で記述され、そのままMySQLのテーブルにレコードとして挿入できる形で表現されます。以下に、ゲーム内アイテムを表現するマスタデータの例を示します。
このアイテムテーブルはid
とname
とeffect_type、effect_parameter
で構成されています。id
は、アイテムを区別するサロゲートキーです。name
はアイテム名です。
アイテムの種類や挙動を決めるのに、effect_type
カラムとeffect_parameter
カラムを用意しています。effect_type
はプログラム内で列挙型として定義され、1は体力回復アイテムを指します。effect_parameter
は、アイテムをeffect_type
に沿って適用するときの引数で、JSON形式で書かれています。SQLアンチパターンで言われているものの一つではありますが、アイテムごとに発揮する効果が違い、設定される値の数や意味も違うため、このようになっています。この場合、小さい体力回復ドリンクは体力を30回復しますが、大きい体力回復ドリンクは体力を100回復します。
ほかにもアイテムのマスタデータには、売却する際にゲーム内通貨としていくらで売れるかなどが定義されます。
実際に使用するときには、上記に示したCSVをitems
テーブルに格納します。このitems
テーブルに対応するユーザーデータのテーブルをuser_items
とします。user_items
テーブルは、サロゲートキーid
、アイテムを所持しているユーザーを示すuser_id
、所持しているアイテムを示すitem_id
、所持数を示すhaving_num
カラムで構成されています。
マスタデータを用いたコード
では、上記のマスタデータを用いて、どのようにゲームの挙動を変化させるかを見ていきます。
回復アイテムの消費と効果をユーザーに適用する関数の例
アイテム消費APIから呼ばれる、アイテム消費関数を考えます。関数は次の処理に分解できます。
実際にアイテム消費と効果適用を行う関数
アイテムを消費して効果を発揮させるconsume_and_effect
メソッドの中身を次に示します。
マスタデータによる効果適用処理の切り替え
前項のコードで示したとおり、効果に応じたクラスをeffect_type
の値に応じて取り出します。この機構についての具体的な実装を示します。
Perlであれば、上記のようにハッシュにeffect_typeに対応するクラス名を保持しておき、クラス名を取り出してインスタンス化するのが、記述量やわかりやすさの面でバランスが良いです。ほかのプログラミング言語ではswitchやパターンマッチなどの記法を用いると思います。
回復アイテムの効果適用の処理例
さらに、MyApp::Model::ItemEffect::RecoverEnergy
内の、実際に体力回復を適用する処理を示します。
マスタデータの内容によって、使用するクラスパッケージを切り替えることにより、マスタデータだけでさまざまな効果を持つアイテムを作れました。
マスタデータをテストする
マスタデータで効果を切り替えできると、自由度が高いことの裏返しで、マスタデータに起因するバグが生まれやすくなります。ほかにも、JSONなどの形でスキーマレスなデータをカラムに格納することで、RDBMS(Relational Database Management System)の型に頼った値域などのチェックも弱くなります。
また、プランナーは必ずしもSQLに明るいわけではありません。どのカラムが何の意味を持っていて、どんな値が入るかは、設計者が説明しなければなりません。
そこで、ドキュメントとマスタデータの形式チェックを兼ねた、マスタデータのテストを記述することが考えられます。
マスタデータのカラムの値域をテストする例
MySQLに入ったitems
テーブルのeffect_type
の値域を、素朴にチェックする例を挙げます。
PostgreSQLなどではCHECK
制約があるため、こういった定義もDDLに記述すればよいのですが、CHECK
制約のないMySQLではテストでカバーします。
また、subtest
の説明や、ここで言うcmp_ok
の説明部分も大事です。筆者のチームでは、このテストをプランナーがCIサーバで実行するため、何が原因でfail
したか理解できるような説明を、普段のテストよりも厚く書いています。
ただ、これではPerlのプログラムそのままで、Perlを読めなければ、fail
するまで何のテストをしているかを読み解くことができません。Perlプログラマー以外にもわかる、ドキュメントとしてのテストを目指すには難しすぎます。
カラムの値域のチェックを宣言的に書く
そこで、筆者が作成したモジュールであるTest::MasterData::Declare
を用いて、宣言的にマスタデータのテストを記述します。
ループなどを用いずに、カラムが満たすべき定義を書きます。これにより、一定の規則さえ覚えてしまえば、マスタデータが満たすべき条件を知ることができます。また、fail
したときのための説明を書かなくても、なぜfail
したかを出力してくれます。
テストするレコードを条件指定してテストする
別の例として、effect_type=1
のときは、effect_parameter.energy
があって、それが1から100までの値であるかを確認するテストを記述してみます。
カラムの値が10刻みかどうかのテストをする
Perlプログラマー以外にも記述がわかりやすいことは、Perlプログラマー以外でもルールを追加できることにつながります。ゲームでは、マスタデータの値がプログラム上は正しくても、ゲームバランスが崩壊していたり、おもしろくない場合があります。そうなる条件は、パラメータと深く向き合っているプランナーのほうが理解しています。また、プランナーが自分でテスト上のルールを追加できれば、ほかの人にコードでルールを伝えられます。
簡単な例として、体力回復アイテムの回復量は10刻みでないとユーザーにわかりにくく、それ以外の値はミスなので禁止するケースを考えてみます。Test::MasterData::Declare
を使用して書くと、
となります。
若干Perlコードが入りましたが、簡単なコードなので、プランナーでもコピー&ペーストで追加できるでしょう。
まとめ
ゲームサーバの運用と開発は、大量のアクセス、大量のデータ、大量のユーザーを扱いつつ、安定的に運用することが大事です。これは気合いだけでは実現できません。誰でも同じ結果になりやすく、引き継ぎやすく、理解しやすいしくみを整えていくことが必須です。
ゲーム以外のほかの種類のサービスにも、通じる部分があると思います。本稿を参考に、より良い開発のためのアイデアを、読者のみなさんが思いついて、サービスに適用していただけたらうれしく思います。
さて、次回の執筆者は水音ぴねさんで、テーマは「CPANモジュールの品質を支えるCI技術」です。
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT