有关《Minecraft》爆炸算法的拆解

又名我和我的1352条射线

0. 爆炸

在MC中,爆炸将会产生三种独立计算的效果:影响方块,影响实体,以及生成粒子。由于三者各自独立计算的缘故,可能会出现实际产生的效果相互不匹配的情况,如方块的破坏范围或爆炸粒子效果两倍外的实体仍然受到了足以致死的爆炸伤害。

影响爆炸的变量主要为为爆炸威力(Explosion power)和方块的爆炸抗性(Blast resistance)。前者决定爆炸的范围,破坏力和伤害等属性,后者决定方块阻挡爆炸能力的属性。实体另有一套机制以抵抗爆炸伤害,但这不影响爆炸本身的效果。

特别的,可能有其他以代码形式存在,而非常规游戏规则的爆炸类型,如凋零之首会将接触到的大多数固体方块的爆炸抗性重设为0.8。

1. 爆炸对方块的效果

受限于像素方块游戏的载体限制,MC并未使用“传统”的爆炸算法,或是可能被作为爆炸算法的一些向量计算逻辑,而是采用了一种与游戏绑定程度较高的方式,结合游戏中众所周知的“爆炸威力”和“爆炸抗性”等参数,计算爆炸对方块的破坏效果与掉落概率。

1.1 立体角(Solid angle)

在开始讲MC之前,先让我们来理解立体角的概念。所谓立体角,就是用于描述空间中角度大小的一个概念模型。

图例出自GAMES101

学过计算机图形学的同学可能会很熟悉我下面要讲的部分。现在我们将球面上一点用

表示,那么对于球面上一块区域面积的微分,可以用这样的方式进行表示:

在单位球面上略去球半径r,就可以得到微分立体角的定义:

有心的同学可能会发现,如果我们将球心视作爆炸原点,向外散射射线,那么既然能求出一块空间的立体角,是否就可以基于此,设计一套算法作为爆炸威力的离散模拟了呢?

这当然可以,不过这样的方法既然是基于现实世界,那么就不能直接套入MC中。由于后者以立方体结构作为游戏的最小元素,采取球体作为爆炸威力的原型难以找到对应的立方体参照物。且,如果出射无限多射线,那么相应的一次爆炸的内存占用也会高的吓人。

MC实际上采用了另一种方式来让爆炸对方块的破坏效果近似表现球体,以减少计算的压力并降低爆炸在方块世界中的不确定性。不过先让我们从这场爆炸事件的最基本单位开始。

1.2 爆炸射线

我们在1.1中提到过通过从爆心散射射线的方式以离散模拟真实爆炸产生的效果,这样做最显著好处便是大幅度减少了内存占用,加快了爆炸的处理速度。而对于不计算爆炸效果衰减,或是直接令一片区域受到爆炸效果的简单爆炸处理方式,这样毫无疑问更加真实。

我们假定P(Explosion power)为爆炸威力,R(Blast resistance)为爆炸抗性。每次爆炸效果产生衰减时,我们对这一射线的强度做重新采样,采样次数为n。由此,我们可以为射线定义初始强度:

其中的0.3便是这条射线在被生成时赋值的随机强度,由此可以得到其生成时的强度存在1.3p与0.7p两种等可能情况。因此,每次爆炸都有一定随机性,但在同一算法的控制下的一次爆炸仍旧显得可控,在真实性和游戏性上做出了平衡。

MC中,一条爆炸射线每延其方向前进0.3格,就进行一次强度衰减采样。强度衰减的比例为0.75单位强度/1方块长度。也就是说,爆炸射线每不受阻挡的经过1格空气方块的空间距离,理论上就衰减了0.75强度。但由于采样区间0.3与1并不能整除,且衰减并非连续变化,实际结果与用整数格的估计值会有所出入:

一般来说,使用0.225/0.3方块长度的参数计算会更加精确。

然而,实际的爆炸中爆炸射线有极大可能被方块所阻挡。当被方块所阻挡时,游戏采取另外一种强度衰减的采样方式,这种采样仍然以0.3格为界,因此可能出现在爆炸抗性极高的方块边缘引爆爆炸物,射线在方块边缘进行2次采样而忽略了方块,导致爆炸效果越过方块的情况。

2次可能的爆炸采样,实际越过了方块

MC中对于穿过非空气方块采样时,使用如下的修正公式:

