[Web App] LAYER_FIGHTER ─ R3Fで作ったVR対応 3D格闘ゲーム

[Web App] LAYER_FIGHTER ─ R3Fで作ったVR対応 3D格闘ゲーム

はじめに

「LAYER_FIGHTER」は、Three.js / React Three Fiber で構築したブラウザベースの3D格闘ゲームです。

lain-lab.comに搭載された5つ目のゲームタイトルで、VRMアバターが対面して殴り合うフレーム単位の格闘ゲームエンジンを実装しています。

テンキー表記の方向入力、startup/active/recoveryのフレームデータ、ヒットボックス判定、ヒットストップ、ラウンド制KOシステムを備え、キーボード・PS4コントローラー・Meta Quest(WebXR)の3入力デバイスでプレイ可能です。

[Web App] LAYER_FIGHTER ─ はじめに

Youtube紹介動画

LAUNCH_COMMAND: ftg

serial experiments lain 世界観 × フレーム単位3D格闘

[Web App] LAYER_FIGHTER ─ 起動コマンド

設計思想 ─ フレーム単位の格闘エンジン

他の4タイトルがリアルタイム物理やターン制だったのに対し、LAYER_FIGHTERは60FPS固定のフレーム駆動ゲームループで動作します。

useFrame 内でデルタタイムを蓄積し、1000/60 ms 単位で物理演算・入力処理・ヒット判定を実行する固定フレームレート方式です。

プレイヤーの状態は FighterState 型のステートマシンで管理され、各状態にJSON定義のフレームデータ(startup・active・recovery)が紐づきます。

[Web App] LAYER_FIGHTER ─ 設計思想 ─ フレーム単位の格闘エンジン [Web App] LAYER_FIGHTER ─ 設計思想 ─ フレーム単位の格闘エンジン

ゲームモード構成

[Web App] LAYER_FIGHTER ─ ゲームモード構成

FTGManager.tsx がゲーム全体のモード遷移を管理します。

モード内容
TITLEタイトル画面。メニュー選択
STAGE_SELECTGLBステージ3Dプレビュー付き選択画面
TRAININGトレーニングモード(対CPU戦)
SOUND_TESTBGM/SE再生テスト
CREDIT_SEQUENCEクレジットスライダー
SHUTDOWN終了

入力システム ─ テンキー表記

格闘ゲームの慣例に従い、方向入力をテンキー表記(1〜9)で管理しています。5がニュートラル、8が上、2が下、6が前方、4が後方。キャラクターの向き(facingRight)に応じて物理的な左右が forward/backward に自動変換されます。

入力デバイス統合

3種の入力デバイスを1つのゲームループ内で統合しています。

デバイス方向ボタン
キーボードWASD / 矢印キーZ(P) X(K) C(G) V(E)
PS4コントローラーDpad / 左スティック×(P) ○(K) □(G) △(E)
VR(Quest)左スティック右A(P) 右B(K) 左グリップ(G) 左トリガー(E)

PS4コントローラーは navigator.getGamepads() によるポーリング方式。VRコントローラーは XRSession.inputSources から毎フレーム読み取り。

PS4メニュー操作フック ─ useFTGMenuPad.ts

タイトル、ステージセレクト、サウンドテスト、確認ダイアログなど全メニュー画面で共通利用されるPS4パッド入力フック。Dpad/左スティックでメニュー移動、×ボタンで決定、○ボタンでキャンセル。エッジ検出(前フレームとの差分)で長押し連打を防止し、方向入力には250msのクールダウンを設けています。

入力バッファ

直近10フレームの方向入力を inputBufferRef に蓄積し、将来的なコマンド入力(波動拳コマンド等)の判定に使用可能な基盤を構築しています。

[Web App] LAYER_FIGHTER ─ 入力システム ─ テンキー表記

戦闘エンジン ─ useFTGGameLoop.ts

FighterState ステートマシン

プレイヤーの状態は以下の FighterState 型で管理されます。

状態アニメーション説明
IDLEIDLE_CHANGE_POSE立ち待機
WALKING_FSYS_PLAYER_WALKING_F前進
WALKING_BSYS_PLAYER_WALKING_B後退
CROUCHINGSYS_CROUCHINGしゃがみ
JUMPINGSYS_RUNNINGジャンプ(空中)
PUNCHINGSYS_PUNCH立ちパンチ
CROUCH_PUNCHSYS_CROUCH_PUNCHしゃがみパンチ
KICKINGSYS_KICK立ちキック
KICK_KNEESYS_KICK_KNEE膝蹴り
KICK_FORWARDSYS_KICK_FORWARD前蹴り
KICK_LOWSYS_KICK_LOW下段蹴り
GUARDINGSYS_GUARD立ちガード
CROUCH_GUARDINGSYS_CROUCH_GUARDしゃがみガード
HITSTUNSYS_HURT被弾硬直
JUMP_PUNCHSYS_PUNCH空中パンチ
JUMP_KICKSYS_KICK空中キック

