Astra は、私が思い描くものをゲームプレイやアートの方向性として形にするのがとても得意になりました。私は Codex で Astra を使い、遠くの星々から異星の地表まで旅できる宇宙探索ゲーム、Void Explorer を開発しています。
Void Explorer のゲームプレイをまとめたハイライト映像。
このゲームには 2,048 の恒星系と、プロシージャル生成された 10,000 個以上の惑星があり、地球ほどの大きさの惑星も含まれています。見える星はすべて、この宇宙に存在し、目的地として指定できます。遠くにある光の点を選び、そこまで旅できるのです。
宇宙から惑星に近づき、大気圏を通過して、そのまま海岸線の上を飛ぶ高度まで降下できます。着陸して宇宙船から降り、周囲を歩いて、再び離陸することもできます。この一連の旅を実現するには、スケール、地形、操作、レンダリングの課題をまとめて解決する必要がありました。
開発中に使ったプロンプトをいくつか、長さとわかりやすさを考慮して編集し、掲載しています。私が何を求めているかをどう伝え、そこから技術的な作業がどう進んだのかがわかる例です。
体験を起点にした設計
最初の依頼では、プレイヤーが何をできるようにしたいかを説明しました。
見えるものすべてにたどり着けるようにしてください。距離は現実のスケールを保ち、そのうえでスケールと速度を工夫して移動を成立させてください。宇宙から惑星の大気圏に入り、地表まで飛んでいきたいです。惑星は地球ほどの大きさになることもあるので、プロシージャル生成の地形と、チャンク単位で処理するレンダラーが必要です。
これで Astra に具体的な制約を示せました。星は、背景に描いた点であってはなりません。惑星に近づいたときに、別のステージへ切り替わるのもいけません。移動では実際の距離を飛ぶ必要があり、その距離を現実的な時間で移動できるよう、架空のパルス航行やハイパードライブを使います。
ゲームを本格的に作り込む前に、画像生成で見た目も検討しました。最初のコンセプトはリアルすぎて、次の案はシンプルすぎました。そのときに伝えた修正依頼の一つです。
今度は少し単純すぎます。中間を目指しましょう。配色を改善し、ネオンの光とより強いコントラストで、宇宙の奥深さを感じさせたいです。宇宙船の周りに星や塵がある状態で、高速飛行中の見た目を見せてください。
気に入った画像ができたら、Astra に保存を依頼し、その方向性をゲーム用の参考資料にまとめてもらいました。軌道上の飛行、高速飛行、大気圏突入、着陸について、それぞれ具体的な目標ができました。これらの画像のおかげで、次のビルドを評価しやすくなりました。

ゲームの配色とビジュアルの方向性を定めた生成コンセプトアート。
Astra によるアーキテクチャの提案
私が制約を定め、Astra が実装方法を提案しました。アプリケーションには TypeScript と Vite を使い、レンダリングには Three.js を使っています。これにより、メッシュ、マテリアル、ライティング、プロシージャルなジオメトリをコードから直接制御できます。このゲームのビジュアルを作る作業の大部分は、これらの領域に集中しています。
最初にプレイできるようになったレンダラーは WebGL2 を使っていました。その後、Three.js がビジュアルの向上を妨げていないかと尋ねました。Astra は、シミュレーションは維持し、レンダラーを Three.js の WebGPU アーキテクチャに移行することを勧めました。宇宙、ナビゲーション、地形のシステムは残し、レンダリング層を変更しました。
レンダラーは Three.js のノードマテリアルと Three.js Shading Language(TSL)を使い、大気、水、ライティングの効果をコードで定義しています。
地形生成は Web Workers で実行するため、入力と描画を処理するスレッドとは別にジオメトリを準備できます。Vitest では生成の再現性、座標計算、地形が満たすべき条件などをテストします。Playwright ではブラウザ上でゲームを動かします。こうして、私がプレイ感を確認している間に、Astra が基盤となるシステムを検証できるようになりました。
その間も、アートの方向性を一貫して目標に据えていました。惑星を包む大気、環の輝き、恒星が放つ色のついた光、そして Neon Phosphor エフェクトが、見た目を作り上げています。このフィルターはナビゲーションの読みやすさを保ちながら、レトロなディスプレイの質感を加えます。