任何强度衰减采样点上,未能使射线强度衰减于0的方块都会被破坏。

MC中,爆炸对方块的效果只考虑方块的爆炸抗性。即使该方块的形状或性质允许实体通过,如水,台阶,楼梯和蜘蛛网等。它们在爆炸中仍旧会被算作“一整个”方块。

1.3 最大&最小

由此,我们可以得到理论上的最大爆炸半径计量公式(此处以r表示,与爆炸抗性R区分):

而对于“能够完全抵抗某种爆炸所需的最低爆炸抗性”一则,为了自身不被破坏,方块需要在其碰撞箱内的第一个强度衰减采样点就吸收所有的爆炸强度。也就是一个方块能够吸收最大爆炸强度且保持自身不被摧毁时,我们可得该方块的最低爆炸抗性为:

特别的,此处的采样数n取决于碰撞体积与不同的爆炸形式。TNT与Creeper这样具有碰撞箱的爆炸会因为碰撞箱的存在,总是距离最近的方块0.49和0.5格的距离,这将n至少赋值为2。而其他类型的爆炸,如火球或Mod中的射弹爆炸,由于其可发生在任何地方,碰撞箱较小,这将n至少赋值为1。

当然,这些都是理论值。由于实际上爆炸射线初始强度的随机性,接近爆炸边缘处但爆炸抗性不足的方块并不总是被破坏。爆炸亦不存在累积性效应,即使多次爆炸波及到相同方块,依旧均为独立计算,不存在多次爆炸累计破坏某一方块的情况。

1.4 爆炸的表示

MC中采取爆炸自爆炸中心出射1352条向外延伸的爆炸射线的方式模拟爆炸。假设围绕爆炸中心正居于某方块中心,在此方块外表面划分为16×16×16的网格。则爆炸中心到网格上每个交点都会形成一条射线,总计1352条射线。但游戏中,爆炸中心并不总是居于方块网格中的某方块正中心,因此这样的射线划分总是采取临时构建的方式实现,并不存在实际参照物。

每条射线均按我们在1.2节中提供的公式赋值初始强度,并独立计算其衰减。

射线从爆心延伸至以其为中心,边长为2的立方体表面上时,此图等效真实情况的8倍比例

当然,实际上游戏中射线的运行仍旧等同于自球体外表面扩散,若按球面平均分布的方式出射,最终仍然会成为一个类球体,但MC并未这么处理。MC以立方体表面作均匀分割的方式出射射线会导致其爆炸在参数较大时出现明显的误差与反常,当爆炸威力逐渐增大时,爆炸的破坏效果在水平面上从圆形逐渐向正八边形转变,并最终向十字型逼近。

由于爆炸对方块效果的本质是从爆炸中心发射出1352条向外延伸的爆炸射线,而随着爆炸威力的增加导致爆炸覆盖距离的增加,这些射线之间的距离会迅速超过1格,以至于能容纳下许多方块。

如上图所示,爆炸威力过高时,其对方快的破坏效果呈现出古怪且反直觉的孔洞状。其中每处坑洞都来自最初爆炸时放出的1条射线。

如上图所示,在距离爆心较近时,射线越接近立方体的棱或角处,所经历的衰减采样次数就越多,而射线的密集度也更高。而在距离较远时,棱角处的射线密集度反而会大幅降低,导致大规模爆炸的边缘呈现出越发离散的破坏点。因此有大型爆炸的Mod,如IC2或龙之研究等,可能会重写原版提供的爆炸方法。

在下文中爆炸造成伤害部分,我们会讨论这样的分布可能造成非典型伤害产生的情况。

1.5 爆炸掉落物

被爆炸破坏的方式有一定概率掉落。被爆炸破坏的方块有1/P的概率满足战利品表中为minecraft:survives_explosion类型的掉落条件,并掉落对应战利品。否则该方块被摧毁,不会有任何东西掉落。特别的,龙蛋、信标、潮涌核心、头颅和潜影盒总是会因为被爆炸破坏而掉落,他们并不受minecraft:survives_explosion掉落条件的影响。

Java版中,存在多个游戏规则控制不同爆炸是否会导致方块有概率不掉落,即游戏规则为false时,游戏会将对应爆炸情况下的1/P重设为1。

blockExplosionDropDecay控制由方块引发的爆炸,默认为true。

mobExplosionDropDecay控制由生物引发的爆炸,默认为true。

