- Web
- Game
- Graphics
ドパ書き
画面に「ド」と書けばガード、「パ」と書けば波動砲。手書き文字で戦う対戦ゲーム。着弾の直前に書いたドは反射になり、達筆なパほど威力が上がる。CPU戦に加えて、合言葉やランダムマッチでのオンライン対戦と、ニックネーム付きの勝利数ランキングにも対応。WebGL2で描いた。

指やマウスで画面のどこにでも「ド」か「パ」を書いて、CPUやほかのプレイヤーと撃ち合うブラウザの対戦ゲームです。「パ」はMPを使って波動砲を撃ち、「ド」は六角形のシールドを張って相手の波動砲を防ぎます。シールドを張ってから0.3秒以内に受けると「PERFECT 反射」になり、速く強くなった波動砲が相手に跳ね返ります。反射された側も反射し返せるので、ラリーが続くほど波動砲は金色に強くなっていきます。お互いの波動砲がぶつかると押し合いになり、威力が同じくらいなら相殺、差があれば強い方がそのまま押し切ります。手書きの認識には、書き順や画数に左右されない$P Point-Cloud Recognizerを自前で実装しました。「ド」「パ」それぞれ数通りのお手本から、回転や伸び縮み、ゆがみを加えた変形を作って照合し、「長い縦線がある」「小さな丸がある」といった形のヒントで補正しています。手で崩したような線を機械的に作った300件のテストでは約99%を正しく読み分けました。お手本との近さは「達筆度」として表示され、波動砲の威力になります。描画は3Dライブラリを使わずWebGL2とGLSLで直接書きました。日輪と星雲、ネオンの床グリッドを1枚のシェーダーで描き、キャラクター・波動砲・シールド・火花はすべてインスタンス描画のスプライトをシェーダーで描き分けています。HDRで描いた画面にブルーム、トーンマップ、色収差をかけ、着弾時にはヒットストップと画面の揺れを入れています。効果音はWebAudioでその場で合成し、技名とK.O.・WINの声は自作の音声合成ツールで作った音声を切り出して使っています。BGMは自作のBGM生成ツール(BGM Forge)で、EDMにヨナ抜き音階を合わせた継ぎ目なしのループ曲を作りました。オンライン対戦は、Cloudflare WorkersのDurable ObjectsでWebSocketの部屋を作り、4文字の合言葉ごとに2人をつないでいます。ランダムマッチを選ぶと待合室で相手を待ち、そろった時点で新しい合言葉が発行されます。当たったかどうかは撃たれた側の端末が自分のシールドの状態で判定して相手に知らせ、ビーム同士の相殺だけは部屋を作った側が判定する、という分担にして、通信の遅れがあっても判定が食い違わないようにしました。相手から届いたビームは通信にかかった時間のぶん先へ進めて表示しています。結果はXでシェアでき、部屋の合言葉入りの招待リンクもXで送れます。ニックネームを登録すると、オンライン対戦の勝利数がランキングに載ります。ニックネームは全角・半角や大文字・小文字の違いも同じ名前とみなして、重複できないようにしています。勝敗は端末からの申告ではなく対戦部屋のサーバーが見届けて記録し、対戦中に抜けた側は負けになります。同じ相手との対戦を数えるのは1日3回までにして、勝利数の稼ぎを防いでいます。
「ド」と「パ」を読み分ける
手書きの認識には、書き順や画数に左右されない$P Point-Cloud Recognizerを自前で実装しました。書いた線を40個の点に並べ直し、大きさと位置をそろえてから、お手本の点の集まりとどれだけ近いかを比べます。お手本は「ド」「パ」それぞれ4通りの書き方を用意し、回転・伸び縮み・ゆがみを加えた変形を5つずつ作って、合計48個と照合しています。
点の集まりだけで比べると、雑に書いた字では迷うことがあります。そこで「縦に長い線がある」「小さく閉じた丸がある」「短い点が2つある」といった形のヒントを取り出して、距離を補正しています。どちらにも遠いとき、または2つの差が小さすぎるときは「読めない」として、誤って発動しないようにしました。
- 最後の一画を書き終えて0.4秒たったら読み取る
- 濁点を書かずに「ト」と書いても「ド」として読む、ゆるめの判定
- お手本との近さを「達筆度」として表示し、波動砲の威力に反映
書く時間を前提にした駆け引き
「ド」を書くには1秒ほどかかるので、相手の波動砲は「パ」の文字が浮かんで溜めに入ってから着弾まで2秒前後かかるようにしています。見てから書いても間に合いますが、ギリギリまで待って書くと反射になる、という読み合いが生まれます。
CPUも人と同じルールで動き、難しさによって撃つ間隔、ガードする確率、反射を狙う確率、波動砲の速さが変わります。
画面の描き方
背景の日輪・星雲・ネオンの床グリッドは1枚のフラグメントシェーダーで描いています。キャラクター・波動砲・シールド・火花・衝撃波は、1種類のインスタンス描画に種類の番号を持たせ、シェーダーの中で描き分けています。
画面はいったんHDRの画像に描き、明るいところだけを縮小しながらぼかして足すブルームで光をにじませ、トーンマップと色収差、ビネットをかけて仕上げています。着弾の瞬間はヒットストップで一瞬止め、画面を揺らしています。
オンライン対戦のしくみ
サイトを配信しているCloudflare WorkerにDurable Objectを1つ足し、4文字の合言葉ごとに1つの部屋を作っています。部屋は2人までで、届いたメッセージをもう1人へ中継するだけです。誰も話していない間は眠るWebSocket Hibernation APIを使っています。ランダムマッチは名前の決まった待合室の部屋で、2人目が来た時点で新しい合言葉を発行して両方をそこへ案内します。
通信には必ず遅れがあるので、判定は「撃たれた側の端末」が行います。自分のシールドがいつ張られたかを正確に知っているのは本人だけなので、命中・ガード・反射を自分で決めて、結果とHPを相手に送ります。撃った側のビームは、返事が届くまで着弾した位置で待ちます。ビーム同士の相殺だけは両方の端末で見え方がずれるため、部屋を作った側が判定して相手に知らせます。
- 相手から届いたビームは、片道の遅れのぶん先へ進めて表示する
- 相殺の知らせが自分のビームより先に届いたときは、ビームが出るまで少し待ってから相殺する
- 再戦は2人とも「もう一戦」を押した時点で、部屋から同時に開始の合図が出る
ニックネームと勝利数ランキング
ニックネームとランキングは、もう1つのDurable Objectに入れたSQLiteで管理しています。ニックネームはNFKC正規化(全角英数を半角に、など)してから小文字にしたものを一意のキーにしているので、「Wakame」「wakame」「wakame」は同じ名前として扱われます。アカウントの代わりに、登録時にランダムな鍵を発行してブラウザに保存し、サーバーには鍵のハッシュだけを残しています。
勝利数は、ブラウザから「勝ちました」と送らせる方式にすると簡単に書き換えられてしまいます。そこで、対戦部屋のサーバーが「KOを知らせてきた側」「対戦中に接続が切れた側」を負けとして記録するようにしました。2人ともニックネームを登録している対戦だけを数え、同じ2人の対戦は1日3回までしか数えないので、捨てアカウントで勝ちを積み上げることもしにくくしています。