Astra は、私が思い描いたものをゲームプレイやビジュアルに落とし込むのがとてもうまくなりました。私は Codex で Astra を使って Void Explorer を開発しています。遠くに見える星まで飛んでいき、異星の地表に降り立つことができる宇宙探検ゲームです。
Void Explorer のプレイ映像をまとめたものです。
ゲームには 2,048 の恒星系があり、プロシージャル生成される惑星は 1 万個を超えます。地球ほどの大きさの惑星もあります。目に映る星はすべてこの宇宙に実在し、目的地に指定できます。遠くに光る点をひとつ選んで、そこまで飛んでいけるのです。
宇宙から惑星に接近し、大気圏に入ってそのまま降下すると、やがて眼下に海岸線が広がります。着陸して宇宙船から降り、辺りを歩いてから再び飛び立つこともできます。この一連の旅を実現するには、スケール、地形、操作、描画の問題を一緒に解決する必要がありました。
開発中に使ったプロンプトをいくつか、読みやすい長さに整えて掲載しています。作りたいものをどう伝え、そこから実装がどう進んだかがわかると思います。
出発点はプレイヤーの体験
最初に伝えたのは、プレイヤーに何ができるようにしたいかでした。
見えるものにはすべてたどり着けるようにしてください。距離は現実に即したものにして、スケールと速度の扱いを工夫し、実際に移動できるようにしてください。宇宙から惑星の大気圏に入り、地表まで飛んでいきたいです。地球ほど大きな惑星も想定しているので、地形のプロシージャル生成と、チャンク単位で描画するレンダラーが必要です。
これで Astra に具体的な制約が伝わりました。星は背景に描いた点では済みません。惑星に近づいた瞬間に別のステージに切り替えるわけにもいきません。実際の距離を移動しつつ、ゲームとして無理なく旅ができるよう、架空の移動手段であるパルス航行とハイパードライブを使うことにしました。
ゲーム本体を作り込む前に、画像生成も使ってビジュアルの方向性を探りました。最初のコンセプトアートは写実的すぎました。次の案は逆にシンプルすぎたので、こんな修正を頼みました。
今度は少し単純になりすぎました。両者の中間を目指したいです。配色を工夫してネオンの光を加え、コントラストを強めて宇宙の奥深さを感じさせてください。宇宙船の周囲に星や宇宙塵が見える状態で、高速飛行中にどんな景色になるか見せてください。
気に入った画像ができたところで、Astra に保存を頼み、ゲーム制作の参考資料にまとめてもらいました。軌道上の飛行、高速飛行、大気圏突入、着陸のそれぞれに、目指す姿を示す画像ができました。次のビルドが狙いどおりか、ずっと判断しやすくなりました。

画像生成で制作したこのコンセプトアートをもとに、ゲームの配色とビジュアルの方向性を決めました。
Astra が提案したアーキテクチャ
制約を決めたのは私で、実装方法を提案したのは Astra です。アプリケーションには TypeScript と Vite を、描画には Three.js を使っています。この構成なら、メッシュ、マテリアル、ライティング、プロシージャル生成するジオメトリをコードから直接制御できます。このゲームのビジュアルは、ほとんどがこれらの要素で決まります。
最初にプレイできるようになった時点では、レンダラーは WebGL2 を使っていました。その後、Three.js が表現の幅を狭めていないか Astra に尋ねました。Astra の提案は、シミュレーションは維持し、レンダラーを Three.js の WebGPU アーキテクチャに移行するというものでした。宇宙、航行、地形の各システムを引き継ぎ、描画層を入れ替えました。
レンダラーでは、Three.js のノードマテリアルと Three.js Shading Language(TSL)を使い、大気、水、光の効果をコードで定義しています。
地形生成は Web Workers で実行します。入力と描画を担当するスレッドとは別に、ジオメトリを準備できるようにするためです。Vitest では、同じ条件で同じ地形を生成できるか、座標計算が正しいか、地形システムが仕様を満たすかなどを検証します。Playwright ではブラウザでゲームを動かしてテストします。私がプレイ感を確かめている間も、Astra はこうした手段で内部の仕組みを検証できました。
開発を通じて目標にしたのは、最初に決めたビジュアルです。惑星を取り巻く大気、環の輝き、恒星から届く色とりどりの光、そして Neon Phosphor エフェクトが、ゲームの見た目を形作っています。このフィルターは、航行情報の読みやすさを保ちながら、レトロなディスプレイの質感を加えてくれます。

