十个开源游戏、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 里的键盘事件、全屏、音频自动播放都要单独处理,手机上尤其明显。

一句判断

自托管第三方游戏的真正成本不在“搬文件”,而在许可证的边界、体积的取舍、以及把别人的运行环境改造成离线可用。前两件是一次性判断,第三件是每个游戏都要重做一遍的脏活——做完之后,它们和站上自己写的游戏用同一套列表、搜索、分页和署名规则,这才是值得的地方。

← 返回文章列表

评论

…