技フレームデータ ─ Moves.json

各攻撃技は Moves.json にフレームデータとして定義されています。

パラメータ説明
state対応する FighterState
startup発生フレーム(判定が出るまでの予備動作)
active持続フレーム(攻撃判定が有効な期間)
recovery硬直フレーム(技の後隙)
damageダメージ値
knockbackノックバック距離
hitboxes攻撃判定の座標・サイズ配列
hitSe / hitSeVolumeヒット時のSEとボリューム
animSpeedアニメーション再生速度倍率

技の総持続時間は startup + active + recovery で算出され、この期間中はステートが固定されて他の入力を受け付けません。

ヒットボックス判定

プレイヤーと敵のそれぞれに「食らい判定(ハートボックス)」と「攻撃判定(ヒットボックス)」が独立して設定されています。

食らい判定は常時表示の BoxGeometry(0.5×1.6×0.4)。攻撃判定は Moves.jsonhitboxes 配列で複数箇所を定義可能で、startup中は0番目のみ縮小表示、activeフレームに入ると全ヒットボックスがフルサイズで判定開始。

判定はXY軸のAABB(軸並行バウンディングボックス)交差テスト。キャラクターの向きに応じてX座標のオフセットが反転します。

[Web App] LAYER_FIGHTER ─ 戦闘エンジン ─ ヒットボックス判定

ヒットストップ

攻撃命中時、hitStopRef に指定フレーム数がセットされ、その間すべてのゲームロジック(物理演算・入力処理・AI)が完全に停止します。これにより格闘ゲーム特有の「ヒット時の衝撃感」を演出しています。

物理演算

ジャンプは初速 JUMP_VY = 0.18、重力 GRAVITY = 0.012/frame の放物運動。着地判定は posY <= 0 で即座に接地。キャラクター間の押し返しは最小距離(MIN_DIST = 1.0)を保つように双方を均等に移動させます。ステージ端の移動制限は STAGE_LIMIT = 5.5


ラウンドシステム

2ラウンド先取制(KOで1本)。ラウンドフェーズは FIGHTINGKOWAIT_INPUTROUND_CALLFIGHT_CALLFIGHTING のサイクルで管理されます。

フェーズ表示持続
KO「K.O.」大文字赤表示約1秒
WAIT_INPUT「YOU WIN / YOU LOSE」+スコアボタン待ち
ROUND_CALL「ROUND N」シアン表示約1.5秒
FIGHT_CALL「FIGHT!」赤表示約1秒
FIGHTINGバトル進行中HP0まで

2本先取でGAME_SETとなり、VICTORY表示後にタイトルに戻ります。

[Web App] LAYER_FIGHTER ─ ラウンドシステム

敵AI

敵は EnemyState(IDLE / WALKING / COOLDOWN / ATTACKING / HITSTUN)の5状態で動作する簡易AIで、プレイヤーとの距離に応じて接近→攻撃→クールダウンのサイクルを繰り返します。攻撃技は Assets.json の敵VRM定義に紐づいた技リストからランダム選択。歩行速度やAIパラメータもJSON定義されています。


ステージセレクト ─ FTGStageSelect.tsx

5つのバトルステージがGLBモデルとして用意されています。

ステージ制作者ライセンス
Sci-fi FightingUmarCC Attribution
Lava Fighting ArenaUmarCC Attribution
Mitaiken HorizontspmosybauCC Attribution
Hikari LLSIFAStspmosybauCC Attribution
Yume Utaou LLSIFAStspmosybauCC Attribution

ステージ選択画面では左側にステージ名リスト、右側にGLBモデルのリアルタイム3Dプレビュー(ゆっくり回転)が表示されます。

[Web App] LAYER_FIGHTER ─ ステージセレクト ─ FTGStageSelect.tsx

VRMキャラクター ─ FTGComponents.tsx

FTGCharacter

VRMモデルのロード・アニメーション再生・物理座標追従を担うコンポーネント。physicsRef の座標を毎フレーム group.position に反映し、向き(facingRight)に応じて -π/2 または +π/2 に回転。

アニメーション制御

ANIM_MAPFighterState からアニメーションIDへの変換テーブル。攻撃アニメは LoopOnce + clampWhenFinished で1回再生後に最終フレームで停止。しゃがみ・ガードも LoopOnce + clamp で姿勢を保持。アニメーション速度は Moves.jsonanimSpeed または Assets.jsonanimations[].animSpeed で技ごとに個別調整可能。