Neon Phosphor エフェクトを有効にしたプレイ画面です。
Astra がゲームを調べてプレイできる仕組み
自分でも繰り返しプレイしましたが、Astra にも、私の説明を読む以外に問題を調べる手段が必要でした。ゲームには window.__VOID_EXPLORER__ という小さな JavaScript インターフェースを用意しています。ブラウザテストから呼び出すと、接近中の天体、飛行モード、地形の準備状況、使用中のカメラを確認できます。ドローコール数、三角形数、キューに入った地形ジョブ数、バッファに蓄積された地形データ量など、描画とストリーミングの指標も取得できます。
ゲームには、軌道上、パルス航行、大気圏内の降下、海岸への着陸を再現する、名前付きのテストシーンがあります。Astra は毎回宇宙を横断しなくても、調査に適した地点から再開できます。Playwright はシーンを読み込み、地形の準備が整うまで待ってスクリーンショットを撮り、そのときの内部状態を確認できます。移動の過程を検証するテストは別に用意しました。実際の操作でゲームを進めながら位置や状態の変化を記録するので、着陸シーンを用意するだけで終わらず、着陸操作そのものを検証できます。
これらのテストは、着陸、降船、歩行、セーブと再読み込み、乗船、離陸までをカバーします。衝突判定に使う地表がまだ準備できていなかったり、状態が切り替わる途中で船の位置が突然飛んだりと、スクリーンショットだけでは見つからない問題も検出できます。
私がプレイしているブラウザタブを Astra に調べてもらうこともできました。Aurelia の雲がまだ見えているか尋ねると、その時点の画面をキャプチャして、そこに表示された飛行情報を読み取りました。こうした確認は必要なタイミングで行うもので、Astra が飛行中の全フレームを監視し続けていたわけではありません。
問題を再現し、スクリーンショットと内部状態を調べ、関連するコードをたどって修正し、もう一度検証する。この流れを繰り返しました。別のゲームを作るときも、再現可能なシーンをいくつか用意し、役に立つ状態情報と性能指標を取得できるようにして、主な操作を検証するブラウザテストを早めに組み込みたいと思います。そうすれば、私が見た目や操作感を確かめている間も、Astra が自分で問題を調べ、変更を検証できます。
複数のスケールで表現する宇宙
惑星が何千個あっても、精細なメッシュを何千個も読み込む必要はありません。最初に用意するのは、半径、軌道、大気、シード値といった惑星の定義です。シード値が同じなら同じ地形を再現できます。ゲームは私が近づいている場所の周辺にジオメトリを生成し、後で戻ってきたときには同じ風景をもう一度生成できます。
座標の扱いにも同じように工夫が必要です。光年単位の距離と、船のすぐそばに立つ人とでは、スケールがまるで違います。巨大な座標値をそのまま GPU に渡すと、地表付近で必要な精度が失われます。
Astra は、宇宙の中での物理的な位置と、描画に使う位置を分けました。宇宙を大きなセルに区切り、整数のセル座標と、セル内の小さなオフセットで位置を表します。描画前に観測者の位置を差し引くことで、カメラは常に原点にあり、近くの物体の座標値は小さく収まります。画面に表示する距離や、物体同士の大きさの比率も一貫します。
動くもの同士では時刻もそろえる必要があります。恒星、惑星、衛星は数式で定まる軌道をたどり、レンダラーは観測者と同じシミュレーション時刻で各天体の位置を求めます。着陸した船は、惑星が自転しても地表の同じ場所にとどまります。恒星の位置と色はライティングにも使うので、連星系の夕空に沈む 2 つの太陽は、軌道上から見えていた星そのものです。
宇宙から地表まで途切れない降下
Void Explorer で宇宙から衛星の地表に向かって飛行する様子です。
宇宙から地表まで自然に移動できるようになるまで、何度も調整を重ねました。浅い角度で惑星に近づいたときには、こんなプロンプトを書きました。
惑星の地表とほぼ平行に進入すると、まだ速度が出すぎることがあります。地形の読み込み中には、惑星がほとんど見えなくなります。速度曲線と地形のストリーミングを一緒に直してください。遠景用の LOD から詳細な地形に切り替わるところを、大気の表現でなじませられますか? 接近中に惑星が消えることは絶対にないようにしてください。
現在の飛行制御では、浅い角度での進入に専用の速度上限を設けています。高度と、惑星の半径と重力から推定した軌道速度をもとに決める上限です。地表に近づくと、進行方向の地形もサンプリングして減速の強さを調整します。
ゲームでは複数の詳細度(LOD)を使い分けます。遠くの惑星は、プロキシと呼ばれる、描画負荷の低い多面体状の球で表現します。画面に占める割合が大きくなるにつれて、レンダラーは細部を描き足します。地表に近づくと、立方体の 6 面を球に投影して作った地形が有効になります。それぞれの面を 4 つの小さな正方形に分け、さらに同じ分割を繰り返せる四分木構造です。
こうすると、地球ほどの大きさの惑星の地表全体を、徒歩で見て回るときに必要な解像度で生成せずに済みます。見えている範囲と進行方向の地形だけを細かくできます。地表付近では局所的なパッチを使い、船とプレイヤーの周囲にさらに細かなジオメトリを用意します。
どの表現も、同じ惑星データをサンプリングしています。共通の関数で大陸、山の稜線、クレーター、細かな起伏を組み合わせ、標高、水域、バイオームなど、景観を作るための情報を出力します。粗いメッシュと細かいメッシュは同じデータをそれぞれの解像度で近似します。配色のルールも共通にして、惑星の見た目をそろえています。
地形の生成と同じくらい大切なのが、表示の切り替えです。粗い地形の 6 面がすべてそろうまでは、遠景用の惑星を表示し続けます。地形タイルも、4 枚の子タイルがすべてそろうまでは表示したままにします。切り替え中は、互いに補い合うピクセルマスクを使い、各ピクセルを古い地表か新しい地表のどちらかに割り当てます。これなら、次の地表を読み込んでいる間に惑星全体が透けてしまうことはありません。
大気の見え方は実際の高度に応じて変わり、雲は惑星の地形の上を流れます。そのおかげで、軌道上からの眺めと地表の風景が自然につながります。レンダラーが粗い地形を取り除くのは、細かな地形の準備ができた範囲だけです。それでも、より細かなジオメトリが現れると、山の輪郭が変わることはあります。
着陸には、さらに厳しい条件があります。接地する範囲では、画面に見える地面、衝突判定用の三角形、危険な液体の領域がすべて同じ回の生成結果に基づき、同時に使えるようになっていなければなりません。歩行には、小さなローカル物理空間で Rapier を使います。船の着陸判定はこれとは別に、着陸脚の接地状態、地面の傾斜、船体と地面の間隔を確認します。
ここでは意図的に制約を設けています。惑星にはハイトフィールドを使い、中心から地表に向かう各方向に対して標高をひとつだけ持たせています。広大な景観には向いていますが、洞窟やせり出した岩、破壊できるトンネルは表現できません。
描画に時間がかかるフレームの処理負荷の計測
同じ飛行中に見た目と性能の両方の問題に気づいたときは、まとめてフィードバックしました。
宇宙から地表に向かって飛んでいるとき、惑星をどう読み込んで描画しているか調べてもらえますか? 遠くから見た球体と詳細な地形で見た目がそろっていません。特に色が変わると、切り替わりが唐突に見えます。見た目が急に変わる原因と、細部を読み込む処理のどこを軽くできるのかを知りたいです。降下中の景色を改善して、動きも滑らかにしたいです。
Astra は Three.js のレンダラーのカウンターを使い、ドローコール、三角形、ジオメトリ、テクスチャの数を調べました。Playwright のテストではフレーム間隔を 90 回計測し、各カウンターの値とともに平均値とパーセンタイル値を記録しました。見た目の問題については、特定の効果を有効にした場合と無効にした場合で、描画されたピクセルを比較するテストもできました。たとえば、重なった地面を隠すマスクの有無で地形の描画結果を比べます。これで、不具合が起きた場面のスクリーンショットだけに頼らず、地面が消える原因を切り分けられました。
以前、宇宙船を作り直したときにも、こうした計測が役に立ちました。プロシージャル生成した船を、Blender で制作した初代 AURORA モデルに置き換えると、シーンの三角形数は増えた一方で、ドローコール数は減りました。Astra が WebGPU バックエンドを使い、変更前後に軌道上で同じ 90 フレームのテストを実行した結果です。
| 計測項目 | 変更前 | 変更後 |
|---|---|---|
| シーン全体のドローコール数 | 119 | 77 |
| シーン全体の三角形数 | 48,809 | 61,092 |
| 平均フレーム間隔 | 251.48 ms | 199.26 ms |
この比較にはヘッドレス Chromium と SwiftShader によるソフトウェアレンダリングを使いました。後で紹介する 4 枚翼の船より前のテストです。数値が示すのはそのテスト環境での変化で、私の GPU でのフレームレートではありません。これらのツールを使うと、Astra はブラウザで計測したフレーム間隔、描画結果の画像、リソース数を取得できましたが、個々の GPU コマンドやシェーダーの実行時間までは計測していません。
Astra は読み込み中と降下中の処理も調べました。生成したジオメトリの量、ワーカーとレンダラーの間で転送したデータ量、結果が使われる前に地形ジョブが破棄された頻度を確認しました。
変更のひとつは、遠くの惑星の詳細度を画面上の大きさに応じて決めることです。視界いっぱいに広がる惑星には、最初から整った輪郭が必要です。1 ピクセルにも満たない惑星に、同じだけのジオメトリは要りません。開始シーンの条件をそろえて比較すると、プロキシに使う三角形は従来の 51,200 個から、新しい方式では 7,040 個になりました。ジオメトリを生成する仕組みはどちらも同じなので、読み込み方の違いだけを評価できました。
別の改善では、地形の三角形数をまったく変えずに転送量を減らしました。インデックス付きメッシュでは、隣り合う三角形が共有する頂点を再利用するので、位置、色、法線のデータを重複して持たずに済みます。地形品質を「高」にしたテストでは、地表の 6 レイヤー全体で 241,952 個の三角形を維持しながら、転送するバッファの合計サイズが約 35 MB から 15 MB に減りました。地面と景観を飾る要素も別々のジョブに分けたので、装飾を待たずに地面のジオメトリを使えるようになりました。
地形を選択する処理にも無駄がありました。チャンクを要求した後に選択が変わり、それを破棄して代わりのチャンクを要求することがあったのです。Astra は細分化の判断が頻繁に変わらないようにし、4 枚の子タイルすべてを処理できる余裕があるときだけタイルを細分化するようにしました。ワーカーの遅延を固定したシミュレーションで、6 秒間降下してから 3 秒間静止して処理が落ち着くのを待つと、破棄されたジョブは従来の 6,074 件から 13 件に減りました。

