[Web App] LAYER_STG_PROTOCOL ― R3Fで作ったVR対応 3Dシューティング

[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)

[Web App] LAYER_STG_PROTOCOL : タイトル画面(PC)

タイトル画面(WebXR)

[Web App] LAYER_STG_PROTOCOL : タイトル画面(WebXR)


LAUNCH_COMMAND: stg

serial experiments lain 世界観 × Z軸奥行き3Dシューティング

[Web App] LAYER_STG_PROTOCOL : LAUNCH_COMMAND: stg

設計思想 ─ 電脳空間を三次元で

Witchadiusがモニター面のローカル座標系に閉じた横スクロールだったのに対し、STG Protocolは3D空間そのものをゲームフィールドとして使います。

プレイヤーのVRMアバターが画面手前に立ち、Z軸の奥(SPAWN_Z = -120)から敵・障害物・隕石が押し寄せる。

[Web App] LAYER_STG_PROTOCOL : 設計思想 ─ 電脳空間を三次元で

背景はサイバーグリッドが手前に向かって高速スクロールし、CoplandOS風のUI群が「LAYER_STG_PROTOCOL」の文字列で統一されたダークサイバー美術を形成しています。

[Web App] LAYER_STG_PROTOCOL : 設計思想 ─ 電脳空間を三次元で

この後の資産「Witchadius」と同様に維持されており、ADVモード内のアバターや衣装がそのままSTGの自機として流用され、プレイ中のスコアやHP・シールドゲージはwindowグローバルを介したイベント駆動で各UIコンポーネントへブロードキャストされます。

その後の実装で、キャラクターはトップページで選択した物を使用できるようにしました。

[Web App] LAYER_STG_PROTOCOL : 設計思想 ─ 電脳空間を三次元で

ステージ構成 ─ 全5ステージ+エンディング

各ステージはJSON駆動のタイムライン(stage1_timeline.jsonstage5_timeline.json)で構成され、敵・障害物・隕石・ゲートの出現タイミングと座標がすべてデータとして定義されています。

ステージは時間経過で4フェーズに分かれます。

フェーズ経過時間内容
PHASE_10〜20秒ザコ敵と障害物の序盤パターン
PHASE_220〜45秒射撃型敵(shooter)が混じり始める中盤
PHASE_345〜57秒高密度の終盤パターン
PHASE_BOSS57秒〜サイレン警告 → ボスBGM → ボス弾幕戦

ボスを撃破するとステージクリアとなり、次のステージへ進行。全5ステージをクリアするとエンディングシーケンスが再生されます。

[Web App] LAYER_STG_PROTOCOL : 設計思想 ─ 電脳空間を三次元で

ステージ間の難易度スケーリング

ステージが進むごとに、ボスの弾幕パラメータが以下のように段階的にスケールアップします。

パラメータStage 1Stage 2Stage 3Stage 4Stage 5
同時発射数(WAY)58121620
発射間隔(秒)0.550.380.250.180.13
弾速倍率0.750.901.001.151.35
ボスHP406080100150
狙い弾の本数12234
パターン切替間隔(秒)12121087

