概要
本記事では、Next.jsとThree.jsを用いて、ブラウザ上で動作するFPS視点の3D迷路探索ゲームを構築した際の実装手法や設計上の工夫、パフォーマンス最適化、そして開発を通して得られた知見についてまとめます。
WebGLを扱うゲーム開発は、一般的なフロントエンド開発とは異なる観点で検討すべき事項が多くあります。本記事がThree.jsを用いてゲームや3D表現を行いたい方の参考になれば幸いです。
技術スタック
- Next.js(App Router)
- Three.js
- React Fiber(UI レイヤー)
- JavaScript / TypeScript 混在
最終的な成果物: ブラウザで FPS 視点の 3D 迷路を歩き回り、Goal に到達するゲーム。PC・モバイル双方に対応。
迷路は DFS(深さ優先探索)によって自動生成し、InstancedMesh を使って大規模迷路でも軽量に表現しています。
迷路生成 — DFS を用いたシンプルで高拡張性なアルゴリズム
迷路生成には、再帰的 DFS(深さ優先探索)を採用しました。非常にシンプルですが、迷路の一意性・伸縮性・調整のしやすさに優れ、Web ブラウザ上でも高速に動作します。
defineMaze = () => {
const moved = [{y: 0, x: 1}, {y: -1, x: 0}, {y: 0, x: -1}, {y: 1, x: 0}];
const init = () => Array(size).fill().map(() => Array(size).fill(1));
const data = init();
const shuffle = (d) => {
for (let i = d.length - 1; i > 1; i--) {
const r = Math.floor(Math.random() * (i + 1));
[d[i], d[r]] = [d[r], d[i]];
}
return d;
}
const dig = (y, x) => {
data[y][x] = 0;
shuffle(moved);
for (let i of moved) {
const y2 = y + i.y * 2;
const x2 = x + i.x * 2;
if (0 <= y2 && y2 < size && 0 <= x2 && x2 < size && data[y2][x2]) {
data[y + i.y][x + i.x] = 0;
dig(y2, x2);
}
}
}
const startPoint = parseInt(size / 2);
dig(startPoint, startPoint);
return data;
}
特徴
- シンプル・高速・可読性が高い
- 迷路サイズを大きくしても動作が軽い
- 経路生成の確定性と多様性の両立
Three.js での実装 — 必要十分で効率的なシーン構築
ゲーム空間の構築では、以下の方針を取りました。
- 迷路は InstancedMesh でまとめて描画し、DrawCall の増加を抑える
- 地面・天井には同一マテリアルを使い、コストを低減
- プレイヤー視点は Camera をそのまま用い、オブジェクトを持たせない
迷路の描画(InstancedMesh)
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const wallMaterial = new THREE.MeshStandardMaterial({ color: 0x444444 });
const mesh = new THREE.InstancedMesh(boxGeometry, wallMaterial, maze.tiles.length);
InstancedMesh によって、巨大迷路でも FPS が大きく低下しない設計になりました。
描画最適化 — 「見える範囲だけ描画する」
Three.js では特に 無駄な描画を行わないこと がフレームレート維持の鍵となります。本プロジェクトでは複数のアプローチで最適化を行いました。
1. Frustum Culling(視錐台カリング)
プレイヤーの視界に入っていないオブジェクトは描画しないよう、カスタムのカリング処理を追加しました。
メリット:
- 大規模迷路でも、画面に映っていない壁の描画が省ける
2. LOD(詳細度レベル)の導入
一定距離以上の壁については、ジオメトリを単純化する、または描画を省略するアプローチも採用しています。
3. レンダラー設定の調整
const renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setPixelRatio(window.devicePixelRatio * 0.7);
renderer.shadowMap.enabled = false;
負荷の高い処理(影・高解像度レンダリング)を最初から切り捨て、ゲームとして必要な部分にのみ計算リソースを割きました。
操作システム — PC / モバイル両対応
PC
-
WASD移動 - マウスドラッグで視点移動
モバイル
- 左半分:仮想スティック
- 右半分:画面スワイプで視点移動
FPS ゲームはモバイル操作が難しいため、UI のレスポンスやタッチイベントの最適化にも時間を割きました。
モバイル最適化 — 一番苦労したポイント
描画負荷の増大
モバイル端末では GPU 性能の差が大きいため、以下の調整を行いました。
- ピクセル比の大幅な低減
- 遠距離の壁は描画しない
- UI は React + CSS とし、Three.js 側の処理を極力軽減
結果として、エントリーモデルの Android でも快適な描画が可能になりました。
デバッグ・検証で役立った工夫
- リアルタイムで迷路サイズを変更できる UI を用意
- 壁の描画数・FPS の計測値をオーバーレイ表示
- プレイヤー衝突判定の可視化ツールを自作
ゲーム開発では「今何が起きているか」を可視化することが重要であり、これらのデバッグ機能が最終的な品質向上に寄与しました。
まとめ — WebGL × ゲーム開発で得た経験
Next.js + Three.js の構成で FPS 3D ゲームを構築したことで、以下のような知見を得られました。
- ブラウザゲームでも最適化次第で十分高速に動く
- Three.js はゲームエンジンとしても活用可能
- GPU 負荷を減らす設計が全体の安定性に直結する
- モバイル最適化は PC 以上に工夫が必要
今回のプロジェクトを通じ、WebGL を用いたゲーム開発の奥深さと楽しさを改めて実感しました。今後は、マルチプレイヤー化や Raycasting を用いたインタラクション拡張などにも挑戦する予定です。