Misskey & Webテクノロジー最前線

Misskey Games バブルゲーム編

本連載では分散型マイクロブログ用ソフトウェアMisskeyの開発に関する紹介と、関連するWeb技術について解説を行っています。

今回は最近Misskey内に追加されたゲームである「バブルゲーム」についての実装を紹介します。

Misskey Gamesとは

Misskey GamesはMisskey内に実装されている様々なゲーム機能の総称で、現在ベータ版として利用可能です。当記事執筆時点ではバブルゲーム、リバーシ、麻雀(鋭意開発中)があります。

リバーシについては昔のMisskeyにあった機能で、途中から他機能の開発に集中するため削除されたという経緯があります。しかし復活を望む声が少なくなかったことや、以前よりMisskeyのコードベースが充実しより少ないコストで実装が可能になったことから、今回晴れて復活を果たしました。

これらのゲームはランキング機能などを除けばクライアントのみで完結することも多く、サーバーへの負荷が少ないながらもアクティブユーザーの獲得に繋げやすいという、運営者にとってコスパの良い機能になっています。

今回はそんなMisskey Gamesで最初に追加されたゲームであるバブルゲームの実装について取り上げます。

バブルゲームとは

バブルゲームは一人プレイ専用のゲームで、いわゆる落ちものパズルです。より端的に言うと「スイカゲーム」です。

一般的な落ちものパズルと違って実際に物理演算が行われることが特徴で、落ちてくる「モノ」同士をくっつけながらハイスコアを目指すものになります。落ちてくるモノの形が異なる複数のモードもあり、それぞれ戦略性が異なります。

また、サーバー内でプレイのスコアを集計しランキングを表示する機能もあるので、一人プレイ用ゲームですがハイスコアを他のユーザーと競うことができます。

図1 バブルゲーム。モノ(図では顔の絵文字)の落とす位置を決めて次々に落としていく。同じ種類のモノがくっつくと別のモノに変化してスコアが得られる。モノがハコからあふれないように、モノを融合させてハイスコアを目指す
図1

物理演算

ゲームの肝となる2D物理演算は、matter.jsを使用して行っています。

matter.jsはJavaScriptにおける物理演算ライブラリで最もメジャーなものになると思います。

matterjsの演算は決定論的(deterministic)なので、同じ入力を与えれば必ず同じ出力が得られます(つまり、ランダムな要素がありません⁠⁠。

この「再現性がある」という特性は重要で、後述するランキングにおけるチート対策に必要になります。

matter.jsでは丸や四角といった基本的なシェイプの他に自分で頂点データを渡して複雑な形のシェイプも用いることができる他、コリジョン検出も用意されていて、バブルゲームの実装に活用しています。

ノート

複雑な形のシェイプをそのままシミュレーションで用いると計算負荷が高くなりますので、コリジョン用に簡略化したシェイプを別途用意する工夫も行なっています。

他にも、標準で簡易的なレンダラー実装も含まれているので、すぐにシミュレーション結果を確認することもできます。バブルゲームではこのレンダラーをそのまま利用しています。

ただmatter.jsでは、摩擦の挙動に難がある、シミュレーションが不安定になりやすい、メンテナンスがあまりされていないなどの理由で、バブルゲームではplanck.jsなどの別のライブラリに切り替えることを検討しています。

アンチチートシステム

ランキングを愚直に実装する場合、スコアが自己申告だと実際には出していない高いスコアをAPIに直接送信するチートができてしまいます。

そこでMisskeyのバブルゲームではスコアだけでなく、⁠どのタイミングでどの操作を行ったか」という手順情報と「ゲームの初期値」情報も一緒にサーバーに送ります。

「ゲームの初期値」とは具体的には乱数のシードのことで、シードによってゲームのランダムな要素(次の落ちてくるモノの種類等)が決定されます。

それらの情報を受け取ったサーバーは、サーバー上で申告された手順通りに実際にゲームをプレイし、申告されたスコアと同じスコアになるかを検証します。

その結果、スコアが同じになれば正当な申告と判り、もし違っていれば嘘の申告と判ります。

先述したように物理演算はdeterministicで、ゲームがどのように振る舞うかというのはシードによって一意に決まりますから、シードと手順が同じならば必ず同じ結果が得られます。

また、シードはゲームがプレイされた日時情報を使っているため、サーバー側でゲームがプレイされた日時の検証も同時に行えます。

例えば、シードが古すぎたり未来だったりする場合は受け付けないようにすることで、⁠できたての」結果であることを保証でき、過去のハイスコアデータを改めて新しいものとして送り付けるようなチートも不可能になります。

以上のような仕組みによって、実際に出していないスコアが登録されることを防げます。

ノート

厳密にはそれらの対策を行なってもTAS(Tool-Assisted Speedrun)のように何らかの自動化プログラムを使用して、特定のシードで高いスコアを出せる手順を割り出してランキングに登録させることは可能ですが、それはチートには含めないことにします。
⁠ただ、前述したように許容されるシードの鮮度は限定されていて、あまり解析に時間がかかっても登録できませんのでプログラムを使用したとしても不正の難易度は高いと思われます。)

