DODGE MASTER 是 Hao0321 遊戲大廳目前流量最高的一款小遊戲。玩法很直觀:控制一個霓虹方塊在賽博龐克風格的賽道上閃避障礙物、撿金幣、拚高分。但表面簡單的背後其實擠了很多細節 — 物件池、連擊系統、角色稀有度、金幣商店、廣告觸點、每日任務、轉盤獎勵。這篇把整個 921 行的 HTML 單檔拆開講,順便分享我對「街機小品 = 讓玩家每 30 秒都有驚喜」的設計哲學。
為什麼用純 HTML 單檔
遊戲大廳其他遊戲都是 React .jsx,只有 DODGE MASTER 是獨立的 dodge-master-v5.html,在大廳裡用 iframe 嵌入。這個選擇是刻意的:
- 性能原因:閃避遊戲對幀率非常敏感,60 FPS 是底線。避開 React 的 reconciliation overhead,直接操作 Canvas 最省。
- 隔離原因:遊戲內有自己的角色解鎖、金幣、主題配色等 state,用 iframe 把 localStorage 命名空間跟遊戲大廳的
hao_系列分開。 - 可攜性:這個檔案可以獨立丟到 itch.io、Gumroad 或任何靜態空間,不依賴大廳的 SDK。
代價是不能直接用 React 的好處(component reuse、hooks 之類),但 DODGE MASTER 整個遊戲 UI 大概 10 個畫面,用 class 切換 + 手寫 DOM 操作就足夠。
核心玩法設計
「30 秒驚喜」原則
街機小品的黏著度不能靠劇情,只能靠每一關都有新東西。我給自己立了一個規則:玩家從開始到死亡的平均 90 秒內,必須遇到至少 3 次「喔這個!」的瞬間。最後盤點下來,驚喜來源有:
- 隨機道具:護盾、磁鐵、幽靈狀態、雙倍金幣、縮小碰撞箱(5 種,隨機掉落)
- 連擊計量:連續閃過 5 個障礙物觸發「Combo x2」倍率,螢幕邊緣發光
- 角色系統:蒐集 10+ 個角色(COMMON / RARE / EPIC / LEGENDARY / MYTHIC),每個有不同 hitbox、速度、計分乘數
- 轉盤與寶箱:死亡後有機會看廣告旋轉大轉盤,命中機率給 50~200 金幣
- 每日任務:5 個彈性目標(撿 50 金幣 / 存活 60 秒 / 達成 x5 連擊…),完成領獎勵
這些系統彼此疊加:一局遊戲裡,玩家既在閃障礙物,又在追 combo、又在注意有沒有特定任務的道具出現。認知負荷剛好吃掉短期專注力,讓人停不下來。
難度曲線
遊戲沒有「關卡」概念,是無限長的賽道。難度隨時間遞增:
difficulty = 1 + gameTime * 0.008 spawnRate = Math.max(35 - difficulty * 4, 12) // 越後期越快生怪 objSpeed = 4 + difficulty * 0.3
這個公式沒什麼魔法,就是 playtest 出來的。重點在於前 15 秒給玩家送分(生怪慢、速度慢),建立「我還撐得住」的錯覺,然後陡峭上升。平均每局時長落在 60~120 秒,剛好一個廣告的長度。
Canvas 效能細節
物件池(Object Pool)
一場遊戲裡會生數百個障礙物、金幣、粒子。如果每個都 new 一個 object 再丟棄,瀏覽器 GC 會在高頻時段造成明顯卡頓。我用 pool pattern:
const pool = [];
function getObs() {
return pool.pop() || { x:0, y:0, vx:0, type:0, alive:false };
}
function returnObs(o) {
o.alive = false;
pool.push(o);
}
粒子效果(撞擊、連擊、金幣閃光)的池預分配 500 個,實測 GC pressure 降了 60% 以上。
繪圖順序(Layer Sorting)
Canvas 沒有 z-index 的概念,只有「後畫的蓋前面的」。我把每一幀的繪圖拆成:
- 背景漸層 + 掃描線效果(
ctx.fillRect+ 條紋 repeating pattern) - 道路紋理 + 遠景粒子
- 障礙物(按 y 排序,製造近大遠小的深度感)
- 金幣、道具
- 玩家角色
- 粒子特效(打擊、連擊閃光)
- HUD(分數、連擊、金幣圖示)
所有繪圖每幀重畫一次,沒有用 requestAnimationFrame 以外的技巧。420px × 720px 的 canvas 在中階手機穩定 60 FPS。
角色 hitbox 縮放
玩家常覺得「明明看起來沒碰到為什麼死了」。我讓玩家的實際碰撞箱比視覺尺寸小 20%(hitbox.r = visual.r * 0.8)。這在動作遊戲裡是常識級的魔法 — 視覺上差 2 pixel 對玩家心理有巨大差別。
角色稀有度系統
10+ 個角色分 5 種稀有度,每個有不同視覺與屬性修飾:
| 稀有度 | 數量 | 解鎖方式 | 屬性範圍 |
|---|---|---|---|
| COMMON | 4 | 初始 / 看廣告 | 乘數 1.0x |
| RARE | 3 | 開寶箱 | 乘數 1.1~1.2x |
| EPIC | 2 | 抽卡 | 乘數 1.3~1.4x |
| LEGENDARY | 2 | 任務累積 | 乘數 1.5~1.6x |
| MYTHIC | 1 | 全成就 | 乘數 1.7~2.0x |
屬性差異不能太大,否則玩家會覺得免費角色被逼退。我訂的準則是:MYTHIC 比 COMMON 強 70%,但不會讓 COMMON 玩家「無法破紀錄」。實際數據:MYTHIC 玩家平均分數比 COMMON 高約 50%,符合設計。
為什麼稀有度系統對留存重要
免費街機遊戲最大的挑戰是「玩家為什麼今天還要開」。稀有度系統提供了兩層動機:
- 短期:每局的寶箱開箱 + 轉盤的驚喜(變動報酬 schedule,心理學上最上癮的結構)
- 長期:蒐集所有角色的成就感(完成率 = 終極牽引力)
AdSense 整合觸點
免費遊戲的收益來源就是廣告。在不破壞體驗的前提下,我設了 7 個「看廣告領獎勵」的選擇性觸點:
- Revive(死亡畫面) — 看一支廣告復活一次。
- 2x Coins(結算畫面) — 看廣告把當局金幣翻倍。
- Bonus Spin(結算畫面) — 看廣告多轉一次大轉盤。
- Free Gacha(商店) — 看廣告免費抽一次角色。
- Free Wheel(主選單) — 看廣告轉每日幸運輪。
- Daily x2(任務完成) — 看廣告把獎勵加倍。
- Shop Deal(商店) — 看廣告領 50 金幣特惠。
所有觸點都是選擇性(opt-in),從不打斷遊戲中期、從不強制插入。這是我對自己立的一條紅線:寧願賺少一點,也不要讓玩家在撞到關鍵障礙物前三秒跳出廣告。
資料持久化
整個遊戲沒有伺服器(不像大廳其他遊戲要上傳分數到 Cloudflare D1)。所有資料存在 localStorage:
D = {
co: 0, // coins
best: 0, // all-time best score
bt: 0, // best time
bc: 0, // best combo
tg: 0, // total games played
td: 0, // total dodges
chars: {}, // unlocked characters
activeChar: 'basic',
missions: {},
lastDaily: null,
skinsPurchased: {}
};
saveData = () => localStorage.setItem('DM', JSON.stringify(D));
簡單直接。缺點是換裝置或清快取會掉資料,但這是免費瀏覽器遊戲可接受的權衡。
和遊戲大廳的整合
DODGE MASTER 作為 iframe 嵌在 play.html?game=dodge-master 裡。結算時要上傳分數到 Hao0321 遊戲大廳的排行榜:
function endGame() {
try {
const h = window.parent && window.parent.haoGame;
if (h && h.reportScore) h.reportScore(score);
} catch (e) {}
}
踩過一個坑:Cloudflare Pages 預設的 _headers 設 X-Frame-Options: DENY,連同源 iframe 都擋掉。改成 SAMEORIGIN 才解決。
數據回饋
目前 DODGE MASTER 的數據(過去一週):
- 平均每局時長 72 秒
- 玩家角色完成率(至少解鎖 5 隻):26%
- 每日任務完成率:平均一天 3.2/5
- 轉盤使用率:80%(幾乎每局死亡都會轉)
留存曲線符合預期:第一次開玩玩 3-4 局平均、回流率(隔天再開)約 35%。對於一款完全免推廣的瀏覽器小品,這個數字我很滿意。
結語
DODGE MASTER 證明了一個人週末可以做出讓人想連玩 5 局的小遊戲。關鍵不是引擎、不是預算,是設計上的克制:把該做的 5 個系統做好,砍掉 10 個沒有必要的系統。每一條動線都要對玩家有清楚的意義,不能只是「因為其他遊戲都這樣做」。
想親自玩:前往 DODGE MASTER。玩完覺得有什麼可以改進的,歡迎寄信 lo246179268@gmail.com。