ワーカーを 2 つ使い、ワーカーの遅延を 100 ms に固定したシミュレーションでは、スケジュール後に破棄されたジョブが 6,074 件から 13 件に減りました。計測対象は地形生成のスケジューリングで、フレームレートではありません。
ワーカーを使うだけでは、すべての引っかかりは解消できません。メインスレッドで地形を生成するフォールバック処理にも、途中で他の処理に実行を譲る仕組みが必要でした。Astra は生成処理を中断・再開できるステップに分割し、1 フレーム当たりの処理時間の目安を 4 ms に設定しました。これで、大きな地形ジョブを一気に最後まで処理せずに済みます。レンダラーではゲーム世界の描画解像度にインターフェースとは別の上限を設けているので、画質を下げても航行情報の文字はくっきり表示されます。
これらの地形の計測から、ジオメトリ、転送データ量、破棄される処理が減ったことがわかります。実機のフレームレートを測るベンチマークではありません。シェーダーのコンパイルや GPU へのアップロードの影響、動きの滑らかさ、地形が切り替わるときの見え方を確かめるには、やはりブラウザで実際にプレイすることが大切です。
私が伝えたのは、プレイしてどこで遅さを感じたか、何が不自然に見えたか、何を改善したいかです。Astra はコードをたどり、同じ条件で繰り返せる地形テストをスクリプトにして、計測結果を記録しました。私が方針を了承すると、Astra は変更を実装し、同じテストを再実行して結果を比較しました。調査や実装の手順を私がひとつずつ指定する必要はなく、その間もゲームの見た目と操作感を確かめることに集中できました。
コンセプトアートからゲームアセットへ
この宇宙を構成する惑星、地形、雲、環、恒星は、ほとんどがコードで生成されます。一方、プレイヤーの宇宙船は個別に制作した主要な 3D アセットで、作り方も変えました。
ゲームに組み込む前に、画像生成で作ったコンセプトアートと Blender のレンダリング画像で船のデザインを確認しました。翼の形がイメージと違ったときは、モデル制作に使いやすい参考画像を頼みました。
この宇宙船の形がよくわかる画像を、いくつかの角度から生成してください。特に前後の翼に注意してください。その画像を使って 3D モデルを作り直します。今のモデルの、前後がつながった丸みのある翼は好みではありません。
私が選んだのは、4 枚の翼がそれぞれ独立した左右対称のデザインで、翼端にピンクのライトが付いた案です。その画像をもとに、ゲームのアートスタイルに合わせて Blender で新しいモデルを作り、ゲームに組み込むよう Astra に頼みました。
参考画像からジオメトリを作り、シルエットとマテリアルを確認して、ゲームの実行時に使うアセットを用意しました。Blender の元データには、編集可能なメッシュが 193 個あります。書き出した船は 14,968 個の三角形で構成され、不透明マテリアルの描画バッチ 8 個にまとめられています。私はモデルを細部まで作り込むよう頼みつつ、書き出したモデルの描画負荷は Astra に抑えてもらえました。

