2025-05-08 14:48:19
纯单人游戏在桌面游戏中不太多见,但我很喜欢这种。毕竟,找人一起玩桌游太不容易,虽然多人协作桌游总可以一个人操控多方进行 solo ,但终究不是为单人游戏设计的。今天介绍的两款单人卡牌游戏,我没买到实体版,都只是在桌游模拟器上玩过几盘。
第一款是 Legacy of Yu (2023) 大禹治水。老实说,这不是一款卡牌“构筑”游戏。虽然在游戏过程中玩家还是需要从市场列“购买”新卡片,但游戏过程并不是围绕构筑进行的。这些卡牌更像是消耗品。
游戏中的工人卡有三种用法:
打出后获得卡片上标注的资源,然后进入弃牌堆。
销毁一张工人卡,获得卡片上额外标注的一次性资源,卡片将移出游戏。这样获得的资源一定比前一种方法获得的多。
把卡牌(常驻)押在版图中已经盖好的房子上,此后的回合每回合获得持续资源奖励。
和一般的卡牌构筑游戏不同,卡堆不是越少越好。一般的卡牌构筑游戏,精简卡堆总是好的,因为这样可以加快卡堆循环,能更快抽到自己需要的强力卡。而这个游戏中,当抽牌堆耗尽,游戏进程就会向前推进。一旦准备不足,推进游戏会加快失败进程。虽然,游戏过程中,除非迫不得已,都不要销毁工人卡获得额外资源。
游戏中有砖头、木头、粮食、货币(贝壳)四种资源,以及白色劳工、红色战士、黄色弓箭手、黑色骑士、蓝色枪兵五种工人。
资源可以用来做建设:砖头+木头+劳工=农场(三个待建),砖头+3x木头+劳工=前哨(四个待建),3x砖头+木头+劳工=房屋(四个待建),运河。
农场效果是固定的,为后面的轮次每回合增加固定产能。三个农场分别对应粮食、劳工、其它任意工人;前哨可以让四种特殊工人和白色劳工相互替代;房屋的触发效果是随机的,标注在房屋卡片背面,大多数是触发说明书上的事件。建成房屋还可以为工人提供工作地(将工人换成对应资源)以及常驻资源产地(减少卡组中的工人卡换取持续产能)。
游戏需要玩家以不断增加的成本修建六段运河。成本会逐步递增,一二段需要两个劳工及两个贝壳;三四段需要三个劳工及两个贝壳;五六段需要两个劳工两个指定颜色工人及三个贝壳。每段运河修建成功后,可获得一次性奖励并摧毁部分抽牌堆中的工人卡,以及触发事件。并可以为之后的游戏回合带来一些贸易选项:
贸易选项是固定的:
两个贝壳换一个粮食
一张弃牌换两个贝壳
劳工及粮食转换为任意工人
四个贝壳换一个劳工
两个粮食及两个贝壳增加一张工人卡
砖头或木头换一个贝壳
玩家需要在修完六段运河后存活到回合结束才能赢得游戏。
在游戏版图上方由预备工人卡和蛮族卡共享市场列。蛮族卡越多,可选择的工人卡就越少。一旦市场列全部挤满蛮族卡,游戏就会失败。
市场列的最左端位置上的工人卡总是免费的。玩家可以选择拿取放在弃牌堆,也可以直接销毁获得一次性资源。而蛮族卡会随着修建运河的进程逐步进入市场列。未消灭的蛮族,在每个回合结束会收取贡品,通常贡品可以用销毁一张工人卡抵消,但如果工人卡被销毁光也会导致游戏失败。击败蛮族需要支付卡片上标注的指定种类的工人,击败蛮族后会获得卡片上标注的一次性奖励。
这个游戏有传承机制,熟悉基础规则后,可以根据说明书逐步解锁新的玩法:游戏难度会随着游戏进程慢慢加强,并引入一些新的游戏机制。因为根据单局游戏失败或成功,导向不同的游戏进程,难度也是动态调节的。
这个游戏不是很热门,可能是因为它只能单人游玩,在国内很难买到实体版。但这个游戏我非常喜欢,桌游模拟器上有汉化过的电子版。
Kingdom Legacy: Feudal Kingdom (2024) 是一个较新的游戏。它和很多传承类游戏一样,几乎只能玩一次。因为游戏过程中会涂改卡牌,这个过程是不可逆的。而且卡堆一开始在包装内的次序是设计过的,一旦打乱还原比较麻烦。后期的一些卡片一旦被巨头,也会失去一些探索未知的乐趣。
可能是因为每开一局都需要新买一盒游戏的远古,这个游戏各处都缺货,不太好买到。好在其官网有所有卡片的电子版本,可以方便做研究。
游戏规则简单有趣,整个游戏过程是升级卡牌。大部分卡片有四个状态:两面每面上下两端。卡牌升级指支付一些资源,让卡片旋转到另一端或翻面(根据卡片上的指示),同时结束当前回合;卡片上也可能有一些标注的效果让卡片状态变化。资源不使用额外指示物,而是弃掉当前的卡片获得(同样标注在卡面),资源必须立刻使用,无法保留到下一回合。
这个游戏的“构筑”过程颇有新意。它没有主动挑选购买新卡的环节,但一旦抽牌堆为空,就会结束一大轮,会从卡堆中新补充两张新卡(以设计过的次序,没有随机元素)。而玩家的主动构筑在于对已有卡片的变化(升级)。
每一个回合,抽四张手牌,用其中三张的资源换取一张卡的升级。在玩的过程中,如果当前回合资源不足以升级卡片,可以选择增加两张新卡继续当前回合;也可以选择 pass ,放弃当前所有手牌,重新补四张。游戏不会失败,不管怎么玩,游戏都在抽到第 70 号卡后结束,并结算得分。玩家可以以得分多少评价自己玩的成绩。游戏包装内不只 70 张卡片,超过编号 70 的卡片会在游戏进程中根据卡片上描述的事件选择性加入游戏。
游戏中有六种资源:金币、木材、石头、金属、剑、货物。它们对应了不同卡片的升级需求。通过卡牌升级,把牌组改造为更有效的得分引擎是这个游戏的核心玩点。部分卡片是永久卡,可以在游戏过程中生效永久驻留在桌面提供对应功能。有些卡片会被永久销毁,如前所述,一盒游戏只能玩一次,所以一旦一张卡片被销毁就再也用不到了。按游戏规则的官方说法,你可以把已销毁的卡片擦屁股或是点火。
为了方便初次游戏熟悉规则,在翻开第 23 号卡片之前,可以反复重玩,玩家可以不断的刷开局;直到 23 号卡片之后,游戏才变得不可逆。而在游戏后期,玩家牌堆会越来越大(因为每个大轮次,即抽完牌堆后后会加入两张新卡),这时就引入了支线任务 ,在游戏术语中叫做扩展(expansions)。触发支线时,需要清洗(销毁)一张桌面的永久卡,并执行一次 purge 12 动作。即洗掉牌堆,抽出 12 张卡,选一张保留,并销毁另外 11 张卡。但 purge 的这 11 张卡上的分数可以保留下来,记录在支线得分中。支线会让牌堆维持在一个较小的规模。
每个支线都会持续 4 轮(对应卡片的四个状态),每轮旋转或反转支线卡推进任务。支线的每个状态都会有一个当轮生效的效果。游戏的基础包中有三个内置支线卡,额外的扩展包增加了许多任务(以及额外的卡片)。一局游戏可以最多触发 10 个支线。
2025-04-10 22:52:40
最近读了一本书:《数学的雨伞下》。阅读体验非常好,这本书用浅显的语言,科普了许多深刻的道理。这本书所介绍的知识结构比较类似我挺喜欢的另一本《从一到无穷大》,但讲解更为细致一些,以至于如果事先明白这些知识,甚至会觉得有些冗长。但细细品味,会觉得理解能更深一层。
我在通读完一遍之后,这几天带着儿子精读。重读第一章中“对数之桥”一节时,我思考了一个问题:当年纳皮尔 Napier 到底出于什么动机制作一张高精度对数表,他制表的计算思路是怎样的。书中并没有答案,所以我又在互联网上翻看了当年 Napier 原著 Mirifici Logarithmorum Canonis Descriptio 的介绍,感觉收获颇丰。
制作对数表的直接原因当然是为了简化大数乘除法的计算。对数概念的提出在幂概念建立之前,而现在的数学教学中,一般却是从幂自然推导到对数的。似乎后者才是自然而然的。这应该是因为,古人研究数学,最初是为了解决现实中的问题。所以,乘法必须有对应的几何意义。比如,计算正方形面积需要把计算边长的平方;立方体的体积需要计算边长的立方。更高次的幂却难有对应的几何意义,有理数幂则更为抽象。
现实中,也很难碰到极大的数字,超乎寻常的精度需求也很小,除了天文学。
人无法以上帝视角在宇宙空间中做测量,只能以地球为基点。所以,天文尺度的计算都依赖三角学。把天文(以及地理这种地球尺度的)数字问题化为三角函数,然后再加以计算。比如,测量地球到太阳的距离、地球到月球的距离、地球的直径都是这样。因为这些尺度都非常大,如果计算精度不够,就容易失之毫厘,差之千里。
为了测算太阳系内天体的距离,可以在地球表面找两个尽可能远的点(最多相距地球的直径),观察天体,记录下天体在视野中的角度。这样,地球表面的两个端点和被观察的天体,就构成了一个三角形。三角形的底边就是两地的距离,而顶角则可以对比两地观测的结果得到。这就是三角视差法。可想而知,对于太阳系内的天体,这个视察角度非常小,需要极高的观测精度和计算精度才能计算出距离(远大于地球直径)。
甚至,这个方法可以运用到测量附近恒星到地球的距离。这几乎是人类利用三角法能测算的最大尺度。在地球表面找两个点已经不够了,因为那最长不超过地球的直径。更长的标尺只有地球绕太阳的轨道:在一年中隔半年做一次观察,这两个观测点在宇宙空间中就隔了地球和太阳距离的两倍长,这总该够长了吧?其实不然。在这个尺度上,古人依然观察不到星星的位置相隔半年的星图中有所不同。这也是为什么日心说提出后,不光是神学家不接受,连天文学家(比如第谷)也不接受。
如果地球围着太阳转,而地球距离太阳如此之远,那么就算是恒星离得再远,地球位于太阳两侧时,总能观测到某些明亮(离我们相对较近)的星星位置有些许偏差吧?人类难以相像太阳系外的宇宙如此空旷。事实是,太阳以外的恒星离我们真的太远了,即使以地日这种天文距离为底边,和附近的恒星形成的等腰三角形的顶角也不到一秒。过去的测量工具的精度是完全不够用的。直到 19 世纪中叶(哥白尼死后 200 多年)人类才真正观测到天鹅座61/贝塞尔星 有 0.3 个秒视差,从而估算出离地球大约 10 光年左右。
测量精度是一方面,计算精度也很重要。在三角公式里算几个乘法,若是通过对数方法转换为加减法计算,而精度不够的话,恐怕结果会差上一个数量级。
纳皮尔在没有幂概念的基础上就发展出了对数概念,靠的还是寻求其几何意义。他的灵感来源并不是幂运算,而是三角公式。三角和差公式中,角度相加被转换为三角函数的乘法运算,这提示着,乘法和加法之间可以相互转换。纳皮尔的对数表也并不是现在意义的列一系列数字,逐个列出它们的对数。而是给出角度的三角函数值的对数。它可以看成是当时已存在的三角函数表的拓展。这也是为什么,纳皮尔的表只有 90 * 60 = 5400 项(对应四分之一圆周在分精度下的所有角度值),但数字精度却有小数点后 7 位。因为当时最精确的三角函数表是 7 位精度。
在没有计算机的年代,计算对数必须查对数表。那么最初的对数表怎么得到的呢?如果是按幂函数的逆去计算,那就涉及高次开方,人肉计算显然是不可能的。而且当时,还并没有发现对数和幂的互逆关系(那要等到 100 年后的欧拉),甚至连幂的概念都没有。
《数学的雨伞下》这本书为了让读者更容易理解对数表,举例子使用的是以 2 为底的对数。对数列是一个自然(等差)数列:1,2,3,4,5... ;真数列是 2, 4, 8, 16, 32 .... 这样一个等比数列。但实际这样制作对数表会难以实用,因为真数数列膨胀的太快了。如果要实用,最好真数数列的间隔不要太大。如果间隔太大,在利用它做乘法运算的时候,很多数字会偏差很大。
把对数用于快速计算乘法,选用怎样的底并不重要。当等差数列的差距为 0.00000001 时(因为当时的三角函数表有 7 位精度),等比数列的差值选为 1.0000001 或 0.9999999 最方便计算。因为这样,列出等比数列时,就不需要连续计算乘法,而只需要移位相加即可。一个十进制数乘以 1.0000001 只需要把这个移动 7 位的小数点,再加上原数即可。如果等比数列的公比为 1.0000001 ,其实是给对数表选择了一个以 (1+1/n)^n (n = 10^7) 的底。当然,这是现代数学的看法,在纳皮尔的时代,还没有发展出底这个概念。
纳皮尔研究的是三角对数,真数范围在 0 到 1 之间。当时的人并没有完整的小数和数级的概念。过去研究圆,使用的是一个超大的(10^7)的半径而不是今天流行的 1 。因为这样,三角函数才能近似为整数(对于 7 位精度,使用10^7 的圆半径,相当于比今天的三角函数放大了一千万倍)。btw, 纳皮尔在制作对数表的过程中,发明了小数点,用来保留计算过程中的精度。
他先构造了一个等比数列,再通过几何定义去计算其对数对应的等差数列。从现代观念看,纳皮尔选择的底约为 0.9999999^1000000 ,非常接近 1/e 。
在今天来看,如果我们想制作一张好用的对数表,真数列自然是越密越好。如果我们把 n 取无穷大,让 1/n 足够小,(1+1/n)^n 的极限即为欧拉数 e 。我想,这也是为什么欧拉数 e 被称为自然对数的底。纳皮尔的时代,无法通过“视对数函数为幂函数的逆函数”来建立这种直观的认识,人类深刻认识 e,要到百年后的欧拉。
关于纳皮尔如何制作对数表的,他自己写过构造方法一书。300 年后的 1914 年 EW Hobson 写了 John Napier and the invention of logarithms, 1614 纪念纳皮尔,详细讨论了纳皮尔原著中的方法。这篇文章在网上可以找到中文翻译。另可以参考这一篇文章 。
纳皮尔在计算过程中,充分考虑了计算的误差区间,严格保证他计算的对数表满足 7 位精度。他首先计算了 0.9999999 的 0 到 100 次方,然后计算 0.99999999^100 = 0.99999 的 0 到 50 次方。虽然计算这个等比数列只需要把前一个数字在十进制上移位并计算减法,这个计算工作并不难(并不需要算乘法),但纳皮尔在这一步把最后一项算错了:本应该是 0.999500122480 ,而他计算成了 0.9995001222927 。这个 bug 导致了使用最终的对数表会产生微小的误差(影响最后一位数字),纳皮尔自己觉得这个误差是三角函数表不精确导致的,并建议用 8 位精度重制三角函数表。
2025-04-08 21:10:54
这次介绍两款在国内人气不高的卡牌构筑类桌游。游戏都还不错,可能是因为没有中文版,所以身边没见什么朋友玩。
首先是 XenoShyft 。它的最初版全名为 XenoShyft: Onslaught (2015) ,后来又出了一个可以独立玩的扩展 XenoShyft: Dreadmire (2017) 。
故事背景有点像星河舰队:由人类军士抵抗虫子大军。简单说,这是一个塔防游戏:游戏分为三个波次,每个波次三轮,一共要面对九轮虫群的冲锋。
游戏中有四类卡片:部队、敌人、物品、矿物,另有一组表示玩家所属部门的能力卡,每局游戏每个玩家可以分到一张,按卡片上所述获得能力
矿物就是游戏中的货币,用来在市场购买部队卡和物品卡。敌人卡分为三组,对应到三次波次,洗乱后形成系统堆。玩家需要在每轮击败一定数量的敌人,撑过三个波次就可以取得游戏胜利。
玩家基础起始牌组 10 张,4 张最低级的士兵和 6 张一费的矿物。根据玩家的部门,还会得到最多 2 张部门所属的特殊卡。
市场由部队卡和物品卡构成,其中部队卡是固定的,分三个波次逐步开放购买。物品卡一共 24 种(基础版),但同时只会有 9 种出现在市场上。玩家部门可能强制某种物品一定出现在市场上,其它位置则是每局游戏随机的。在游戏过程中,当一种物品全部买空后,会在市场中随机补充一堆新的物品卡。普通敌人卡按波次分为三组洗乱,然后根据波次再从 6 张 boss 随机分配到三个波次中。
每个轮次,玩家先抽牌将手牌补齐到 6 张,然后打出所有的矿物卡,并根据波次额外获得 1-3 费,然后用这些费用从市场购买新卡片,花不完的费用不保留。新购得的卡片直接进入手牌(而不是弃牌堆)。这是一个合作游戏,所以玩家可以商量后再决定各自的购买决策。
然后,玩家把手牌部署到战区。每个玩家把部队卡排成一行(最多四个位置),物品中的装备可以叠在部队卡上增强单位的能力。玩家可以给队友的部队卡加装备(但不可以把自己的部队卡部署在队友战区)。部署环节玩家之间可以商量,同时进行。
之后进入战斗环节。这个环节是一个玩家一个玩家逐个结算。翻开敌人队列上的敌人卡(在部署环节是不可见的)、在敌人卡片翻开时可能有一次性能力,发动该能力、然后(所有)玩家都有一次机会打出手牌中的物品卡或使用部署在战场上的卡片能力。之后,双方队列顶部的两张卡片结算战斗结果。卡片只有攻击和 HP 两个数值,分别将自己的 HP 减去对手的攻击点。一旦有一方(或双方)的 HP 减到 0 ,战斗结束,把战斗队列卡片前移,重复这个过程。直到一方队列为空。
如果己方部队全灭,每场战斗的反应阶段(每个玩家都可以打出一张手牌或使用战斗卡片能力)依然有效,但改由基地承受虫子的攻击。基地的 HP 为所有玩家共享,总数为玩家人数乘 15 。可以认为基地的攻击无限大,在承受攻击后,一定可以消灭敌人。一旦基地 HP 降为 0 ,所有玩家同时输掉游戏。
游戏中的死亡效果有两种,毁掉(burning)和弃掉(discarding)。毁掉一张卡指把这张卡片退回市场(如果市场上还有同类卡)或移出游戏(市场上没有对应位置),而弃掉一张卡指放去玩家的弃牌堆。
通常,敌人卡片效果一次只会结算一张(即当前战斗的卡片)。但有些卡片效果会对场上敌人队列中尚未翻开的卡片造成伤害。这种情况需要先将所涉及的敌方卡片都翻过来,并全部结算卡片出场能力。对于需要同时结算多张敌人卡片出场能力时,玩家可以讨论执行次序。
如果对这款游戏有兴趣,又找不到人玩的话,可以试试它的电子版,在 steam 上就有。不过看评论,据说电子版 bug 有点多。
另一个游戏是 G.I. JOE Deck-Building Game (2021) 。G.I. JOE 特种部队是孩之宝(也就是变形金刚品牌的拥有者)旗下的一个品牌,除了玩具,有衍生的漫画、电影和动画片。这个桌游也是这个玩具品牌的衍生品。我认为这个 DBG 里的某些设定(不同的游戏剧本、同一剧本中不断推进的故事任务、队员的多种技能)也影响了星际孤儿那个电子游戏。
游戏有很多剧本、以及若干扩展。不同的剧本在规则细节上有所不同(这一点和星际孤儿很相像),这里只减少核心共通的规则。
这是个多人协作游戏。当然,只要是协作游戏,就一定可以单人玩,只需要你轮流扮演不同角色的玩家即可。每个玩家一开始有一张特殊的领袖卡,然后配上 9 张固定的初始牌组成了起始卡组。每个回合摸 5 张卡,用不完的卡会弃掉,不能保留到下一回合使用。每张领袖卡都对应了一个升级版本,可以在游戏进程中购买替换。
市场由一组卡片洗乱,随机抽出 6 张构成。每当玩家购买一张卡,就会补充一张新卡。但如果卡堆耗尽尚未结束游戏,游戏失败。在游戏过程中,可能有敌对卡片出现,会盖掉市场中的卡。玩家需要解决掉敌人,否则盖掉的卡片无法购买。如果 6 张市场卡片都被盖掉也会导致游戏失败。
当每个玩家执行完一轮行动,即为一大轮游戏。在每大轮开始,都会推进一个全局的威胁指示条。一旦威胁指数上升到某一程度,就会发生一些固定事件。维护指数走到头会导致游戏失败。
游戏故事由三幕构成,每幕随机选取两张对应的故事任务卡和一张固定的终局局故事卡,一共 9 张故事任务卡构成了整局游戏。永远有一个故事任务呈现在场景中,它有可能触发一个回合效果,需要在每个玩家回合开始时结算。
每一幕开始都会洗混所有的系统事件卡堆(包括之前解决完弃掉的事件卡),故事卡和威胁进度条会触发这些事件。这些事件会给玩家增加一些负面效果,或是在场上增加一些任务让玩家解决。
游戏任务分两种:团队任务和支线任务。故事卡一定是团队任务,事件产生的 boss 卡也是团队任务。团队任务可以在当前玩家决定去进行任务时,其他玩家提供协作;而支线任务只能由当前玩家独立完成。任务有地形、难度、技能需求、持续效果等元素构成。
地形指玩家开启任务需要使用怎样的载具,分陆海空三类。技能要求则限制了玩家可以派出的队员。难度数字决定了玩家最终需要在此技能上获得多少点才能完成任务。持续效果则会在该任务完成前,对玩家造成的负面效果。
开启一个任务需要玩家从机库派出一个对应地形的载具以及至少一个队员(从手牌打出)。该载具是在玩家回合开始时从手牌打在机库中的,VAMP 作为默认载具总可以保证一个回合使用一次。高级载具可以从市场购买。对于团队任务,所有玩家都可以协商派出队员,但队员总数不能超过载具的容量。
任务上标注了所需技能,派出的队员卡如果有符合的技能,则可以把技能点加到任务中。如果技能不匹配,也可以视为一个通用技能。任务卡最多要求两种技能,如果是 & 标记,则表示可以只要符合两种技能中的任意一种都可以生效;如果是 or 标记,则需要当前玩家选择其中一种技能,所有队员都需要匹配这种技能。
最终参与任务的技能总数决定了最终可以用几个六面骰。每个六面骰有三个零点,两个一点,一个两点;扔出对应数量的骰子,把最终点数相加,如果大于等于任务的难度值,则任务成功并或许成功奖励,否则任务失败。对于故事任务,失败需承受任务卡上的失败惩罚,并结束任务;对于其它任务,失败会让任务继续保留在场上。
有一类叫做 Precision Strikes 的任务,在翻出时需玩家讨论后决定放在谁的面前,变成它代做的支线任务。每个玩家最多只能放两张 Precision Strikes 在面前,到他的行动回合,必须先处理掉 Precision Strikes 任务。
在玩家做完所有想做的任务后,剩余的手牌可以作为够买新卡的费用。每张卡都标有一个自身的价格,以及一个在购买阶段可以当成几点费用使用。没有用完的费用不会积累到下个回合。购买的载具卡会在购买后直接进入机库,供后续回合所有玩家使用。其它卡片则放在抽牌堆顶。
整个回合结束后,弃掉所有手牌,以及回合中使用过的卡牌以及载具,重新抽五张。
玩家可以共建一个基地。这个基地由五部分构成,在游戏过程中逐步升级,升级也时通过购买完成的。在游戏过程中,以升级的部分也可能被摧毁或重建。这五个部件如下:
Repair Bay 会让任务中使用的载具都放在该处而不是弃牌堆。在回合结束(或被摧毁),Repair Bay 中的载具都会回到机库。这样,载具的利用率会大大提升。
Stockade 建成后,击败的 boss 卡会进入这里而不会重复进入游戏。
Battlestation 可以在团队任务中重掷一个骰子。
Laser Cannon 可以在支线任务中增加一个骰子。
Command Room 把手牌增加到 6 张。
2025-03-12 09:30:12
最近的兴趣重心转移,没怎么研究桌游。不过前几个月的笔记还有一点,今天继续整理一下。继续谈谈 PvE 向的卡牌构筑类桌游。
Aeon's End (2016) 末日决战是一款偏传统卡牌对战规则的游戏:一开始手牌中只有水晶 (gem) 和基础法术 (spell) 卡。游戏过程中,在一个固定市场(9 种)用水晶购买更强力的卡片升级自己的卡组。游戏的目标是合作(或 solo)击败 boss 或 boss 的仆从。如果被敌人攻击太多次,自己的 HP 减到 0 就失败了。
法术卡其实更像是炉石/万智牌中的玩家仆从,只不过是一次性消耗品。需要先部署在桌面,然后才可以攻击。而玩家卡组内的第三种遗物卡(relic)则更像是一般意义的法术:可以即时产生效果。
打出法术卡或遗物卡是没有直接费用的,水晶主要用来支付从市场购买新卡的费用。另外,法术卡要生效,需要桌面有空闲的位置(叫做 breach 裂隙)。所以即使手牌中的很多强力法术卡,也不能无限制的一次摆出来。
裂隙需要花费水晶费用打开才能使用(摆放法术)。打开 (open) 裂隙在一局游戏中是一次性的,一旦裂隙开启,就可以一直使用(放置法术)。但开启费用较高,也可以 focus 一个关着的裂隙一次性使用。focus 费用较低,但下次使用还需要再次支付水晶 focus 。focus 同一个裂隙 4 次后会自动开启。在开启的裂隙上摆放的法术,玩家可以选择再后续的任意回合生效;但通过 focus 预备的法术则必须在下一回合用掉。
每个玩家角色有一个独特的能力,这个能力需要 charge 后才可以使用。每次 charge 的费用是 2 水晶,不同角色使用能力需要的 charge 数量各不相同。
这个游戏规则中比较独特的是:玩家回合结束后,未使用的卡牌不会自动丢弃,但摸牌是将手牌补齐五张。即,打的牌少,卡组循环也会变慢。而弃牌是有次序的,正面朝上依次置入弃牌堆。抽牌堆用完后,弃牌堆不洗牌,直接反过来形成新的抽牌堆。
游戏的随机性不来源于抽牌堆的乱序,而是每个轮次的次序。每个轮次,玩家和敌人的指代物会放在一起洗混,其随机次序决定了这个轮次中,哪个玩家(如果有多个)以及敌人谁先行动谁后行动。
末日决战这几年口碑不错,一直在发新的扩展。尤其是新的版本加入了传承机制,也就是游戏可以一局局玩下去,玩家可以升级和继承过去的能力。玩家主要的升级是角色本身的技能,敌人也会逐步加强。
另一款同期发行,口碑不错的合作类 DBG 是 Clank!: A Deck-Building Adventure (2016) 。
这款游戏的玩法不是纯粹的打牌,而是加入了版图移动机制。玩家需要在版图上移动获得神器和宝藏(计分)。它并不算是一个合作游戏,更像是玩家有一个共同的敌人(系统),而玩家之间则需要比拼谁拿的分更多。
系统会攻击玩家,当至少有一个玩家 HP 减到零时,游戏会进入结束倒计时阶段。获得一个神器并逃离游戏的玩家会获得额外计分奖励。玩家需要在版图上获得一个神器的基础上尽可能的得分(获取宝藏)并在游戏结束前脱离。玩家如果没能获得神器,得再多分也不计算。游戏的结束条件是由第一个被击倒的玩家开启,但第一个被击倒的得分损失很大,所以玩家应尽可能的活得更久。
游戏规则中比较有特色的是 Clank 系统(也就是游戏的名称)。有些卡牌效果会给玩家增加 Clank 方块的数量。而系统发起攻击时,会把所有玩家获得的 Clank 方块以及系统方块一起放入抽取袋再抽出来。抽到 Clank 数量决定了对应玩家会掉多少 HP ,所以 Clank 越多(尤其是比队友多)意味着会更快挂掉。系统方块更多意味着玩家受伤害的概率越少。不过,每次系统发动攻击抽取出的黑色方块(系统方块)都不会再次放入抽取袋中,这就意味着系统攻击造成的伤害会越来越大。
市场(地城)上有三类卡片:一次性效果卡、小怪、新的可以加入卡组的卡片。这和 Ascension 非常的类似:玩家可以选择不同路线增强自己的能力,杀怪或购买更强力的卡片。
市场上除了随机卡堆翻出的六张卡外,还有固定卡堆:一个技能点及两点攻击(剑符号)的雇佣兵,两个技能点及一点移动力(腿符号)的探索卡、价值 7 分的宝藏卡、需要 2 点攻击击败的小怪哥布林。
每个玩家行动结束后,都会把市场补全六张。当市场上得卡上出现龙符号时,系统发起攻击。
玩家的牌主要有三种能力:
击败怪物可以获得金币,这是和技能点不同的第二种游戏货币。金币在游戏结束时可以用于计分,也可以在游戏过程中花掉购买物品。物品有固定的三种,价格均为 7 金币:
打牌过程和传统的领土一样:每个回合的手牌必须全部打出,不可保留到下一回合。每个回合抽取新的 5 张手牌。抽牌堆用完后,洗混弃牌堆形成新的抽牌堆。
Clank! 在最近几年也一直在出各种扩展,同样发行了传承版本。
传承机制加入后,游戏更有故事性。每个章节会加入一些事件卡以及每局游戏的合约(单局游戏目标)。玩家需要扮演不同角色(有一点不同的能力),并可以随着游戏进程增强能力,游戏版图也会慢慢开放。
2025-03-11 11:13:58
我在 Windows 10 下倾向于通过 store 安装 app ,而不是使用独立的软件安装包。这样安装的 app 会比较安全。但正因为这些额外的安全措施,有些过去很容易做的事情却变得麻烦了。比如,把一个特定类型的文件关联到 app 上。
我习惯用 IrfanView64 查看图片。在游戏开发中常见的 .dds 文件它也可以打开(需要额外安装官方插件)。但 IrfanView64 在提交 store 发布时没有提交 .dds 这个后缀名,这导致 windows 10 无法选择用它打开 .dds 文件。在自选打开特定文件类型文件的设置菜单里(设置-默认应用),你会发现怎么都找不到这个应用。
通常,你很难找到 store app 的执行文件路径。它在 %ProgramFiles%\WindowsApps
目录下,这是一个特殊的目录,受 Windows 系统保护。但可以在启动应用后,开启任务管理器,找到对应进程打开文件所在位置找到它。
我通过 google 找到了一篇吐槽文,也是谈这个问题。这篇 blog 也是通过这个方法找到了 store app 执行文件的位置。有了这个地址似乎就可以在打开文件的设置菜单设置本地程序路径了。
但估计是最近的 windows 更新又进一步增强了安全机制,即使你这样设置打开 .dds 为 Irfanview64 app 本地安装的程序路径,双击 .dds 文件既然会出错。(缺少访问该程序的权限)
我想还是得自己动手改注册表。打开 regedit 注册表编辑器,找到 \HKEY_CLASSES_ROOT\.dds\OpenWithProgids
这里是 .dds 的备选程序列表。ps. 如果从来没有设置过 .dds 的关联程序,可能没有 OpenWithProgids
这个子项。创建出来即可,或随便设置一次 .dds 的打开方式也会被系统创建出来。
下一步在这里添加 Irfanview64 这个 app 项。每个 store app 都有一个 id 。Irfanview64 的为 AppXhg16hybbkbv7j3fk4s35ykmfvp63yx63
。其实我也不知道怎么获得这个 id ,但我的系统上 .jpg 或 .png 等都可以通过 Irfanview64 打开,找到 \HKEY_CLASSES_ROOT\.jpg\OpenWithProgids
把它抄过来就好了。
一旦添加了对应的 key ,然后再去 .dds 下选择打开应用,就会发现在右键菜单里多出来 Irfanview64 app 的选项。
2025-02-20 10:46:18
考虑到我想做的独立游戏并不需要以画面效果吸引人,游戏是策略向的,所以 2D 表现就足够了。之前几年做的 3d 引擎对这个需求来说太复杂了,而且这次我也不打算主打移动平台,之前为移动平台做的考虑也没太大意义。所以,最近想花个把月重新搭一个 2d 游戏用的框架。当然,最重要的是:我太久没写代码了,而做这个非常有趣。
前天在 github 上开了一个新坑,具体想法写在项目的讨论区了。
虽说 2d 游戏在如今的硬件上,性能那是相当富裕。但在具体写代码时,还是忍不住想想怎么设计,性能会比较好。不然总是重复大家都有的东西也是无趣。
在现代 GPU 上实现一个最简单的 2d 管线,就是把它当成 3d 网格,一堆顶点数据填进去,绑定贴图,提交渲染即可。所谓 2d 图片,就是两个三角形,看成是 3d 世界里的一个面片即可。
所以,每个顶点的数据就是 vec2 pos ,要画一个矩形需要四个顶点,用 vertex buffer 传进去。
但是和 3d 游戏不同,2d 图片形状大多不规整,不是边长为 2 的幂的正方形,尺寸也不大。如果每张小图片(2d 游戏中通常称为 sprite)都构造一张贴图的话,会非常低效。通常我们会把很多 sprite 打包在同一张大的正方形的贴图上。这样,顶点数据中还需要定义绘制矩形对应在贴图上的区域,通常称之为 uv 坐标。至此,常规的实现方法中,每个顶点就是 4 个数据量:vec2 pos 和 vec2 uv 。因为 sprite 都在同一张贴图上,一次图形指令提交只画一个矩形就太浪费了,我们会把多个矩形的顶点放在一起,一次把整个顶点数组提交到 vertex buffer 中。
虽然 2d 游戏的大多数 sprite 只需要指定屏幕(画布)坐标渲染即可,画布可以整体缩放。sprite 单独缩放旋转的机会比较少,但也并非没有。用上面的方法怎么处理旋转和缩放呢?过去常见的方法是在 CPU 中计算好四个顶点,把结果填在顶点数据流中。btw, 很早以前,我在实现 ejoy2d 的初版时就是这么做的。这样最为灵活,CPU 计算一个 2x3 的矩阵也不慢(ejoy2d 使用了定点数,在早期的手机上性能更好)。而且,大多数 sprite 并不需要旋转和缩放,只需要做一次 vec2 的加法即可。
计算该怎么做?我们需要找到 sprite 的基准点。大多数情况下,这个基准点并不是图片的左上角。然后以这个点为坐标原点,对 sprite 的四个顶点依次做旋转和缩放变换再加上 sprite 的绘制位置。这一系列运算相当于乘一个 2x3 的矩阵。如果我们想把这个运算放在 GPU 该怎么做?顶点数据流中就不能直接放顶点计算结果的坐标了,而应该放针对 sprite 的基准点的相对坐标,以及一个 2x3 的变换矩阵。这样,顶点数据就变成了:vec2 offset ; vec2 uv ; mat2 sr; vec2 t; 一共是 10 个数据。
很明显,后面这个 mat2 sr; vec2 t; 在数据流中重复了 4 次(一个 sprite 的 4 个顶点有相同的 2x3 矩阵)。另一方面,绝大多数的 sprite 不需要旋转和缩放变换,这种情况下,mat2 sr 都是单位矩阵;即使有旋转变换,旋转角度也是有限的。整个数据流中必然存在大量重复的 mat2 sr 。怎么优化掉这些重复数据呢?我们可以用一个 storage buffer 保存唯一的 mat2 sr ,在顶点流中保存一个索引 index 即可。这样,顶点数据就剩下 vec2 offset; vec2 uv; index; vec2 t; 7 个数据。最后这个 vec2 t 不放在索引中是因为大多数 sprite 会有不同的位移坐标,而 2x2 的 SR 矩阵更容易合并。
接下来的问题是,index 和 vec2 t 还是重复了 4 次。为了去掉这个重复,我们可以采用 instance draw 或 indirect draw 。理论上用 indirect draw 更合适,但它对图形 api 版本要求高一些(如果想运行在 web 上,还是需要考虑这点),所以我选择用 instance draw 实现。
使用 instance draw 的一个额外好处是可以省掉 index buffer ,使用三角条带描述矩形即可。
但 instance draw 有个问题:它最初是为了把一组顶点数据重复渲染设计的。而这里,我们有很多不同的矩形需要同一批次渲染。即,vb 中每组数据 vec2 offset; vec2 uv; 有很多组。所以,我选择不使用顶点数据流,把这组数据放在另一格 storage buffer 中,然后在顶点着色器(vs)中通过 gl_InstanceIndex
和 gl_VertexIndex
索引它。
做到这里,我注意到:2d 游戏中的 sprite 矩形都是轴对齐的。所以,描述四个顶点并不需要 8 个量,而只需要 4 个,保存两个对角顶点即可。另外,offset 矩形和贴图上的 uv 矩形形状也是一致的,我们只是把贴图上的一个区域完整映射到画布上,这样还可以少两个重复信息。最终,我们只需要 3 对 vec2 就可以表达一个矩形以及 uv 。
而图片是以像素为单位的,贴图尺寸不会有几万像素大。这个坐标量使用 int16 足够了。所以在保存 sprite 元信息的这个 storage buffer 中,每个图元其实只需要 6 个 int16 ,也就是 12 字节足够了。最终,绘制每个 sprite 的数据为 6 short + 3 float ( x,y,index ) = 26 字节。
layout(binding=0) uniform vs_params { vec2 texsize; vec2 framesize; }; struct sr_mat { mat2 m; }; layout(binding=0) readonly buffer sr_lut { sr_mat sr[]; }; struct sprite { uint offset; uint u; uint v; }; layout(binding=1) readonly buffer sprite_buffer { sprite spr[]; }; in vec3 position; out vec2 uv; void main() { sprite s = spr[gl_InstanceIndex]; ivec2 u2 = ivec2(s.u >> 16 , s.u & 0xffff); ivec2 v2 = ivec2(s.v >> 16 , s.v & 0xffff); ivec2 off = ivec2(s.offset >> 16 , s.offset & 0xffff) - 0x8000; uv = vec2(u2[gl_VertexIndex % 2] , v2[gl_VertexIndex >> 1]); vec2 pos = uv - ( off + ivec2(u2[0], v2[0])); pos = (pos * sr[int(position.z)].m + position.xy) * framesize; gl_Position = vec4(pos.x - 1.0f, pos.y + 1.0f, 0, 1); uv = uv * texsize; }
再来看 CPU 侧的设计:
我这次使用了 sokol 做底层图形 api 。sokol api 不支持多线程,所有图形指令必须在同一个线程提交。所以我做了一个简单的中间层:绘图时不直接调用图形 api ,而是填充一个内存结构。这个结构被称为 batch ,不同的线程可以持有多个不同的 batch 。所有 batch 汇总到渲染线程后,渲染线程再将 batch 中的数据转换为图形指令以及所需的数据结构。
因为 2d 游戏据大多数情况都在处理图片,使用默认的渲染方式。我对这种默认材质做了特别优化。batch 是由这样的结构数组构成:
struct draw_primitive { int32_t x; // sign bit + 23 bits + 8 bits fix number int32_t y; uint32_t sr; // 20 bits scale + 12 bits rot int32_t sprite; // negative : material id };
其中,用两个顶点 32bit 整数表示 sprite 的画布坐标;一个 32bit 整数表示旋转和缩放量;一个 sprite id 。
渲染层会查表把 sprite id 翻译成对应的元信息(上面提到的 offset 和 uv ),当 sprite id 为负数时,表示这是一个非默认材质,batch 中的下一组数据是该材质的参数。例如,文本渲染就会用到额外材质,文本的 unicode 和颜色信息就放在接下来的数据中。