tntExplosionDropDecay控制由TNT和TNT矿车引发的爆炸,默认为false,所以由TNT和TNT矿车炸毁的方块总是会掉落。

1.6 当爆炸遇上墙

理论上,墙的碰撞箱对于玩家和生物是1.5格高,但在其他情况下均被视为1格高。墙的碰撞箱宽度也小于1格。但就笔者的实测经验来看,在较早期的MC版本中,墙的碰撞箱高度对爆炸射线来说仍然被视为1.5格高,碰撞箱宽度亦被视作1格计算。典型的例子是在墙边发生爆炸时与在碰撞箱正常的固体方块边发生爆炸时,相差甚远的爆炸结果。

而新版修复这一现象后,出现了另一种情况。

若TNT分别在3×3的圆石和圆石墙上引爆时

造成这一情况的缘故是因为TNT被激活时,在TNT方块的中心(+0.5,+0.5,+0.5)生成了一个被激活的TNT实体。而墙的碰撞箱高度墙的碰撞箱对于被激活的TNT仍然是1.5格高,因此被激活的TNT卡入了墙中爆炸。

2. 爆炸对实体的效果

爆炸对实体施加爆炸伤害,根据实体距离爆心的距离不定,实体有可能会被施加与爆炸方向相反的动量,并导致实体受伤且被推开。

要注意的是,由于爆炸对实体和对方块产生的效果分开计算,爆炸对实体造成伤害的“伤害半径(本文中称其为damage_range)”和爆炸对方块造成影响的“爆炸半径(本文中称其为explosion_range)”是完全不同的两个变量。同时爆炸对实体产生的效果亦无法被方块完全阻挡。

2.1 爆炸接触率(explosion exposure)

爆炸接触率在MC中被用于形容一个实体的碰撞箱对爆炸的接受程度,这与所受爆炸伤害的比例与冲击力相关。

MC中的实体均具有立方体外形的碰撞箱(AABB盒,Axis-aligned minimum Bounding Box),这使得使用一套统一的算法处理爆炸接触率成为可能。将碰撞箱中心处设为三维空间坐标的原点,同时将碰撞箱的宽度、高度、深度分别乘以2后再加1。碰撞险被划分为等同于此三值的不等距网络。对应的转换如下(此处n为划分次数):

边界箱被不等距的分成网格。爆炸中心向每个网格交点出射一条射线,若该射线路径上无固体方块阻挡,则视作有效射线。若被固体方块所阻挡,则射线中断。

爆炸接触率即为未被固体方块阻挡的射线占全部射线的百分比,下文中以α表示爆炸接触率:

这一算法存在一定的采样误差,导致爆炸接触率在各个方向上的大小不一致。典型的例子是,一个TNT大炮向西或南时的射程大于向其他方向的射程。这是由于起始的划分采样点位于碰撞箱的下东北角处,向上西南角处延伸,并因为取整的缘故在上西南方向可能留下一定偏移。

图例出自Minecraft Wiki

因此上西南三个方向的接触率总是偏大的。

2.2 爆炸伤害

伤害半径的计算方式为:

如果一个实体的碰撞箱位于伤害半径的单位内,不论爆炸接触率的大小。若非和平模式,总是至少会受1点伤害。这使得爆炸往往可以隔着墙破坏画和物品展示框,甚至在极端情况下隔墙杀死玩家。

以d表示实体到爆炸中心的距离,m表示玩家所穿戴盔甲的最大爆炸保护魔咒等级,则定义爆炸冲击力F如下:

而对实体造成的伤害为:

其中M的和不会超过20,若大于20将被视作20,即爆炸保护最多减少80%的伤害。

2.3 爆炸冲击

成功造成伤害后,爆炸接触率将会被更新:

即意味着每级爆炸保护能够抵抗15%的爆炸冲击。若拥有7级以上的爆炸保护,实体便不会因爆炸而被施加动量。

一般来说,实体会根据被更新后的爆炸接触率推动,方向为自爆炸中心到实体眼睛位置的射线延伸方向,若被推动者是玩家,即玩家屏幕的正中心点处。推动方式为将实体的速度加以α’。因爆炸对实体造成的冲击效果并非击退,速度在爆炸过程被直接添加而不是由伤害造成击退,因此这种冲击不受击退抗性和伤害免疫影响,但会被爆炸保护降低。

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2023-2024 TWFish
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信