生成した宇宙船のコンセプトアートの中から、私が採用した案です。

ゲームで使うモデルを Blender でレンダリングした画像です。
プロシージャル生成による水の表現
プロシージャル生成した海を小さなボートで航行するゲーム Sunwake でも、水の表現に時間をかけて実験しました。目指したのは、多面体状の波と滑らかな動き、そしてステンドグラスのような色合いです。Astra は Three.js で専用の水面レンダラーを作り、海の見た目とボートの浮力に同じ波のモデルを使いました。船体は波に持ち上げられ、前後左右に揺れます。船の周囲では局所的なシミュレーションによってさざ波や泡が流れ、ボートが進むと航跡や水しぶきが生じます。その後、Blender でもっと細部まで作り込んだボートを制作し、ゲームに組み込むよう Astra に頼みました。プロシージャル生成した環境に個別に制作した乗り物を置き、シミュレーションで両者を結び付けるという、Void Explorer と同じアプローチです。

Sunwake では、プロシージャル生成した海に、Blender で制作したボートを浮かべています。
Hollowflux では、水の表現を別の方向に発展させました。光る地下河川を舞台にした、小さな 2D アクション RPG です。洞窟やキャラクター、装備はコードで描画し、外部のスプライトシートは使っていません。歩く、ダッシュする、攻撃するといった動作に水面がもっと自然に反応するよう、何度も Astra に調整を頼みました。格子状に並べたセルで、水位、流れ、泡、電荷を追跡します。一歩踏み出すと波紋が残り、槍を突くと水面に細い筋が走り、ハンマーを打ち下ろすと輪状の波が広がります。流れは岸に沿って回り込み、泡を下流へ運びます。その同じ流れにプレイヤーや敵も押されます。水は戦闘にも影響します。たとえば、ウナギが放った電気は水がつながっている範囲に伝わるので、乾いた岩に上がればその放電による感電を避けられます。

