ダンジョン生成
概要
Rift Survivors のダンジョンは毎回プロシージャル生成される。BSP(Binary Space Partitioning)アルゴリズムをベースに、部屋の配置と通路の接続を自動生成する。
生成アルゴリズム: BSP(Binary Space Partitioning)
アルゴリズム全体フロー
BSP分割の詳細
分割パラメータ
| パラメータ | 値 | 説明 |
|---|---|---|
| 全体サイズ | 80×80 units | ダンジョン全体の大きさ |
| 最大分割回数 | 5回 | 再帰の深さ上限 |
| 最小区画サイズ | 10×10 units | これ以下は分割しない |
| 分割比率 | 0.4〜0.6 | 分割位置のランダム幅 |
| 分割方向 | 交互(横→縦→横...) | 偏りを防ぐ |
分割アルゴリズム(疑似コード)
typescript
interface BSPNode {
x: number; y: number;
width: number; height: number;
left?: BSPNode;
right?: BSPNode;
room?: Room;
}
function splitBSP(node: BSPNode, depth: number): void {
if (depth >= MAX_DEPTH) return;
if (node.width < MIN_SIZE * 2 && node.height < MIN_SIZE * 2) return;
// 分割方向を決定(面積比で判断、近い場合はランダム)
const splitHorizontally = node.width < node.height
? true
: node.width > node.height
? false
: Math.random() > 0.5;
if (splitHorizontally) {
const splitY = node.y + lerp(node.height * 0.4, node.height * 0.6, Math.random());
node.left = { x: node.x, y: node.y, width: node.width, height: splitY - node.y };
node.right = { x: node.x, y: splitY, width: node.width, height: node.y + node.height - splitY };
} else {
const splitX = node.x + lerp(node.width * 0.4, node.width * 0.6, Math.random());
node.left = { x: node.x, y: node.y, width: splitX - node.x, height: node.height };
node.right = { x: splitX, y: node.y, width: node.x + node.width - splitX, height: node.height };
}
splitBSP(node.left, depth + 1);
splitBSP(node.right, depth + 1);
}部屋配置ルール
各リーフノード内にランダムサイズの部屋を配置する。
部屋の幅 = random(区画幅 × 0.5, 区画幅 × 0.9)
部屋の高さ = random(区画高さ × 0.5, 区画高さ × 0.9)
部屋の位置 = 区画内でランダムにオフセット(壁から最低1unit離す)| パラメータ | 値 |
|---|---|
| 部屋最小サイズ | 6×6 units |
| 部屋最大サイズ | 区画の90% |
| 壁厚 | 0.5 units |
| 生成される部屋数 | 8〜16部屋(分割回数に依存) |
部屋タイプ
部屋タイプ一覧
| タイプ | 出現数 | 説明 | 視覚的特徴 |
|---|---|---|---|
| スタート部屋 | 1 | プレイヤーの初期位置 | 明るいネオン照明、安全地帯 |
| 戦闘部屋 | 4〜8 | 敵がスポーンする主要エリア | 暗い照明、赤い警告ライン |
| エリートアリーナ | 1〜2 | エリート敵が出現する大部屋 | 紫のオーラ、広い空間 |
| ボスアリーナ | 0〜1 | ボス戦用の最大部屋 | 特殊BGM、閉鎖ゲート |
| 宝物部屋 | 1〜2 | 高レアリティ武器の宝箱 | 金色のライティング |
| 回復部屋 | 1〜2 | HP回復+バフアイテム | 緑色のライティング |
| ポータル部屋 | 1 | 脱出ポータルの出現場所 | シアン色のリフトエフェクト |
部屋タイプの配置ルール
距離計算: 部屋間の距離はBSPツリー上のホップ数(部屋中心間のマンハッタン距離ではなく、通路を通った実際の経路長)で算出する。
通路生成
BSPツリーの各内部ノードにおいて、左右の子ノードに属する部屋同士を通路で接続する。
通路パラメータ
| パラメータ | 値 |
|---|---|
| 通路幅 | 2.0 units |
| 通路形状 | L字型(水平→垂直 or 垂直→水平) |
| 接続方式 | 各部屋の最寄りの辺の中点を結ぶ |
通路生成アルゴリズム
typescript
function connectRooms(roomA: Room, roomB: Room): Corridor {
const pointA = roomA.center;
const pointB = roomB.center;
// L字型の通路を生成(ランダムに水平→垂直 or 垂直→水平)
if (Math.random() > 0.5) {
// 水平→垂直
return [
{ x1: pointA.x, y1: pointA.y, x2: pointB.x, y2: pointA.y }, // 水平
{ x1: pointB.x, y1: pointA.y, x2: pointB.x, y2: pointB.y } // 垂直
];
} else {
// 垂直→水平
return [
{ x1: pointA.x, y1: pointA.y, x2: pointA.x, y2: pointB.y }, // 垂直
{ x1: pointA.x, y1: pointB.y, x2: pointB.x, y2: pointB.y } // 水平
];
}
}環境バリエーション
テーマ
ダンジョンのビジュアルテーマはミッション難易度と進行度で決定される。
| テーマ | 解放条件 | 色調 | 環境ハザード |
|---|---|---|---|
| データグリッド | 初期 | シアン / ブルー | なし |
| ネオンアーケード | 永続Lv3以降 | ピンク / マゼンタ | 電撃トラップ(床が周期的に通電) |
| グリッチコア | 永続Lv6以降 | パープル / グリーン | 不安定床(一定時間で崩壊) |
| ヴォイドリフト | 永続Lv10以降 | 黒 / 赤 | 次元亀裂(床から出現する攻撃) |
環境ハザード
| ハザード | ダメージ | 発動間隔 | 予兆 |
|---|---|---|---|
| 電撃トラップ | HP 5%/秒 | 8秒ON/4秒OFF | 床の点滅(2秒前) |
| 不安定床 | 即死(落下) | プレイヤーが乗って3秒後 | 亀裂のひび割れ(1秒前) |
| 次元亀裂 | ATK 30固定 | 5秒間隔 | 床の赤い円(1.5秒前) |
環境オブジェクト
| オブジェクト | 機能 | 破壊可能 |
|---|---|---|
| データモニュメント | 触れるとEXPジェム(大)ドロップ | Yes |
| エネルギーピラー | 一定時間ごとにHP回復フィールド展開 | No |
| 破壊可能な壁 | 攻撃で破壊、ショートカット生成 | Yes(HP 50) |
| 爆発バレル | 攻撃で爆発(半径2.0, 50ダメージ, 敵味方問わず) | Yes(HP 1) |
ミニマップ
ダンジョン探索中はHUD上にミニマップを表示する。
| 表示要素 | アイコン/色 |
|---|---|
| 現在位置 | 白い矢印 |
| 訪問済み部屋 | 薄い灰色 |
| 未訪問部屋 | 非表示 |
| 通路 | 薄い灰色のライン |
| ボスアリーナ | 赤いアイコン |
| 宝物部屋 | 金色のアイコン |
| 脱出ポータル | シアンのアイコン |
| 敵(ミニマップ内) | 赤い点 |
ミニマップサイズ: 画面右上 150×150px。M キーで全体マップ表示に切り替え。
シード値によるリプレイ可能な生成
シード値の役割
ダンジョンの全要素はシード値から決定論的に生成される。同じシード値を使えば、完全に同じダンジョンが再現される。
typescript
interface DungeonSeed {
seed: number; // 乱数シード値
mission: Mission; // ミッション内容
cycleState: CycleState; // サイクル状態(ボスヒント配置に影響)
}
function generateDungeon(dungeonSeed: DungeonSeed): Dungeon {
const rng = createSeededRNG(dungeonSeed.seed);
// BSP分割、部屋配置、敵配置、アイテム配置を全て rng から生成
// → 同じシードなら同じ結果
}リベンジシステムとの連携
敗北時にシード値をlocalStorageに保存。リベンジポータルから再挑戦すると同じシード値で生成。
| 要素 | リベンジ時の再現 |
|---|---|
| マップ構造 | 完全に同じ |
| 敵の種類・配置 | 完全に同じ |
| ボスの種類・弱点 | 完全に同じ |
| ドロップテーブル | 同じ(乱数は再生成) |
| ヒントの配置 | 完全に同じ |
プレイヤーが変更できるもの
リベンジ時にマップは固定だが、プレイヤーのビルドは自由に変更できる。
- レベル(前回より成長している可能性あり)
- 装備(ロビーで変更可能)
- スキル選択(レベルアップ時に異なるスキルを選べる)
→ 同じダンジョンに対して「最適なビルド」を試行錯誤する楽しさ
サイクル内のヒント配置
5回サイクルの2〜4回目のダンジョンには、ボスの弱点ヒントが自動配置される。 ヒントの配置位置もシード値から決定論的に決まる。
サイクル内潜入回数とヒント配置:
1回目: ヒントなし
2回目: テキストヒント1個(壁の碑文)
3回目: アイテムヒント1個(ボスの欠片ドロップ)
4回目: 行動ヒント(ミニボスが弱点属性で大ダメージを受ける)
5回目: ボス戦(弱点を突いて撃破)パフォーマンス考慮
Three.jsでの実装指針
| 項目 | 方針 |
|---|---|
| 床・壁のメッシュ | タイルベースのInstancedMesh で一括描画 |
| 通路 | 同一マテリアルのメッシュを結合(BufferGeometry merge) |
| 視野外の部屋 | Frustum Culling(Three.js標準) |
| LOD | プレイヤーから遠い部屋のオブジェクトを簡略化 |
| 描画上限 | Draw Call 目標: 100以下 |
メモリ目安
| 要素 | サイズ見込み |
|---|---|
| BSPツリーデータ | ~2KB |
| 部屋ジオメトリ(全体) | ~500KB |
| テクスチャ | ~2MB(環境テーマ1種分) |
| 合計 | ~3MB |