DEVELOPER'S JOURNAL
NOTE 02 · ONE EXPEDITION
联机战斗踩坑录——服务器权威、选牌暂停和东京那一周
为什么全交给服务器算、选牌时“你停世界不停”怎么定的、霸主四招、天气只改光,还有在东京重启服务器时遇到的各种离谱失败。
这些是我自己写的记录,会提到失败、犹豫和还没做完的事。它描述的是现在,不是最终形态,也不是任何发售承诺。
前言
一局远征从北门出去,到北门回来,中间四个人得看到同一场仗。这句话是整个战斗系统的第一条规则,也是最贵的一条:伤害、敌人、命中、升级、死亡、结算,全部由服务器决定,客户端只负责显示。自己的走位可以先走,剩下的都等服务器说了算。
技术上是这样落地的:权威模拟用整数 XZ 坐标,高度从锁死的高度场查表;浮点在 TypeScript 和 GDScript 两套运行时之间不可能逐位一致,而状态哈希必须对得上,所以干脆整数。协议 30 Hz,快照 15 Hz,客户端只预测自己的移动和朝向。省事的做法当然也想过——各算各的最后对一下——但那样“同一场仗”就成了四场大致相似的仗,谁掉线谁重连、谁先看到霸主抬手,全会变成扯不清的事。既然要四人同屏,“谁说了算”从第一天起就不能有歧义。
你停,世界不停
升级很简单:敌人倒下掉经验,二级到八级,每级三张牌选一张。牌的界面写的是「选择蜕变」而不是“升级”,因为一局里长出来的东西下一局不会带走。战技升级是小的,传奇升级在四级和八级,会把三张牌摊开来给你慢慢挑。
麻烦就在“慢慢挑”。单机里选牌暂停世界就好;四个人一起打,谁的世界暂停?最后定下来的规则是:选牌的人自己被锁住——不能动、不能攻击、也不会受伤——但森林里其他人照打,时钟照走;只有所有还能操作的人都在选牌,时钟才停。它不偏袒任何人,也确实有点残忍:你挑牌的几秒钟里同伴可能正在被围。所以小升级后来改成不弹窗,屏幕角落提示一句“按 1 到 3 选择”,边打边选,只有传奇升级才真的展开卷轴。
门后的四招
走到最后,遗迹之门另一侧站着邪恶牛头人。它入场时会把场上剩下的敌人换成恰好四只哥布林,不多不少,之后不再补。然后是四招:近战、斧刃横扫、震地践踏、蛮牛冲撞。每招落下前地上都画着预告——扇形、圆形、一条长走廊——边框先出现,再慢慢填满。血量掉到三成会咆哮一声,接下来更快、收招更短。
预告上花的时间比伤害数值多得多。数值可以调,玩家看不懂“接下来会发生什么”的话调什么都白搭。一个人做游戏测试也是自己测,最容易骗自己的地方就是“我知道它下一招是什么”——废话,代码是自己写的。所以预告必须画到第一次见的人也看得懂。
实机截图 · 开发中版本雨改的是光
战场有三种天气:晴天、雨天、有风。雨天压暗光线、把颜色调冷,雨线落下来,地面开始反光;有风的时候白色风痕横穿战场,树冠往一个方向倒——树干不动,只有叶子在摇。
这里得把话说满:天气只改画面。不改地形、不改碰撞、不改敌人的判断、不改伤害,也不同步到服务器。切换入口目前还在开发者调试抽屉里,玩家界面没有。官网上那个晴天、雨天、有风的切换器就是它看起来的那样:同一个战场同一个机位,三种表现的对比。“动态天气改变战术”当卖点当然更带劲,但那不是现在的实现,所以不那样写。网站上每一句话都得能在仓库里找到对应的东西,这是这个项目自己给自己定的规矩。
东京那一周
重启验证
联机部分最难受的一段不是写代码,是证明一件事:服务器在战斗中途重启,结算必须正确作废,不给任何人发奖励。为此在东京的服务器上一次一次地重启,而失败的方式一次比一次离谱:一次是凭证过期;一次是一个命令同时传了 stdin 和 input 两个互斥参数,Python 直接抛异常,子进程压根没起来;一次是校验器把 (None, [], []) 和 (None, tuple(), tuple()) 当成两个东西,于是永远不可能说“是”;最后那次是容器停机前有三十秒缓冲,模拟在这三十秒里继续往前跑,跑过了“作废”变成普通的“失败”,把三十秒改成五秒才对。
规则是这类操作不许重跑、每次单独批准,十几次之后属实撑不住,留了句「不要让我批准,我批准wp4的任何行为」,后来真成了正式授权。那一周的最后,联机阶段的收尾在一项指标上差一——三十分钟混合负载最长连续超时三次,合同要求少于三次——先带着这个缺口合了往前走,缺口原样记在文档里,没有粉饰成通过。
道具没有碰撞
玩出来的
8 月 6 日自己进去玩了会儿,发现场地里的道具没有碰撞——人能从石堆和台座中间直接穿过去。另外树冠遮挡淡出也没了:角色走到树后面叶子本该变透明,现在不透。
第一个问题的修法不能是“给道具加碰撞体”,因为位置是服务器算的,客户端挂上去的碰撞体对玩法来说不存在。阻挡必须写进服务器那边的场地数据,于是场地的身份变了,已经打包出去的旧客户端进不了新对局。一句“道具没有碰撞”,代价是一次内容迁移。第二个问题没那么戏剧:淡出代码从来没被删掉,只是新场地里没有它要找的那个节点,于是安安静静地失效,不报错,就是不工作。
两件事都是玩出来的不是测出来的,几千项检查里没一项在问“人能不能从祭坛里穿过去”。
后记
8 月 10 日试着把东京的服务器恢复到上一个稳定版本,失败,证书链校验没过;自动回滚成功,服务还在。回滚成功不等于恢复成功,文档里就这么分开写的。也是从那天起,局域网成了这个项目“负担得起的开发部署”:同一套连接方式、同一套协议和身份,只是不在东京。
战斗还在改:四人能跑,霸主四招在,天气在抽屉里,结算全有或全无,手感、数值、观战细节都在动。这篇记的是它现在的样子,和它为什么长这样。