カメラ ─ FTGCamera

プレイヤーと敵の中間点をフォーカスし、距離に応じてズームアウトする横視点カメラ。FOV 50で固定。


HUD ─ FTGBattleHUD.tsx

HTML DOMオーバーレイで実装されたバトルHUD。ftgHudRef(Reactのレンダリングサイクルと分離したRefベースのストア)から30FPSでポーリングして表示更新。

トレーニングモード表示

トレーニングモードでは追加のデバッグ情報が表示されます。

表示内容
テンキーグリッド現在の方向入力を9マスで可視化
ボタン表示P/K/G/Eの押下状態をカラー表示
フレームカウンター5桁のフレーム番号
入力ログ直近6件のステート遷移ログ

サウンドテスト ─ FTGSoundTest.tsx

Assets.json に定義された全BGM・SEを一覧表示し、個別に再生テストできるモード。上下キーで選択、Enterで再生。BGMとSEが分離されたタブ形式で表示されます。

[Web App] LAYER_FIGHTER ─ サウンドテスト ─ FTGSoundTest.tsx

クレジット ─ FTGCreditSequence.tsx

Assets.jsonStages.json からVRMモデル制作者・ステージ制作者・BGM作曲者・SE制作者を自動抽出し、スライダー形式で1エントリずつ表示するクレジットシーケンス。

各エントリは INCOMING(右から滑り込み)→ STAY(静止表示)→ OUTGOING(左へスライドアウト)の3フェーズで遷移。3Dモデルタイプのエントリは自動回転プレビュー付き、画像タイプはテクスチャビルボード表示。

[Web App] LAYER_FIGHTER ─ クレジット ─ FTGCreditSequence.tsx [Web App] LAYER_FIGHTER ─ クレジット ─ FTGCreditSequence.tsx

コードアーキテクチャ

src/components/PROJECT_LAIN/FTG/
├── FTGManager.tsx           356行(モード遷移・BGM切替・メニュー制御)
├── FTGComponents.tsx        866行(VRMキャラ・カメラ・ステージ・HP・ヒットボックス)
├── useFTGGameLoop.ts        836行(ゲームループ・入力・物理・ヒット判定・ラウンド制)
├── FTGTrainingScene.tsx     543行(トレーニングモードシーン)
├── FTGTitleBackground.tsx   639行(タイトル画面3D背景)
├── FTGStageSelect.tsx       288行(ステージセレクト+GLBプレビュー)
├── FTGSoundTest.tsx         620行(サウンドテストモード)
├── FTGCreditSequence.tsx    426行(クレジットスライダー)
├── FTGBattleHUD.tsx         109行(バトルHUDオーバーレイ)
├── FTGHudStore.ts            34行(Refベースの状態ストア)
├── useFTGMenuPad.ts          79行(PS4メニュー操作共通フック)
├── utils/
│   └── AudioController.ts       (BGM/SE管理)
└── data/
    ├── Assets.json               (VRM・BGM・SE・アニメーション定義)
    ├── Moves.json                (技フレームデータ)
    └── Stages.json               (バトルステージ定義)

技術的な振り返り

固定フレームレートの実装

useFrame のデルタタイムは可変なので、格闘ゲームに必要な固定60FPSロジックを実現するために accRef にデルタタイムを蓄積し、16.67ms単位でwhileループを回す方式を採用しています。これにより、ディスプレイのリフレッシュレートに関係なくフレームデータが正確に機能します。

ヒットボックスのプール方式

敵のヒットボックスは最大4個の THREE.Mesh を事前にマウントしておき、useFrame 内で visibleposition/scale を直接操作するオブジェクトプール方式。Reactの再レンダリングを完全に回避しています。

HudStoreの分離

バトル中のHP・ラウンド情報・入力ログなどの高頻度更新データは ftgHudRef(プレーンなオブジェクトRef)に書き込み、HTMLオーバーレイ側は setInterval(1000/30) で30FPSのポーリング読み取りを行う設計です。ゲームループ(60FPS)とUI描画(30FPS)のフレームレートを分離することで、DOM更新のオーバーヘッドを抑えています。

VRセッションのグローバル共有

VR入力を useFTGGameLoop 内で読み取るために、XRSession(window as any).__ftgXrSession にグローバル保存してフレームごとにアクセスする方式を採用しています。R3Fの useXR フックの外側(純粋なゲームループ内)からXR入力を読み取る必要があったためです。


LAUNCH_COMMAND: ftg プロトコルに接続せよ。