ボス弾幕システム ─ 数式幾何学弾幕

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 curveStage 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 に直接メッシュを追加する形で管理しています。

  1. 足元の魔法陣 — 4枚の PlaneGeometry を円周上に等間隔配置し、マゼンタ発光(AdditiveBlending)で回転。アバターの足元を旋回しながら透明度がフェードイン
  2. 集中線と光球 — 80本のラインメッシュが球面上のランダム方向から中心に向かって収束。中心にコアオーブ(球体)とトーラスリング2本
  3. チャージ進行度の視覚フィードバック — コアオーブの色がシアン→白に変化。チャージ完了時に se_magic_charge_max SEを1回だけ再生(重複再生防止フラグ chargeMaxSEPlayed
  4. チャージ短縮アイテムCHARGE_SHORTEN アイテム取得で必要時間が0.1秒ずつ短縮(最短0.6秒)。window.wiredRequiredChargeTime で全コンポーネント間を同期

チャージ弾の性能

チャージ弾はボスに10ダメージ(通常弾は1ダメージ)を与え、より大きな当たり判定(半径6.5 vs 通常弾の4.8)を持ちます。速度は CHARGE_BULLET_SPEED = 70(通常弾は BULLET_SPEED = 80)と若干遅いが、貫通力で補う設計です。

[Web App] LAYER_STG_PROTOCOL : チャージショットシステム ─ STGChargeSystem.ts

電脳シールド(Cyber Shield)

Shift長押し(VRではグリップ)で展開するバリアシステム。魔法陣テクスチャの回転プレーンがアバター前方に出現し、被弾を完全防御します。

シールドメカニクス

項目
ゲージ最大値100
消費レート20/秒
回復レート10/秒
オーバーヒート復帰閾値30

ゲージが0になるとオーバーヒート状態に入り、シールドが強制解除されます。グリップを離し、ゲージが30まで回復するまで再展開不可。VRではグリップの離検知が1フレーム遅延するため、同フレームでの即復帰を防ぐ設計になっています。

[Web App] LAYER_STG_PROTOCOL : 電脳シールド(Cyber Shield)

量子爆発ボム(Quantum Bomb)

Bキー(VRではBボタン)で発動する画面制圧兵器。最大3発ストック、敵撃破時に1%の確率でボムチャージアイテムがドロップします。

ボムエフェクトパターン

BombEffects.tsx に5種のボムエフェクトが実装されています。現在の実装ではパターン2(Cyber Rings)が確定で使用されます。

#パターン名演出
1Quantum Sphere超巨大球体が急速膨張→フェードアウト
2Cyber Rings魔力球が自機から画面中央へ射出→着弾後に3重トーラスリングが大爆発
3Orbital Laser前方Z軸方向への極太レーザー照射
4Data Shatterワイヤーフレーム正二十面体が巨大化しながら回転→消滅
5Singularityブラックホール圧縮(黒い球体が吸い込み収縮)→ 反転して白い爆発が膨張

Cyber Ringsの2フェーズ構造

実際に使用されているCyber Ringsは2フェーズで構成されています。

フェーズ1(0〜0.6秒):小さな魔力球が自機の位置から画面中央 (0, -2, 0) へ高速移動。この間はダメージ判定なし。発射音SE再生。

フェーズ2(0.6〜2.0秒):着弾した瞬間に超重低音爆発SE(最大音量)が鳴り、トーラスリングが一気にスケール35まで膨張。この間 wiredBombExploding = true となり、画面上のすべての敵弾消去・ザコ敵粉砕・ボスへの持続ダメージ(毎秒20ダメージ)が発生します。

[Web App] LAYER_STG_PROTOCOL : 量子爆発ボム(Quantum Bomb) [Web App] LAYER_STG_PROTOCOL : 量子爆発ボム(Quantum Bomb) [Web App] LAYER_STG_PROTOCOL : 量子爆発ボム(Quantum Bomb)

敵とオブジェクト

敵ユニット

敵は2種類のバリエーション(variant)で分類されます。

バリエーション挙動見た目
normalZ軸方向に直進するのみマゼンタ(#ff0055)
shooter2秒間隔でプレイヤー方向へ狙撃弾を発射ネオンパープル(#ff00ff)

敵のGLBモデルは mesh_enemy_invader1(alban氏制作のSketchfabモデル)を全個体で共有し、THREE.Group.clone() でインスタンス化。MAX_ENEMIES = 15 のオブジェクトプールで管理しています。

障害物・環境オブジェクト

オブジェクトジオメトリダメージ挙動
柱(Pillar)CylinderGeometry(ワイヤーフレーム)30Z方向に直進
隕石(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エンディングシーケンスが再生されます。

フェーズ構成

フェーズ時間演出
WAITING0〜0.5秒暗転、パーティクル静止
CONVERGE0.5〜3.5秒800個のパーティクルが円周上から中心1点へ収束。グローリング(ミニマルなトーラス)がフェードイン
CREDIT3.5秒〜パーティクル収束が継続する中、クレジットテキストが下から上へスクロール
BURSTクレジット完了後パーティクル群が一斉にフェードアウト、星フィールドも消失
THANKYOUBURST後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 以内に到達するとフェードアウトし、再び円周上にリスポーンするループ構造です。

頂点カラーと PointsMaterialvertexColors を使い、個別の不透明度制御を頂点カラーの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 内で毎フレーム直接操作。

プール最大数ジオメトリ
通常弾30TorusGeometry(1〜3重リング)
チャージ弾5SphereGeometry
15GLBモデル(clone)
障害物150CylinderGeometry
隕石20IcosahedronGeometry
ゲート5ExtrudeGeometry
爆発100SphereGeometry × 3
アイテム15各種ジオメトリ(型で切替)
敵弾30OctahedronGeometry
ボス弾80CapsuleGeometry + SphereGeometry(グロー)

イベント駆動アーキテクチャ

コンポーネント間通信は window.dispatchEvent(new CustomEvent(...)) によるカスタムイベントで統一されています。

イベント名発火元用途
wired-stg-startSTGUI / STGTitleVRゲーム開始
wired-stg-shootSTGController通常ショット発射
wired-stg-charge-startSTGControllerチャージ開始
wired-stg-charge-shootSTGControllerチャージ弾発射
wired-stg-charge-cancelSTGControllerチャージキャンセル
wired-stg-bombSTGControllerボム発動要求
wired-stg-play-seSTGChargeSystem / BombEffectsSE再生要求
wired-stg-get-itemSTGManagerアイテム取得通知
wired-stg-next-stageSTGUI / STGTitleVR次ステージ遷移
wired-stg-to-titleSTGUI / STGTitleVRタイトル画面遷移
wired-toggle-stg-modeSTGTitleVRROOMモードへ戻る

音響システム

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 プロトコルに接続せよ。