ただし、すべての申告についてサーバー上でそれらの検証を行うと負荷が高いと思われますので、サンプリングしたり上位のレコードに限って検証するような対応も必要になります。


このような「サーバー上でゲームを実行する」というのを実現するために、バブルゲームのエンジンはヘッドレス、つまりGUIという環境なしで動作するように実装されています。

具体的にはエンジンはゲームの計算を行うだけにし、サウンドやレンダリングなどGUIに関わる処理はエンジンの外で行うような設計にしています。エンジン部分はモノリポ内の別パッケージとして分離されています。

リプレイ

バブルゲームにはリプレイ機能があり、ゲームを始めてからゲームオーバーになるまでの経過をプレイ後に再生できます。

仕組みとしては、上述してきたようにゲームは再現性がありますので、同じ手順を与えれば同じ結果が再生できるというものです。

シミュレーション速度を変更しても結果には影響しないので、再生のスピードを自由に変更する機能もあります。

開発中は、シードに基づかない乱数を用いている箇所があったりして、⁠リプレイが一致しない」というバグも度々発生しました。このようなバグはゲーム開発ではあるあるかもしれません。

サウンド

バブルゲーム内のモノ同士の接触音などの効果音はWeb Audio APIを使って再生しています。

Web Audio APIは「ノード」を複数作成してルーティングを行うことで、音声のパン、ピッチ、ゲインなどを柔軟に操作できるようになっています。

具体的にはそれぞれ以下の方法で実現できます。

バブルゲームでは、右のほうで発生した効果音は右のスピーカーから聴こえ、左のほうで発生した効果音は左のスピーカーから聴こえるようにパンニングしています。

また、リプレイでは再生速度を変更できると紹介しましたが、その再生スピードに合わせて音声のピッチを変更するようにしています。

このような細かいこだわりでゲームのクオリティを高めています。

バブルゲームで使っているAPIとしてはこれだけになりますが、Web Audio APIは他にも様々な音声処理のAPIが用意されていて、やろうと思えばイコライザーや波形のビジュアライザーも作成できるなど、かなり複雑なことも可能です。

まとめ

今回はMisskey Gamesのゲームのひとつである「バブルゲーム」の実装を紹介しました。

ゲームといっても、何かヘビーなゲームエンジンを使って開発されているわけではなく、すべて一般的なWeb技術で実現されています。

そうすることで開発コストも抑えられますし、開発者の学習コストも少なくて済みます。

Misskey Gamesでは今後も新しいゲームを追加していく予定で、既に実装済みのリバーシではオンライン対局機能など技術的なトピックがまだあるので、また実装について解説を行いたいと思います。

おすすめ記事

記事・ニュース一覧