Rill Valley: 関数型言語で計算する、ブラウザのマルチプレイヤー WebGPU の谷
ブラウザで歩ける夕暮れの谷。森、湖、クリスタル、武器、ボイスチャット、ボット。地形、物理、ゲームロジックは Rill で書かれ、同じファイルがブラウザ用の WebAssembly とサーバー用のネイティブプログラムの両方にコンパイルされます。描画は WebGPU です。

Rill Valley は、ブラウザの中を歩ける夕暮れの谷です。松とポプラの森、真ん中に湖、斜面に光るクリスタル、空には土星。何人もの人が同じ谷を歩き回り、話し、撃ち合うことができます。谷そのもの、つまり地形、木やクリスタルの位置、歩く・落ちる動き、弾の飛び方、カメラの行列は、私が作っている関数型言語 Rill で計算しています。描画は WebGPU です。
遊ぶ: rill.baltavista.com/rillwebgpu。WebGPU に対応したブラウザ (Chrome か Edge) が必要です。クリックでマウスをキャプチャ。WASD で歩く、スペースでジャンプ、スペース 2 回でジェットパック、shift で走る、C でしゃがむ、左クリックで撃つ、右クリックで構える、1-4 で武器を切り替え、R でリロード、M で音楽。

1 つのファイルを 2 回コンパイル
谷が何であるかは、すべて 1 つのファイル valley.rill (約 8,800 行) に書かれています。このファイルを 2 回コンパイルします:
world.rillがその上に 56 個の関数を公開し、WebAssembly にコンパイルされます。これがブラウザ内のゲームエンジンで、232 KB です。server.rillがそれを WebSocket サーバーで包み、Linux 用のネイティブプログラムにコンパイルされます。
どちらも、谷が何であるかについては何も決めません。サーバーとブラウザが「弾がどこへ飛んだか」で一致できる理由はそれだけです。両方が同じコードを動かしているからです。
ブラウザ側の JavaScript は、WebAssembly モジュールにできないことだけを担当します。WebGPU デバイスの準備、シェーダーの読み込み、描画パスを順に実行すること。音、マイク、ネットワーク接続もその担当です。
谷の作り方
谷はダウンロードされるのではなく、計算されます。地形は 12 個のサイン波の和です。松、ポプラ、クリスタル、草むらの位置は、ハッシュの上を決定的にたどって選びます。候補、ハッシュ、地面についてのルールがあり、残ったものは落ちた場所に立ちます。サーバーが各クライアントに伝えるのは 3 つの数 (松、クリスタル、草むらの数) だけで、全員が同じ谷を自分で組み立てます。
谷全体の再構築は約 4 ミリ秒です。だからゲームはマシンに合わせて自分を調整できます。グラフィックカードから選んだ品質レベルで始め、測ったフレーム時間を見て、画面のフレーム予算を使い切るまで谷をより密に、あるいはより疎に作り直します (木、草、ホタル、シャドウマップ、波の数、ピクセル比)。遅いマシンは粗い地形メッシュを描きますが、立っている地面はまったく同じです。物理の問いはすべてメッシュではなく高さ関数に対して行うからです。

描画
WebGPU で、それぞれ独自の WGSL ファイルを持つ一連のパスとして描きます:
- 固定されたゴールデンアワーの太陽からのシャドウパス。
- 4x MSAA のフォワードパス、プロシージャルな空と霧。
- インスタンシングで描く松、ポプラ、クリスタル、風に揺れる草。風は水と松と草を同じ 2 つの数で動かします。3 つとも Rill が計算する同じ値を読んでいるからです。
- 岸と波頭の泡を持つ、ゲルストナー波の湖。湖を本当に水らしく見せているのは、ゆっくり流れるノイズ場がすべての波を一度にスケールすることです。水面に波立つ部分と、ほとんどガラスのように平らな部分ができます。
- コンピュートシェーダーで動くホタルの群れ。
スクリーンショットを撮ったマシン (Apple M シリーズ、最高品質) では、シーンは 34 万 2 千ポリゴン、60 fps、1 フレームあたり GPU 4-9 ms です。
テクスチャ、モデル、音、シェーダー、そしてページ自身のコードは、すべて 1 つのファイル valley.rpak に入っています。103 ファイル、6.1 MB。形式はこのプロジェクト独自のものです。縮むものは LZ で圧縮し、展開するのはブラウザではなく WebAssembly のエンジンです。訪問者がダウンロードするのは index.html と valley.rpak の 2 つだけです。
ゲーム
ゲームはブリーフィングから始まります。あなたは未知の惑星に送り込まれた。会社からの任務は、地球にとって脅威と考えられているクリスタルを破壊すること。同じ任務を受けたライバル会社のエージェントに出会うかもしれない。
- クリスタル: 色は 3 種類で、色がすべてを語ります。琥珀色が最も多く、壊すとプレイヤーを 3% 回復、シアンは 6%、紫 (最も珍しい) は 12%。珍しさと価値が同じ順なので、200 メートル先に見える色が谷を横切る理由になります。クリスタルは火も投げてきます。飛び、減速し、落ちて、近くにいる者を焼くクレーターを残す隕石です。
- 武器: ボルトアクションライフル、RPG-7、AK、MG3。ライフル弾は 420 m/s で出て、重力と空気抵抗で減速し続けるので、落下は教科書の放物線より速く大きくなります。構えると視界が 5 倍になり、スコープが画面を覆います。ボルトは撃つたびに 4 つの別々の動きで操作され、空の薬莢は肩越しに飛んでいきます。
- ランチャー: ロケットは最初に当たった固い物で爆発し、6 メートル以内のクリスタルも巻き込みます。装填されたロケットは筒の先に見え、撃つと消え、次を装填するまで戻りません。
- 重力: 定数ではなくサーバーの設定です。谷のデフォルトは 26 m/s² で、地球の約 2.5 倍。脚はどの惑星でも同じ力で蹴ります。重力の弱い惑星で脚が強くなるのではなく、同じ蹴りがより遠くまで運ぶのです。