Neon Phosphor エフェクトを有効にしたゲームプレイのスクリーンショット。
Astra が調査とプレイに使える手段
自分でもゲームをプレイし続けていましたが、Astra にも、私の説明を読むだけでなく問題を調査する手段が必要でした。そこでゲームに小さな JavaScript インターフェース window.__VOID_EXPLORER__ を用意しました。ブラウザテストから呼び出すと、接近中の天体、飛行モード、地形の準備状況、アクティブなカメラといった現在の状態を確認できます。また、ドローコール数、三角形数、キューに入っている地形生成ジョブ数、バッファに保持された地形データ量など、レンダリングとストリーミングのカウンターも公開しています。
ゲームには、軌道上、パルス航行、大気圏内の降下、海岸への着陸に対応する、名前付きのテストシーンがあります。これにより 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 |
この比較は、以下に示す翼が 4 枚の機体を作る前に、SwiftShader によるソフトウェアレンダリングを使ったヘッドレス Chromium で行いました。測定したのはそのテスト環境での変化であり、私の GPU 上でのフレームレートではありません。ツールからは、ブラウザ内の処理時間、レンダリング画像、リソース数を Astra に提供できましたが、個々の GPU コマンドやシェーダーの実行時間は測定していません。
Astra は、読み込み中や降下中にどのような処理が行われているかを調べました。具体的には、生成されたジオメトリの量、ワーカーとレンダラーの間で転送されたデータ量、地形ジョブが実際に使われる前に破棄された頻度です。
変更の 1 つは、遠方の惑星の詳細度を画面上の大きさに応じて割り当てることでした。視界いっぱいに広がる惑星には、すぐに整ったシルエットが必要です。一方、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 枚の独立した翼、左右対称の形、翼端のピンクのライトを備えた参考画像を選びました。そして Astra に、ゲームのアートスタイルを保ちながら Blender で新しいモデルを作り、ゲームに組み込むよう依頼しました。
その作業には、参考画像をジオメトリに落とし込むこと、シルエットとマテリアルを確認すること、実行時に使うアセットを準備することが含まれました。Blender のソースには編集可能なメッシュが 193 個あります。書き出した機体は 14,968 個の三角形で構成され、不透明マテリアルの 8 つのバッチにまとめられています。私がモデルの細部を求めても、Astra は書き出し後の描画コストを抑えてくれました。

生成された機体のコンセプトアートから、私が採用したものです。

ゲームで使っているモデルの Blender レンダリング画像です。
プロシージャルな水表現の試行錯誤
プロシージャル生成された海で小さなボートを操縦するゲーム、Sunwake でも、水の表現に多くの時間をかけて試行錯誤しました。目指したのは、平面の組み合わせで形作られた波、滑らかな動き、ステンドグラスのような色合いです。Astra は Three.js で専用の水面レンダラーを作り、海の見た目とボートの浮力を同じ波モデルで制御しました。船体は波に合わせて上下し、前後左右に傾きます。局所的なシミュレーションが波紋や泡を運び、ボートは航跡や水しぶきを残します。その後、Astra に Blender でもっと細かく作り込んだボートを制作し、ゲームに組み込むよう依頼しました。プロシージャル生成の環境と個別に制作した乗り物をシミュレーションで結びつける、Void Explorer と同じアプローチです。

Sunwake は、プロシージャル生成された海と、Blender で制作したボートを組み合わせています。
Hollowflux では、これを別の方向に発展させました。光る地下河川を中心にした、小規模な 2D アクション RPG です。洞窟、キャラクター、装備は、外部のスプライトシートを読み込まずにコードで描画しています。私は Astra に、歩行、ダッシュ、攻撃による水の乱れをもっと説得力のあるものにしてほしいと繰り返し頼みました。格子状に並んだセルで、水面の高さ、流れ、泡、電荷を追跡しています。足を踏み出すと波紋が残り、槍の突きは細い航跡を切り開き、ハンマーを振り下ろすと輪状の波が広がります。流れは川岸に沿って曲がり、泡を下流へと運びます。その同じ流れがプレイヤーや敵も押し流します。水は戦闘にも影響します。ウナギの放電はつながった水の中を伝わるため、乾いた岩の上に移動すると、その放電から身を守れます。

Hollowflux はゲームのアートをコードで描画しており、水は移動や戦闘に反応します。
ゲームの制作と共有
Astra と作業していて気に入っているのは、実現したい体験から始め、画像で見た目の方向性を具体化し、実際にプレイしてフィードバックを伝えられることです。惑星が消えてしまうという指摘が、ストリーミング、ジオメトリ、動きの変更につながることがあります。もっと説得力のある水の表現が欲しいという要望が、見た目とゲームプレイで共有するシミュレーションになることもあります。しっくりくるかどうかを判断するのは今も私ですが、Astra はその判断をコード、アセット、テストにまで反映できます。
ブラウザゲームの共有も簡単です。Sites プラグインを使えば、Astra に次のように頼めます。
誰でもアクセスできる設定で公開すれば、誰もがブラウザで直接プレイできます。
Void Explorer、Sunwake、Hollowflux は、ブラウザでプレイできます。
温めているゲームのアイデアがあるなら、Astra と一緒に小さなプレイ可能なバージョンを作ってみてください。まずは感触にこだわりたい要素を 1 つ選び、参考画像をいくつか渡して、結果を試します。ボートでも、部屋でも、1 つの操作でも、始めるには十分です。プレイして、変えたい点を具体的に伝え、そこから作り進めてください。