M3:種子化隨機地城——房間深度鏈與模組拼裝
隨機地圖有兩種做法:開放式迷宮,或房間制。這款選了後者——理由是節奏。
為什麼是房間制,不是開放迷宮
開放迷宮容易迷路、節奏鬆散。房間制(像 Hades、以撒)把遊戲切成一個個「進房 → 戰鬥 → 選擇 → 下一房」的循環,節奏緊湊、難度好控、獎勵好給。對動作 Roguelike 來說,這個結構幾乎總是對的。
種子化 RNG:同種子、同地圖
用 mulberry32 這個輕量 RNG。給同一個種子,就生出一模一樣的樓層——這讓地圖可重現、可分享、可除錯(「這個 bug 在種子 12345 第三層」)。
class Rng {
next() { /* mulberry32:一個 32-bit 狀態進、一個 [0,1) 出 */ }
int(min, max) { ... }
pick(arr) { ... }
fork() { return new Rng(this.int(0, 0xffffffff)); } // 衍生獨立子序列
}
fork() 很關鍵:每個房間拿一個從主序列衍生的獨立種子,這樣「樓層結構」和「房間內細節」的隨機互不干擾。
房間深度鏈
一層樓 = 一條「深度鏈」:depths[d] 是該深度的 1–2 個候選房間。玩家清完房後,從門選一個進入下一深度。
深度0(起點戰鬥) → 深度1 → 深度2 → … → 出口(樓梯)
↓ ↓
[戰鬥] [寶箱]
[精英] [戰鬥] ← 每個深度給 1-2 個選項,玩家抉擇
中段保證塞進一個寶箱房深度、一個精英房深度,剩下隨機。每三層的結構會被換成單一 BOSS 房(後面 M5 會談)。
用 KayKit 模組拼房間
房間不是一塊大地板,而是用 CC0 的 KayKit Dungeon 模組「拼」出來的:
- 地板:
tileSize × tileSize的格子鋪滿,12% 機率換成碎石變化版 - 四面牆:沿邊界放牆模組,北牆開 1–2 個門洞
- 裝飾:四角柱子 + 火把(帶點光源)、隨機桶子箱子
每片牆都生一個對應的 Rapier cuboid 碰撞體;門洞則在兩側放短柱碰撞體,中間留開口。
進房鎖門、清房開門
這是房間制的節奏核心:
- 進房 → 門被「魔法屏障」鎖住(半透明發光面 + 一個阻擋碰撞體)
EnemySpawner按波次表刷怪- 全部清空 → 發
roomCleared事件 → 移除屏障碰撞體、隱藏屏障與木門 - 門上方浮標顯示「下一房是什麼類型」(戰鬥/精英/寶箱/出口)
- 玩家走進門 → 黑幕轉場 → 進下一房
門上的類型標示讓「選哪扇門」變成有資訊的決策,而不是賭博。
難度曲線資料化
敵人數量、種類、HP 係數全寫在 waves.ts 純資料表裡,隨樓層遞增。調平衡時只動資料、不碰邏輯——這個「資料驅動」原則貫穿整個專案(敵人、升級、掉落都是純資料表)。
現在每次爬塔都是新地圖了。下一篇讓「變強」這件事有了意義:升級系統與永久進度。