[Web App] LAYER_STG_PROTOCOL ― R3Fで作ったVR対応 3Dシューティング
はじめに
「STG Protocol(STGプロトコル)」は、Three.js / React Three Fiber で構築したブラウザベースの全画面3Dシューティングゲームです。
この後に開発したWitchadius(横スクロールSTG)とは異なり、Z軸の奥行き方向から敵が押し寄せてくる3D空間を舞台としたシューティングで、 往年の名作「スペースハリア―」を意識しつつ、serial experiments lainの世界観を色濃く反映した「電脳空間への侵入と防衛」をテーマにしています。
全5ステージ+エンディングクレジット、数式幾何学に基づくボス弾幕、チャージショットシステム、電脳シールド、量子爆発ボムを実装。PCブラウザとMeta Quest(WebXR)の両方でプレイ可能です。
Youtube紹介動画(PC版)
Youtube紹介動画(VR版)
タイトル画面(PC)
タイトル画面(WebXR)
LAUNCH_COMMAND: stg
serial experiments lain 世界観 × Z軸奥行き3Dシューティング
設計思想 ─ 電脳空間を三次元で
Witchadiusがモニター面のローカル座標系に閉じた横スクロールだったのに対し、STG Protocolは3D空間そのものをゲームフィールドとして使います。
プレイヤーのVRMアバターが画面手前に立ち、Z軸の奥(SPAWN_Z = -120)から敵・障害物・隕石が押し寄せる。
背景はサイバーグリッドが手前に向かって高速スクロールし、CoplandOS風のUI群が「LAYER_STG_PROTOCOL」の文字列で統一されたダークサイバー美術を形成しています。
この後の資産「Witchadius」と同様に維持されており、ADVモード内のアバターや衣装がそのままSTGの自機として流用され、プレイ中のスコアやHP・シールドゲージはwindowグローバルを介したイベント駆動で各UIコンポーネントへブロードキャストされます。
その後の実装で、キャラクターはトップページで選択した物を使用できるようにしました。
ステージ構成 ─ 全5ステージ+エンディング
各ステージはJSON駆動のタイムライン(stage1_timeline.json 〜 stage5_timeline.json)で構成され、敵・障害物・隕石・ゲートの出現タイミングと座標がすべてデータとして定義されています。
ステージは時間経過で4フェーズに分かれます。
| フェーズ | 経過時間 | 内容 |
|---|---|---|
| PHASE_1 | 0〜20秒 | ザコ敵と障害物の序盤パターン |
| PHASE_2 | 20〜45秒 | 射撃型敵(shooter)が混じり始める中盤 |
| PHASE_3 | 45〜57秒 | 高密度の終盤パターン |
| PHASE_BOSS | 57秒〜 | サイレン警告 → ボスBGM → ボス弾幕戦 |
ボスを撃破するとステージクリアとなり、次のステージへ進行。全5ステージをクリアするとエンディングシーケンスが再生されます。
ステージ間の難易度スケーリング
ステージが進むごとに、ボスの弾幕パラメータが以下のように段階的にスケールアップします。
| パラメータ | Stage 1 | Stage 2 | Stage 3 | Stage 4 | Stage 5 |
|---|---|---|---|---|---|
| 同時発射数(WAY) | 5 | 8 | 12 | 16 | 20 |
| 発射間隔(秒) | 0.55 | 0.38 | 0.25 | 0.18 | 0.13 |
| 弾速倍率 | 0.75 | 0.90 | 1.00 | 1.15 | 1.35 |
| ボスHP | 40 | 60 | 80 | 100 | 150 |
| 狙い弾の本数 | 1 | 2 | 2 | 3 | 4 |
| パターン切替間隔(秒) | 12 | 12 | 10 | 8 | 7 |
ボス弾幕システム ─ 数式幾何学弾幕
STG Protocolの最大の特徴は、ボス弾幕が数学的な曲線関数で定義されている点です。
10種類の弾幕パターンが DANMAKU_PATTERNS 配列に関数として定義され、各パターンは (time, bulletIndex, totalWays, speed) を引数に取り、XY平面上の正規化方向ベクトルを返します。ボスからプレイヤーへの3D前方ベクトルを軸に、right/up ベクトルを交差積で算出し、パターンの出力を3D空間へ射影しています。
全弾幕パターン一覧
| # | パターン名 | 数学的基盤 | 使用ステージ |
|---|---|---|---|
| 0 | ノーマルショット | 均等扇形(0.6π分配) | Stage 1 |
| 1 | 回転リング | 全方位2π等分+時間回転 | Stage 1, 2 |
| 2 | 螺旋(スパイラル) | sin変調付き極座標回転 | Stage 2, 3 |
| 3 | 二重螺旋 | WAY数を2分割し逆回転 | Stage 3, 4 |
| 4 | 薔薇曲線 | r = cos(3θ) のRose curve | Stage 3, 5 |
| 5 | 蝶々形 | ` | sin(2θ) |
| 6 | 星形放射 | r = 0.6 + 0.4·cos(5θ + t) | Stage 4 |
| 7 | 収束→爆散 | sin波打ち+角度変調 | Stage 4, 5 |
| 8 | ランダムバースト | シード付き擬似ランダム | Stage 5 |
| 9 | 地獄弾幕 | 3層逆回転の全包囲高密度 | Stage 5 |
これらのパターンは STAGE_PATTERN_LIST でステージごとに割り当てられ、patternSwitchSec 秒ごとに自動切り替えされます。
さらに、弾幕パターンとは独立して「狙い弾」がプレイヤーへ直接向かって飛び、ジッター(ブレ幅)を加えることで完全な安全地帯の成立を防いでいます。
DanmakuLibrary.ts ─ 独立した弾幕ライブラリ
ボス弾幕とは別に、DanmakuLibrary.ts に spiral / rose / pulsar / winder / chaos の5パターンが独立したライブラリとして定義されています。各関数は (time, index, total) を引数に取り、THREE.Vector3 の速度ベクトルを返す純粋関数です。
チャージショットシステム ─ STGChargeSystem.ts
通常ショット(Space タップ)は魔法弾リング。Space長押しでチャージが開始され、チャージ完了後にリリースすると「GIGA_PIERCE(貫通魔弾)」が発射されます。
チャージエフェクトの構造
チャージ中のエフェクトは STGChargeSystem クラスが THREE.Scene に直接メッシュを追加する形で管理しています。
- 足元の魔法陣 — 4枚の
PlaneGeometryを円周上に等間隔配置し、マゼンタ発光(AdditiveBlending)で回転。アバターの足元を旋回しながら透明度がフェードイン - 集中線と光球 — 80本のラインメッシュが球面上のランダム方向から中心に向かって収束。中心にコアオーブ(球体)とトーラスリング2本
- チャージ進行度の視覚フィードバック — コアオーブの色がシアン→白に変化。チャージ完了時に
se_magic_charge_maxSEを1回だけ再生(重複再生防止フラグchargeMaxSEPlayed) - チャージ短縮アイテム —
CHARGE_SHORTENアイテム取得で必要時間が0.1秒ずつ短縮(最短0.6秒)。window.wiredRequiredChargeTimeで全コンポーネント間を同期
チャージ弾の性能
チャージ弾はボスに10ダメージ(通常弾は1ダメージ)を与え、より大きな当たり判定(半径6.5 vs 通常弾の4.8)を持ちます。速度は CHARGE_BULLET_SPEED = 70(通常弾は BULLET_SPEED = 80)と若干遅いが、貫通力で補う設計です。
電脳シールド(Cyber Shield)
Shift長押し(VRではグリップ)で展開するバリアシステム。魔法陣テクスチャの回転プレーンがアバター前方に出現し、被弾を完全防御します。
シールドメカニクス
| 項目 | 値 |
|---|---|
| ゲージ最大値 | 100 |
| 消費レート | 20/秒 |
| 回復レート | 10/秒 |
| オーバーヒート復帰閾値 | 30 |
ゲージが0になるとオーバーヒート状態に入り、シールドが強制解除されます。グリップを離し、ゲージが30まで回復するまで再展開不可。VRではグリップの離検知が1フレーム遅延するため、同フレームでの即復帰を防ぐ設計になっています。
量子爆発ボム(Quantum Bomb)
Bキー(VRではBボタン)で発動する画面制圧兵器。最大3発ストック、敵撃破時に1%の確率でボムチャージアイテムがドロップします。
ボムエフェクトパターン
BombEffects.tsx に5種のボムエフェクトが実装されています。現在の実装ではパターン2(Cyber Rings)が確定で使用されます。
| # | パターン名 | 演出 |
|---|---|---|
| 1 | Quantum Sphere | 超巨大球体が急速膨張→フェードアウト |
| 2 | Cyber Rings | 魔力球が自機から画面中央へ射出→着弾後に3重トーラスリングが大爆発 |
| 3 | Orbital Laser | 前方Z軸方向への極太レーザー照射 |
| 4 | Data Shatter | ワイヤーフレーム正二十面体が巨大化しながら回転→消滅 |
| 5 | Singularity | ブラックホール圧縮(黒い球体が吸い込み収縮)→ 反転して白い爆発が膨張 |
Cyber Ringsの2フェーズ構造
実際に使用されているCyber Ringsは2フェーズで構成されています。
フェーズ1(0〜0.6秒):小さな魔力球が自機の位置から画面中央 (0, -2, 0) へ高速移動。この間はダメージ判定なし。発射音SE再生。
フェーズ2(0.6〜2.0秒):着弾した瞬間に超重低音爆発SE(最大音量)が鳴り、トーラスリングが一気にスケール35まで膨張。この間 wiredBombExploding = true となり、画面上のすべての敵弾消去・ザコ敵粉砕・ボスへの持続ダメージ(毎秒20ダメージ)が発生します。
敵とオブジェクト
敵ユニット
敵は2種類のバリエーション(variant)で分類されます。
| バリエーション | 挙動 | 見た目 |
|---|---|---|
normal | Z軸方向に直進するのみ | マゼンタ(#ff0055) |
shooter | 2秒間隔でプレイヤー方向へ狙撃弾を発射 | ネオンパープル(#ff00ff) |
敵のGLBモデルは mesh_enemy_invader1(alban氏制作のSketchfabモデル)を全個体で共有し、THREE.Group.clone() でインスタンス化。MAX_ENEMIES = 15 のオブジェクトプールで管理しています。
障害物・環境オブジェクト
| オブジェクト | ジオメトリ | ダメージ | 挙動 |
|---|---|---|---|
| 柱(Pillar) | CylinderGeometry(ワイヤーフレーム) | 30 | Z方向に直進 |
| 隕石(Meteor) | IcosahedronGeometry(ワイヤーフレーム) | 25 | プレイヤー付近をランダムに狙う3D軌道 |
| ゲート(Gate) | ExtrudeGeometry(穴あき壁) | 35 | 穴の形(circle/triangle/star)を通り抜ける |
ゲートの穴判定は形状ごとに異なる数式で実装されています。円形は距離判定、三角形は直線方程式による内外判定、星形は極座標の cos(5θ) による変調半径判定です。
ドロップアイテムシステム
敵撃破時にアイテムがドロップします。アイテム構成は stg_item.json で外部定義されており、コードの変更なしにドロップテーブルを調整可能です。
| アイテム | ジオメトリ | 効果 | ドロップ確率 |
|---|---|---|---|
| POWER_UP | トーラス | 自機の通常弾が段階的に強化(1→2→3段階。2段目で二重リング、3段目で三重リング) | 通常抽選 |
| SPEED_UP | 八面体 | 移動速度を0.014ずつ上昇(最大0.092) | 通常抽選 |
| BONUS | 星型(ExtrudeGeometry) | スコアボーナス | 通常抽選 |
| LIFE_UP | ハート型(ExtrudeGeometry+ベベル) | HPを30回復(最大100) | 10%の特殊抽選 |
| CHARGE_SHORTEN | コーン | チャージ必要時間を0.1秒短縮(最短0.6秒) | 通常抽選 |
| BOMB_REFILL | 球体+シリンダー+トーラスの複合体 | ボムを1発補充(最大3発) | 1%の特殊抽選 |
通常弾のパワーレベルは STGController 側で管理され、wiredPlayerPowerLevel を介してメッシュの色(シアン→マゼンタ)とリング数が動的に変化します。
HUD ─ デュアルUI設計
PC用2D HUD(STGUI.tsx)
画面左上にスコア(6桁ゼロパディング)、シールドゲージ、ライフゲージ、ボム残数を常時表示。ボス戦時は画面上部中央にボスHPバーが出現。
すべてのUI値は requestAnimationFrame ループで window グローバルをポーリングし、前フレームと差分がある場合のみ setState するパフォーマンス最適化が施されています。
タイトル画面、ステージクリア画面、ゲームオーバー画面、エンディング画面はそれぞれ独立した全画面オーバーレイとしてレンダリングされ、CoplandOS風のボーダーラインとモノスペースフォントで統一されたサイバーパンク美術を構成しています。
VR用3D ホログラムUI(STGHologramUI)
VRセッション中のみ表示される3Dテキスト群。アバターの右斜め上(X:+0.4, Y:+1.6, Z:-0.2)に追従し、LP(ライフ)、SS(シールド)、BM(ボム残数)をリアルタイム表示。PC表示時は visible = false で完全非表示化されます。
VR用タイトルUI(STGTitleVR.tsx)
VR空間内に浮かぶ3Dパネル(PlaneGeometry + EdgesGeometry)で、タイトル画面・操作説明・ゲームオーバー・ステージクリア・エンディングの各状態に応じてテキストとカラーが動的に切り替わります。VRコントローラーのポインティング+トリガーで操作。ホバー時にパネルの背景色とボーダー色が変化するインタラクションフィードバック付き。
背景 ─ STGBackground.tsx
背景は THREE.GridHelper(100×50分割、色 #004466)をZ軸方向に毎フレーム移動させ、1マス分を超えたら巻き戻す無限ループ方式。グリッドの直下には真っ黒な PlaneGeometry を敷いて、地下の余計な壁を完全に遮光しています。
Witchadiusのような手続き的な空やグラデーション背景ではなく、serial experiments lainのWired空間を意識した無機質なサイバーグリッドで統一されています。
エンディングクレジット ─ パーティクル収束演出
全5ステージをクリアすると、STGEnding_Final.jsx による3Dエンディングシーケンスが再生されます。
フェーズ構成
| フェーズ | 時間 | 演出 |
|---|---|---|
| WAITING | 0〜0.5秒 | 暗転、パーティクル静止 |
| CONVERGE | 0.5〜3.5秒 | 800個のパーティクルが円周上から中心1点へ収束。グローリング(ミニマルなトーラス)がフェードイン |
| CREDIT | 3.5秒〜 | パーティクル収束が継続する中、クレジットテキストが下から上へスクロール |
| BURST | クレジット完了後 | パーティクル群が一斉にフェードアウト、星フィールドも消失 |
| THANKYOU | BURST後1.8秒 | 「Thank you for playing. / See you in the Wired.」がフェードイン→4.5秒表示→フェードアウト |
| DONE | 感謝テキスト消失後 | 暗転、1.5秒後にタイトル画面へ遷移 |
パーティクル収束の実装
800個のパーティクルは Float32Array で位置・不透明度・速度を管理。各粒子はXY平面の円周上(半径0〜18)にランダムスポーンし、中心(0,0,0)に向かって 0.5〜1.0 units/sec で収束。中心から 0.09 units 以内に到達するとフェードアウトし、再び円周上にリスポーンするループ構造です。
頂点カラーと PointsMaterial の vertexColors を使い、個別の不透明度制御を頂点カラーのRGB値で近似しています。
クレジットデータ
クレジット情報はJSX内の makeCredits() 関数にハードコードされ、各行のテキスト・色・フォントサイズ・太字・行間隔を完全制御。SCROLL_SPEED で一定速度でスクロールし、最終行が画面上端を通過した瞬間に onFinished コールバックが発火してBURSTフェーズへ遷移します。
入力システム ─ STGController.tsx
STGController はVRMアバターの制御を一手に担うコンポーネント。キーボード入力とVRコントローラー入力を統合し、移動・射撃・チャージ・シールド・ボムのすべてを管理します。
操作プロトコル(キーマッピング)
| キー | 割り当てアクション | 実装仕様 |
|---|---|---|
| W / A / S / D / 矢印キー | 自機の上下左右移動 | SPEED_UPアイテムで段階的に加速(0.05→最大0.092) |
| Space(タップ) | 通常ショット | パワーレベルに応じてリング数1〜3が変化 |
| Space(長押し+リリース) | チャージショット(GIGA_PIERCE) | 0.2秒以上でチャージ開始、必要時間到達でリリース時に発射 |
| Shift(長押し) | 電脳シールド | ゲージ消費型。オーバーヒート機構あり |
| B | 量子爆発ボム | 最大3発ストック。e.repeat 無視で連打防止 |
| Enter | ゲーム開始 / 状態遷移 | タイトル→プレイ、ステージクリア→次ステージ、ゲームオーバー→タイトル |
VR入力
| VRコントローラー | 割り当て |
|---|---|
| アナログスティック | 移動(Y軸反転) |
| トリガー | 通常ショット / チャージショット |
| グリップ | 電脳シールド |
| Bボタン | 量子爆発ボム |
アバター物理
移動は THREE.MathUtils.lerp による補間追従(係数0.1)で滑らかに遷移。画面端クランプはビューポートサイズから動的算出し、下限は3D空間の床の高さ(-1.7)に直接固定。
接地判定(wiredPlayerGrounded)がtrueの場合、アバターのY軸ベース回転が Math.PI/4(斜め向き)から 0(正面向き)にLerp遷移し、地上を走る姿勢に切り替わります。左右移動時はZ軸ロール(最大±0.2ラジアン)で傾き表現。
コードアーキテクチャ
src/components/PROJECT_LAIN/STG/
├── STGManager.tsx 1848行(ゲームループ本体 + ボス弾幕 + レンダリング)
├── STGController.tsx 340行(自機操作・VR入力統合・シールド・チャージ)
├── STGUI.tsx 295行(PC用2D HUD全画面UI)
├── STGTitleVR.tsx 135行(VR用3Dタイトルパネル)
├── STGBackground.tsx 41行(無限スクロールグリッド背景)
├── STGChargeSystem.ts 213行(チャージエフェクト管理クラス)
├── BombEffects.tsx 316行(5種のボムエフェクトコンポーネント)
├── DanmakuLibrary.ts 45行(弾幕パターンライブラリ)
├── STGEnding_Final.jsx 518行(エンディングシーケンス)
└── data/
├── stgAssets.json (BGM・SE・モデル・テクスチャ定義)
├── stg_item.json (ドロップアイテム定義)
├── stage1_timeline.json (ステージ1 タイムライン)
├── stage2_timeline.json (ステージ2 タイムライン)
├── stage3_timeline.json (ステージ3 タイムライン)
├── stage4_timeline.json (ステージ4 タイムライン)
└── stage5_timeline.json (ステージ5 タイムライン)
オブジェクトプール設計
すべてのゲームオブジェクトはマウント時にプールとして一括確保され、active フラグで有効/無効を切り替えるフライウェイトパターンで運用されます。useRef でReact再レンダリングから隔離し、useFrame 内で毎フレーム直接操作。
| プール | 最大数 | ジオメトリ |
|---|---|---|
| 通常弾 | 30 | TorusGeometry(1〜3重リング) |
| チャージ弾 | 5 | SphereGeometry |
| 敵 | 15 | GLBモデル(clone) |
| 障害物 | 150 | CylinderGeometry |
| 隕石 | 20 | IcosahedronGeometry |
| ゲート | 5 | ExtrudeGeometry |
| 爆発 | 100 | SphereGeometry × 3 |
| アイテム | 15 | 各種ジオメトリ(型で切替) |
| 敵弾 | 30 | OctahedronGeometry |
| ボス弾 | 80 | CapsuleGeometry + SphereGeometry(グロー) |
イベント駆動アーキテクチャ
コンポーネント間通信は window.dispatchEvent(new CustomEvent(...)) によるカスタムイベントで統一されています。
| イベント名 | 発火元 | 用途 |
|---|---|---|
wired-stg-start | STGUI / STGTitleVR | ゲーム開始 |
wired-stg-shoot | STGController | 通常ショット発射 |
wired-stg-charge-start | STGController | チャージ開始 |
wired-stg-charge-shoot | STGController | チャージ弾発射 |
wired-stg-charge-cancel | STGController | チャージキャンセル |
wired-stg-bomb | STGController | ボム発動要求 |
wired-stg-play-se | STGChargeSystem / BombEffects | SE再生要求 |
wired-stg-get-item | STGManager | アイテム取得通知 |
wired-stg-next-stage | STGUI / STGTitleVR | 次ステージ遷移 |
wired-stg-to-title | STGUI / STGTitleVR | タイトル画面遷移 |
wired-toggle-stg-mode | STGTitleVR | ROOMモードへ戻る |
音響システム
BGMとSEはすべてIndexedDB(Dexie)にキャッシュされ、getCachedAssetUrl() でBlob URLに変換して HTMLAudioElement で再生。2回目以降のロードではネットワークリクエストが発生しません。
BGMはタイトル / ステージ / ボス / エンディングの4トラックが状態に応じて切り替わります。ボス戦開始時にはステージBGMが一時停止→サイレンSE→サイレン終了コールバックでボスBGM再生、という順序制御が HTMLAudioElement.onended チェーンで実装されています。
SEは playSE() 関数でワンショット Audio インスタンスを毎回生成する方式。同時発音が自然に重畳され、チャージ中SEはsetIntervalで450ms間隔ループ、シールドSEは300ms間隔ループで「持続入力中の効果音」を表現しています。
技術的な振り返り
windowグローバルによるコンポーネント間通信
React Three Fiberの特性上、useFrame 内のリアルタイム処理と useState ベースのUIレンダリングが異なるライフサイクルで動くため、高頻度で更新される値(プレイヤー座標、HP、チャージ進行度など)は window オブジェクト経由で共有しています。前フレームの値と比較して差分がある場合のみ setState を呼ぶことで、不要な再レンダリングを抑制しています。
オーバーヒート復帰のフレーム遅延問題
VRコントローラーのグリップ離検知が1フレーム遅延するため、シールドのオーバーヒート解除条件を「グリップを離した」だけでなく「ゲージが30まで回復している」の複合条件にすることで、同フレームでの即復帰バグを防いでいます。
チャージタイマーの上限ロック
チャージ時間が requiredChargeTime を超えてもタイマーが増加し続けると、エフェクトが無限に肥大化するバグが発生していました。解決策として、チャージ蓄積が最大必要時間に達した時点でタイマーを強制固定しています。
ボム爆発の間引き処理
ボム爆発中にボスへ持続ダメージを与える際、毎フレーム spawnExplosion() を呼ぶと100個の爆発プールを瞬時に使い果たすため、0.1秒間隔のタイマーで間引きしています。
ステージ遷移時のデータリセット
5面クリア後にエンディングを経てタイトルに戻る際、ステージ番号とタイムラインJSONを1面に戻し忘れると、次回プレイ開始時に6面のデータをロードしようとしてクラッシュします。STGEndingScene のクレジット完了コールバックと STGManager の両方でリセット処理を二重に仕込むことで、どのルートでタイトルに戻っても確実に初期化される設計としました。
LAUNCH_COMMAND: stg
プロトコルに接続せよ。