ほかの人と
サーバーは、接続ごとの strand と、世界を所有する 1 つのハブ strand でできています。世界に触れるのはハブだけで、それ以外はすべてチャンネルなので、サーバーのどこにもロックがありません。ティックもキー入力と同じチャンネルに届くメッセージなので、入力はティックの前か後に処理され、途中に割り込むことはありません。
- 毎秒 20 ティック、バイナリプロトコル。 スナップショットは 31 バイト + プレイヤーごとに 37 バイト。プロトコルが固まるまではテキストでした。バイナリにしたときに文字をメッセージコードとして残したので、hexdump はいまも読めます。スナップショットはいまも
sで始まります。 - クライアントも歩く。 ブラウザはサーバーと同じ
walk関数で自分を動かすので、向きを変えたり歩いたりする動きは往復時間ではなくキーに反応します。サーバーからの位置は数フレームかけてなじませます。照準は決して補正しません。 - クライアントが送るものは何も信じない。 すべての数値が有限かを確認し (
NaNの位置にいるプレイヤーには何も当たらない)、射撃方向は単位ベクトルに直し、弾は持ち主が見ている方向から 20 度以内にしか飛べません。 - 制限: 接続ごとに毎秒 150 メッセージ、同時接続 64、1 つのアドレスから最大 16。
- 名前: プレイヤーは最初の訪問時にトークンを受け取ります。ブラウザがそれを保存し、取った名前は持ち主がいない間も他の人には与えられません。
- 音声: プレイヤー同士で話せますが、音声はサーバーを通りません。2 つのブラウザが自分たちの WebRTC 接続を作り、サーバーは紹介するだけです。
- ガーディアン: 谷には人間ではない装甲ボットも歩いています。斜面を歩き、光が当たると輝き、攻撃するときだけ跳びかかります。
いまは 4 つの谷が開いています (kuzey、güney、doğu、batı: 北、南、東、西)。ページを開くと、どこに入るか聞かれます。それぞれが独自の Docker コンテナで動き、メモリは約 5 MB です。?solo を付けると、誰もいない自分だけの谷が開きます。サーバーはなく、すべてブラウザの中で動きます。

数字で見る
valley.rill (谷そのもの) | 約 8,800 行の Rill |
server.rill (マルチプレイヤーサーバー) | 約 2,300 行の Rill |
host.js (WebGPU のシーン) | 約 4,800 行の JavaScript |
| シェーダー | パスごとに 1 つの WGSL ファイル |
world.wasm | 232 KB |
valley.rpak | 103 ファイル、6.1 MB |
| サーバーあたりのメモリ | 約 5 MB |
クレジット
テクスチャ、モデル、音は CC0 の素材から: テクスチャは Poly Haven と ambientCG、ライフルは Poly Haven、RPG-7 は OpenGameArt、人物とそのアニメーションは Quaternius の Universal Animation Library。音楽は pauliuw の The Field of Dreams (OpenGameArt、CC0)。どれもそのままは使っていません。テクスチャは昼間に撮った写真で、谷は夕暮れなので、それぞれ谷の光に合わせて作り直しました。
Rill Valley はまだデモです。問題なく動いていますが、午後いっぱい撃たれ続ける以上に厳しい試験は受けていません。遊びに来て何か壊れたら教えてください。