输入一个网址,它的标题、文字、图片、盒子全部变成可破坏的像素关卡——拿枪一路向下轰,把整个页面打成渣。这篇是开发实录:怎么扒闭源原作的设计、怎么用「掩码+画布」双层结构做出 Worms 式可破坏地形、怎么在公共跨域代理全军覆没的国内网络里活下去,以及「出生龛」「击杀顿帧」这些我踩出来/偷出来的小聪明。
🎮 线上开玩:bayshier.github.io/destroy · 📦 源码:github.com/bayshier/destroy(全中文注释)
原版 Destroy Any Website 是 Sprite Fusion 出品的爆款小游戏:输入网址 → 网站变成可破坏像素关卡 → 一路打穿。我把它 187KB 的主 chunk 拆开分析了一遍,确认两件事:没有开源仓库;但它的架构完全可以靠行为反推——
| 环节 | 原版实现 | 我的复刻 |
|---|---|---|
| 网页抓取 | 自有服务端 /api/page?url= 代理 | 公共 CORS 代理链(见第四节,惨烈) |
| 页面转关卡 | 服务端处理 + 前端排版 | 前端 DOMParser 按文档流重排 |
| 破坏系统 | 自研 canvas 引擎 | 掩码+画布双层(见第二节) |
| 武器 | pistol/smg/shotgun/launcher/railgun + grenade | 手枪/冲锋枪/霰弹枪/火箭筒 + 手雷 |
| 联机 | WebSocket 六位房间码 | ❌ 静态托管做不了,单机 |
武器参数都是真扒的:原版弹速 2000~2300 px/s(≈35px/帧)、smg 散布 0.07、火箭带子母弹(bomblets:8)。我的第一版弹速只给了 7px/帧,被自己人吐槽「打不出去」——对齐量级之后手感立刻正常了。复刻一件事,数值量级比机制还原更容易漏。
整个游戏的核心只有一个念头:网页是一张纸,纸上所有墨水都是地形。实现是《百战天虫》的经典套路——
Uint8Array,1=实心 0=空。构建时对全画布做一次 getImageData 扫描生成;之后碰撞检测就是查表,O(1)。破坏率是增量累计的(erase 返回本次毁掉的像素数),全程不需要任何全图统计。
不是截图,是解析 DOM 后重新排版——打碎的是内容本身:
| 网页元素 | 关卡形态 |
|---|---|
| h1~h6 | 26px→14px 标题大字(打碎最解压) |
| p / li / td 等正文 | 11~13px 文字平台,逐行断行 |
| img | 经代理取回真实图(防画布污染),失败画「小山太阳」占位框 |
| pre / code | 灰底代码块 |
| a 链接 | 蓝字+下划线,并记录为敌人「链接虫」出生点 |
| 纯 SPA 空壳 | 检测内容过少,自动补占位盒子保证可玩 |
这是整个项目最绝望的一节。浏览器同源策略不允许直接 fetch 别人的网页,原版用自有服务端代理;GitHub Pages 是静态托管,只能靠公共 CORS 代理。实测结果:
| 代理 | 国内直连 | 备注 |
|---|---|---|
| api.cors.lol | ⚠️ 能通但无 CORS 头 | curl 测通是假象,浏览器照样拒 |
| api.allorigins.win | ❌ 超时 | 主站通,API 子域不通 |
| corsproxy.io | ❌ 401 | 转向收费 |
| thingproxy / r.jina.ai | ❌ 死/被墙 | — |
| codetabs / cors.eu.org | ⚠️ 时好时坏 / 假代理 | — |
三个应对:① 内置演示关卡升为一等公民(标题页黄色大按钮,零网络 100% 可玩);② 自定义代理输入框——填 https://你的代理/?url={url} 存 localStorage 自动插到链首;③ README 提供 10 行 Node 自建代理,有服务器的一劳永逸。网址模式诚实降级为「网络允许时的进阶玩法」。
最阴的 bug 藏在出生点上。直觉做法是扫描页面找「净空+地面」——但密集文字页里根本不存在 13px 高的净空。扫描失败走兜底坐标,那个点恰好落在标题文字内部,物理引擎把角色逐帧顶出屏幕上边缘(y=-22):角色看不见、跳跃没有地面、左右移动在画面外——玩家视角就是「这游戏没法玩」。
修复思路反转:不求页面给出生点,关卡生成时强行造一个「出生龛」——在版心中部 clearRect 清出一条竖井(身体区+跳跃空间),铺一根绿色出生平台,之后掩码扫描自动生效。任何页面(包括纯图片页)都有确定可站的起点。顺手给角色加了白描边+红帽子,在深浅不一的页面上永不难找。
上线后第一反馈:输入 baidu.com,整个页面被一堵 CSS 代码墙糊满。原因:代理传坏的 HTML 会让 DOMParser 的容错把 <style> 内容当成正文文本节点排出。两层防御:
<style/<div),或花括号 ≥6 + 含 :; + 开头非中文段落 → 拒画。4 样本单测全过,正常中英文段落零误杀。用同款坏 HTML 离线复现:CSS 墙消失,标题/导航/正文正常成关卡,稀疏处自动补占位盒子。另修了图片抓取无超时的隐患(单图 8 秒强制放弃)。
| 坑 | 现象 | 解法 |
|---|---|---|
| 出生点落在文字里 | 角色被顶出屏幕外,玩家以为无法操控 | 出生龛:清竖井+铺平台,向关卡索要确定安全区 |
| 画布扩容重置画笔 | 排版后字号全变默认 | ensure() 后必须重设 ctx.font |
| 外链图片污染画布 | getImageData 抛安全异常 | 图片走代理取 blob 再解码 |
| 弹速对不上量级 | 「子弹打不出去」的观感 | 对齐原版 2000+px/s 量级 |
| rAF 被遮挡暂停 | 自动化测试全 0,以为游戏坏了 | 测试时截图强制渲染帧(人玩不受影响) |
| junkText 误杀 | 花括号多的正常技术段落被拒 | 要求开头 40 字非中文 + 括号密度双条件 |
这个项目加起来不到一千五百行,没有一个依赖。做完最大的感受有两层:技术层面,「像素即地形」这个单一念头像种子一样长出了整套玩法——碰撞、破坏、敌人生成、通关判定全部从它推导;产品层面,原版把「浏览网页」这个最无聊的日常翻转成了「摧毁网页」,好的创意不需要复杂的技术,需要的是一次足够狠的视角转换。
后续待办都写在仓库的 AGENT.md 里:自建代理、轨道炮与子母火箭、Boss 战、以及原版最核心的联机合作。