下载 App

一枚三角形撑起的十天:技术栈的妥协

10/21综合
2026-10-10
决定做《余烬荒原:群智回响》时,第一个问题不是玩法,是「怎么才能让别人双击就能玩」。
约束来自上一个项目。屏风镶嵌那套用的是 Vite + 原生 ES module,本地起服务一切正常,但把产物直接发给别人,双击 index.html 就是白屏 —— 浏览器对 file:// 下的 module 加载直接拦掉,控制台甩一行 CORS 报错,玩家看不懂,也不会去开什么本地服务器。
所以这一版的第一条硬规矩定得很早:不用框架、不用构建、不用 ES module。
具体落到三个决定:
  • 13 个模块全部写成 IIFE,挂在 window.EW 命名空间上
  • 入口页用 13 个 <script> 按依赖顺序加载
  • 没有 import / export,跨模块调用一律 EW.xxx
代价是明摆着的:
  • 改一个函数签名,得全项目搜索
  • 没有 tree-shaking,13 个文件全量加载(444 KB,还能接受)
  • 类型全靠命名规范和注释撑着
收益也很实在:
  • 双击即玩,不需要 Node、不需要 npm、不需要任何环境
  • 打包成 Electron 或 Android WebView 时几乎零适配
  • 调试时改一行刷一下就行,没有 HMR 的心智负担
后来还发现一个副作用:本地静态服务不发缓存头,浏览器走启发式缓存,改了代码刷新还是旧的。于是加了 BUILD 号配上 ?b=NN 的缓存击穿。这个小设计后来救了很多次命 —— 每次「我明明改了啊」的错觉,最后查出来都是缓存。
第一天的画面很简陋:一个青色三角形、几个绿色圆点、一片黑。三角形是玩家,圆点是资源节点。没有美术资源,也不需要 —— 这个阶段要验证的只有「能不能动」。
这个技术栈选型最大的价值不是性能,也不是开发效率,是部署的确定性。整套代码从浏览器、Electron、WebView 到别人的机器上,运行环境永远是同一个 —— 一个 <canvas> 加一堆 <script>。没有构建产物和源码的差异,没有开发环境和生产环境的差异,出了问题只看一份文件。
代价是工程化能力的全面缺失:没有模块化、没有类型检查、没有热更新、没有代码分割。对十天的 Demo 来说这是划算的交易。
TapTap
打包是另一门手艺:Electron 与 aapt2 的四次翻车截图
打包是另一门手艺:Electron 与 aapt2 的四次翻车
2026-10-17(补记于 10-20) 代码能跑和能上架是两件事。这一章记四次翻车。 一、APK 全设备秒退 症状:TapPlay 上传后所有设备打开即闪退,日志里一行都不给。 原因查了很久:d8 只喂了 MainActivity.class,没喂匿名内部类 MainActivity$1~3。dex 缺类,真机 onCreate 直接崩。 诊断过程里的坑比 bug 本身更值得记:判断 dex
官方
三个进度条互相打架:一个被误诊三次的 bug截图
三个进度条互相打架:一个被误诊三次的 bug
2026-10-19 这是整个项目最难查的问题,因为它表现得像三个不同的 bug。 症状 玩家同时靠近资源和节点,按住空格,驯养进度和修复进度会互相打断,其中一个突然归零。 第一次误诊:以为目标在乱跳 最直接的猜测是目标选择逻辑每帧重选最近的实体,导致焦点来回切。 改成「粘滞锁定」—— 一旦锁定目标,只要它还在交互范围内就绝不换。修完,问题从「频繁打断」变成「偶尔打断」。 有改善,但没解决。这种「
官方
当测试开始骗你截图
当测试开始骗你
2026-10-20 测试跑绿了不代表对。这一周遇到三种「测试骗人」,每一次都差点让我得出错误结论。 一、flaky test:连跑 6 次挂 3 次 input-check 里「空格驯养野生生物」偶发失败,tamed=false。 诊断第一步是孤立环境跑 40 次驯养,40/40 成功。游戏逻辑没问题。 真凶在测试代码本身:它用 g.allies.splice() 移除野生 ally,但玩家的
官方
给一群三角形配上音乐和片头截图
给一群三角形配上音乐和片头
2026-10-20 游戏从「能玩」到「像个作品」,中间隔着声音和画面。 BGM:交叉淡入淡出 需求是按阶段换曲:标题页和引导页用一首,发育期按友群共鸣等级分前后期,四种群核降临各一首。 实现用两个 Audio 元素轮换:新的从 0 渐强,旧的渐弱到 0 之后 pause(),交叉时长 1.8 秒。 几个细节坑: 不能硬改 volume 再 play(),会听到「咔」的一声 重复 setTrack
官方