Ball Busters 是一款基于六边形网格的方块破碎游戏:发射一串编号方块的球,每收集一个球,它会加入下一次射击,清空板子即可触发 KABOOM。我已经在 Web 端开发了约 20 年,想尝试使用 Unity,并且一直热爱方块破碎类游戏(至今仍然热爱),所以我为此构建了它。2023 年我用 Unity 2021.3 在两个店铺发布,随后搁置了。今年店铺跟上了我。Google Play 要求从 8 月底起的任何更新都必须使用 API 36,而 Apple 则要求自 4 月起所有内容必须使用 Xcode 26 与 iOS 26 SDK 构建。 Unity 项目若要保持不动,需要升级引擎并修复插件,我不想两年后再做一次这些。于是我用 TypeScript、Phaser 和 Capacitor 重新构建了它,发布为 2.0.0,使用相同的包名和 App Store 列表,替换了 Unity 版。

What carried over

  • The spec. 在编写任何新代码之前,我逐一检查了每个 C# 脚本、场景和预制体,记录了每个机制、数值、字符串以及边缘情况:球速、网格大小、特殊概率、广告频率、起始生命数。该文档是真正的资产。重建是同一款游戏,因为规格相同,而非因为任何代码被移植。
  • 六边形网格和规则、精灵、字体和音效,以及用于身份的 Illustrator 源文件。

What's better now

  • Physics. Unity 版使用引擎的物理,外加两段补丁脚本,分别防止球体穿透和破解水平循环。重建版自行实现固定步长的扫射圆形碰撞,针对六边形轮廓、墙壁和地板进行精确反射,并在每次弹跳后重新标准化速度。加速运行时每帧执行更多固定步数而非缩放 dt。没有穿透,没有轻微调整。
  • 每日挑战。每个人都获得相同的板子,但每天的变体不同。日期规则必须保持不变,否则旧版玩家会在同一排行榜上看到不同的变体。因此新变体会有一个开始日期,从该日期起每日板子的 ID 也会变化:旧版保持发布到旧板子,不会让玩家被排除,每个板子只保留一个变体。
  • 强制更新检查。应用启动时向服务器请求最低版本,离线或网速慢时仍可进入。此前的版本无法强制更新,这正是我希望尽早加入的原因。
  • Cubez。第二种模式,每关方块在六边形和立方体之间切换,直接在六边形网格上实现。尖顶六边形是从角度看到的立方体,切换过程是阴影立方体从角度向接近正面旋转,同时板子下降,平坦面让球以六边形从未有的角度弹出。

What bit

  • AdMob 和 iOS 音频会话。广告 SDK 默认管理会话:在视频广告播放时切换类别,并在之后停用会话,这导致网页视图的 Web Audio 继续运行但没有声音。广告播放后游戏变得安静。解决办法是告诉 SDK 由应用自己管理会话,并在广告后第一次点击时暂停并恢复 Web Audio,因为 iOS 只有在手指抬起时才会恢复音频。
  • ITMS-91064。苹果因隐私清单拒绝了该构建:追踪标记为 true 但未列出任何追踪域。列出 Google 的广告域会导致 iOS 对不接受追踪的用户阻止它们,所以答案是将追踪设为 false,让 Google SDK 自行声明。
  • Homebrew 的 rsync 破坏了 Xcode 的归档导出。Xcode 调用 rsync,而我的 PATH 中 Homebrew 的 rsync 在前。将 /usr/bin 提前于它,就解决了导出问题。
  • 死亡关卡上的全屏广告。插页广告在 20 回合后应出现,却出现在板子下落和游戏结束之间,导致死亡关卡在广告上冻结,直至广告关闭。现在广告等到下一回合准备好瞄准后才播放,所以结束游戏的下落总会先解决。这个将在下次更新中修复。

What I'd do again

Write the spec from the old code first. Own the physics. A Capacitor web view has been fine for a 2D game like this. Keep the bundle ID and the listing.

One honest note: I used Claude as a coding assistant for a good deal of the rebuild's code. The design, the original game and every decision above are mine.

It's free on the App Store if you want to see the cube turn for yourself: https://apps.apple.com/app/id6446183092