Hollowflux のグラフィックはコードで描画しています。水面は移動や戦闘に反応します。
ゲームの制作と共有
Astra と開発していて気に入っているのは、作りたい体験から始めて、画像でビジュアルの方向性を具体化し、実際にプレイした感想をもとに改善できることです。「惑星が消えてしまう」という指摘から、ストリーミング、ジオメトリ、移動処理の変更につながることがあります。「水の動きをもっと自然にしたい」という要望が、見た目とゲームプレイで共有するシミュレーションに発展することもあります。狙った感触になったかどうかは私が判断しますが、その判断をコードやアセット、テストに反映するところまで Astra に任せられます。
ブラウザゲームの共有も簡単です。Sites プラグインを使えば、Astra に次のように頼めます。
誰でもアクセスできる設定で公開すれば、そのままブラウザでプレイしてもらえます。
Void Explorer、Sunwake、Hollowflux は、どれもブラウザでプレイできます。
温めているゲームのアイデアがあれば、Astra と一緒に、まずは実際に遊べる小さなものを作ってみてください。思いどおりの感触に仕上げたい要素をひとつ選び、参考画像をいくつか渡して、できたものを試します。最初はボート 1 隻、部屋ひとつ、あるいはひとつの操作だけで十分です。実際に遊び、変えたいところを具体的に伝えながら、少しずつ作り込んでみてください。