十个开源游戏、48 MB:自托管第三方游戏要付的三种代价
我把十个 MIT/公有领域的 HTML5 游戏原样搬进了这个静态站,总计 48.4 MB。真正花时间的不是搬文件,是许可证、体积和"它们本来从 CDN 加载"这三件事。
站上的游戏一直是我自己写的:一个目录、一个 meta.ts、一个 Vue 岛,逻辑全部可测。这次换了条路——把十个别人写的开源游戏原样搬进来,不打散、不改写、不并进打包。
结果是 public/games-vendor/ 下 48.4 MB、10 个目录。这篇记的是代价,不是成果。
体积:两个游戏吃掉三分之二
| 游戏 | 体积 | 许可 |
|---|---|---|
| 未来赛车 HexGL | 16.36 MB | MIT |
| 云架构塔防 Server Survival | 15.33 MB | MIT |
| 台球 Pool | 4.62 MB | MIT |
| 放置 RPG | 3.52 MB | MIT |
| Flexbox 青蛙 | 2.46 MB | MIT |
| 炸弹人 | 2.19 MB | MIT |
| 盖楼 | 1.83 MB | MIT |
| 3D 迷宫 Astray | 1.55 MB | Unlicense |
| 星际战机 | 0.50 MB | MIT |
| 三消 | 0.06 MB | MIT |
前两个加起来 31.7 MB,占 65%。HexGL 的大头是纹理(两套,textures/ 和 textures.full/,代码里都引用了)和音频;Server Survival 的大头是 11.5 MB 的音效。
我砍掉的第一样东西是台球那首 8.6 MB 的背景乐(Kevin MacLeod 的 Bossa Antigua,CC BY)。它占了那个游戏 65% 的体积,而一个静态站的访客里,愿意听背景音乐的比例低到不值得。代码里把加载点换成一个静音桩,播放调用不会报错,游戏照跑。

两个 WebGL/3D 项目占了 65%,剩下八个加起来还没它们多。
许可证:三种坑,都不是“没写 LICENSE”
第一种:仓库的 LICENSE 和文件头打架。 三消那个项目的 LICENSE 写的是 MIT,但 match3-example.js 的文件头明明白白写着 GPL-3.0-or-later。两个都“有效”时只能按更严格的一侧处理:我把它标成 GPL-3.0-or-later,附上 GPL 全文 COPYING,页面署名也照实写。它在 iframe 里独立运行、不进站点打包,所以不会传染到站点的代码——但标注错了就是侵权。
第二种:代码 MIT,美术不是。 炸弹人的代码是 MIT,精灵图却是 CC BY-NC-SA 3.0(非商业 + 署名 + 相同方式共享)。这个站没有广告、没有收费,符合 NC;署名靠游戏自带的致谢页保留,页面下方也补了一条说明。如果哪天要商业化,这个游戏必须撤掉——这条我写进了数据文件的注释里。
第三种:音乐。 台球那首背景乐是 CC BY,署名要求可以满足,但它还是被我删了(体积)。删掉之后要在 THIRD-PARTY.md 里写清楚“移除了什么、为什么”,否则下一个人会以为它本来就没有。
三条合起来是一条规则:“仓库有 LICENSE 文件”不等于“这个目录里所有东西都能再分发”。
工程:三个真实踩到的坑
坑一:.gitignore 把游戏的入口脚本吃了。 盖楼那个项目的运行时代码在 dist/main.js,而站点的 .gitignore 里有 dist/。本地一切正常,部署到线上游戏就是死的——因为文件根本没进 git。修法是把目录改名为 app/,顺便在自检里加了一条“每个第三方游戏目录的入口页必须有 <script>”。
坑二:清理 analytics 的正则删掉了游戏自己的脚本。 为了满足“零第三方请求”,我用一条正则删 Google Tag Manager。写得太贪心,[sS]*? 跨过了整个文档,把游戏自己的 <script src> 一起删了。页面能开、控制台没报错,只是游戏永远停在加载画面。这类“看起来正常其实全死”的问题,只能靠断言兜住——所以自检现在检查三件事:入口页有脚本、没有第三方请求、没有根绝对路径。
坑三:tsconfig 把 public/ 也扫了。 第三方游戏里带着压缩过的 jQuery 和引擎源码,astro check 一进去就吐出几十万字符的告警。加上 "exclude": ["public"] 就干净了——静态资源本来就不该参与类型检查。
零第三方请求:把 CDN 全部本地化
这些游戏原本都靠外部资源跑。搬进来时逐个换成本地文件:
- Flexbox 青蛙:Google Fonts(三个字族 16 个 woff2)、jQuery、Animate.css,还有页面里的 AdSense 广告位——广告位删了,其余本地化;
- 放置 RPG:Material Icons 字体 + 一个指向作者 GitHub Pages 的外站 favicon;
- 云架构塔防:Tailwind 的 Play CDN 构建脚本和 three.js r128——这两个是硬依赖,不本地化页面直接白屏;
- HexGL:Google Analytics 和指向作者域名的 favicon。
一共 20 多个文件,每个都在 THIRD-PARTY.md 里记了来源与许可。换来的是:打开任何一个游戏,Network 面板里没有一条跨域请求。
边界:为什么是 iframe 而不是并进打包
站点自己的游戏走的是“静态空壳 + createApp 挂载”,和站点共享打包管线。第三方游戏不能这么干:
- 它们有自己的全局变量、自己的
window.onload、自己的 canvas 生命周期,塞进 Vue 岛里会互相打架; - 许可证要求“保留原样、保留版权声明”,改了就不是原作了;
- 体积上也划算——不玩的人一个字节都不下载。
所以播放页是一个 iframe 外壳:页头、页脚、主题、语言切换都在,游戏本体从 /games-vendor/<slug>/ 原样加载。代价是 iframe 里的键盘事件、全屏、音频自动播放都要单独处理,手机上尤其明显。
一句判断
自托管第三方游戏的真正成本不在“搬文件”,而在许可证的边界、体积的取舍、以及把别人的运行环境改造成离线可用。前两件是一次性判断,第三件是每个游戏都要重做一遍的脏活——做完之后,它们和站上自己写的游戏用同一套列表、搜索、分页和署名规则,这才是值得的地方。

评论
…