【我帮鹰角修bug】可能是方舟开服以来最大的恶性bug:无限持续的钙质化见过吗?
前言和bug简介
这个系列内容大概是科学分析一些游戏bug或"bug"的原理并给出可能的解决方案
往期内容欢迎查看本人专栏
本期的内容只有一个,但非常重要
如果不及时修复可能会造成极其严重的后果
那就是:在一定条件下,光环buff可以无视范围无限持续
最典型的例子就是无限时长的钙质化
这个bug最早可以找到的记录是是B站UP主是只熊_JP的视频
随后由B站UP主TouringFroog在此视频中对触发条件进行了更进一步的研究
大致触发条件是:
对处于无敌状态的目标施加光环buff,随后使目标离开光环范围。目标无敌结束后会在范围外受到光环效果。使敌人再次进入光环范围,光环即可无限且无视范围地持续。


光环能力的简介
光环能力(这里不包含全场范围的光环)在程序中为AuraAbility类
效果大致为对进入一定范围内所有符合一定条件的目标无差别地施加buff
由于能力类型为被动,因此不能设定开启和关闭,只能设置attach(添加)和detach(撤销)
使用光环的技能有:
拜松2,锡兰2,夜莺3,深海色2,塞雷娅3,铃兰3,年3,闪灵3,地灵2,初雪12,空12,月禾12,梅尔水獭1,巫恋娃娃2
使用光环的天赋有:
温蒂水炮2,稀音小车1,铃兰2,断崖1,莱恩哈特1,月禾1,W2,巫恋1,莫斯提马2,拜松1,诗怀雅1,银灰2,夜莺1,闪灵1,推进之王2,初雪1,可颂1,艾斯黛尔1
其它的光环有:
空的特性,反隐器,吹风机,无人机干扰器,补给站,留声机充能,御4,寒霜,技术侦察兵
这类光环技能添加的buff其本身全部被设定为了无限的持续时间

由于持续时间为无限,因此buff的结束需要通过光环能力本身进行控制
例如绝大部分光环buff,会在目标退出光环范围或能力被撤销时被移除
那么,如果光环能力对buff的撤销机制出现了问题,则buff持续无限时长是理所当然的结果

光环能力的大致逻辑
首先,光环能力拥有生效范围,它需要检测进入范围的目标和离开范围的目标
这个功能依赖于unity引擎自带的碰撞检测完成
实体和光环能力都拥有碰撞箱
两个碰撞箱撞在一起时,unity引擎触发一个事件,这个时候光环能力会尝试给这个碰撞箱的拥有者加上光环buff
两个碰撞箱分离时,unity引擎也触发一个事件,这个时候光环能力会尝试将这个碰撞箱的拥有者身上的光环buff结束
其次,光环能力拥有生效条件,它只能对符合条件的目标添加buff
这个功能由TargetValidator实现
TargetValidator大概就是用于筛选给定目标的条件是否符合设定的条件
然后,由于光环能力需要在能力被撤销和目标脱离范围时撤销指定实体身上的指定buff
因此,光环能力需要一个字典(键值对),用于登记被光环影响的实体,和实体身上光环buff的uid(其中键为实体指针,值为一个由主要由buff uid构成的类)


这样才能最大限度地确保撤销buff机制的执行
这个字典在程序中被命名为m_targetMap
最后,由于之前版本光环不会对隐身后进入范围的目标生效(因为之前版本光环只在目标进入和离开时判定)
因此,光环需要一定频率的更新
这样才能保证隐身目标进入光环后解除隐身也能受到光环效果
根据程序设定,这个更新频率是10ticks(即10帧)

程序分析:光环能力的详细逻辑
目标在进入光环时会触发AuraAbility类下的OnTriggerEnter2D函数
这个函数会获取触发碰撞的碰撞箱所属实体,将此实体作为参数之一调用AuraAbility.TargetEnterExitHandler类下的OnTargetEnter函数
OnTargetEnter函数会触发一堆事件(我也不知道具体干嘛的),然后以实体为参数唤起AuraAbility.TargetEnterExitHandler类下的OnRealEnter函数
OnRealEnter函数会以目标实体为参数唤起AuraAbility类下的_DoTargetCheckAndEnter函数,并判定其返回值,如果返回值为0(即添加失败)则将目标实体添加至名为m_invalidTarget的列表中
_DoTargetCheckAndEnter会调用VerifyTarget函数来判断目标是否符合TargetValidator的设定条件,符合时调用DealTargetTouched函数给目标添加buff(添加成功时将目标添加至m_targetMap中)并返回1,不符合时返回0
光环能力每帧会执行OnTick函数,该函数在attach状态下会调用AuraAbility.TargetEnterExitHandler类下的OnTick函数
该函数每10帧会调用一次_UpdateInvalidTarget函数
_UpdateInvalidTarget函数会遍历m_invalidTarget列表,以其中每个实体为参数唤起AuraAbility类下的_DoTargetCheckAndEnter函数,并判定其返回值,如果返回值为1(即添加成功)则将该实体从列表中移除
目标在离开光环时会触发AuraAbility类下的OnTriggerExit2D函数
这个函数会获取触发碰撞的碰撞箱所属实体,将此实体作为参数之一调用AuraAbility.TargetEnterExitHandler类下的OnTargetExit函数
OnTargetExit函数会触发一堆事件(我也不知道具体干嘛的),然后以实体为参数唤起AuraAbility.TargetEnterExitHandler类下的OnRealExit函数
OnRealExit函数会以目标实体为参数唤起AuraAbility类下的_DoTargetExit函数
_DoTargetExit会尝试获取m_targetMap中键为目标实体项的值(其值为buff的uid),获取成功时以此键值对为2个参数调用DealTargetLeft函数,随后从m_targetMap中移除此键值对
DealTargetLeft函数在removeBuffWhenTargetLeave设定为True时会移除目标身上的指定buff
光环能力会被AuraAbility类下的DoDetach函数所撤销
该函数执行时,(如果removeBuffWhenAbilityDetached设定为True,会遍历整个m_targetMap并移除对应实体上的对应buff),随后清空m_targetMap

bug的成因和分析
如果你仔细地阅读了上面全部内容,可以先试着按bug的触发条件推一遍整个光环能力的逻辑,这样应该就差不多能明白问题出在哪了
首先,无敌单位进入光环范围时,由于不符合大部分光环的TargetValidator的筛选条件,因此_DoTargetCheckAndEnter函数会返回0,单位会被添加至m_invalidTarget列表中
接着,该单位离开光环范围时,由于之前未被添加至m_targetMap中,因此无事发生
然后,精髓的来了。当无敌解除后,光环能力执行OnTick函数时,由于目标仍处在m_invalidTarget列表中,因此会被_DoTargetCheckAndEnter函数检查条件。由于这个时候是符合条件的,因此会被添加buff,并被登记在m_targetMap字典中(即使处在范围外)。随后目标从m_invalidTarget列表中移除
随后,当目标再次进入光环范围内,会再次触发OnTriggerEnter2D函数并被添加buff(但是因为同名buff的默认叠加策略buff只能表现出一个),并登记在m_targetMap字典中。但是注意,m_targetMap这玩意是个字典!因此在登记的时候,键还是不变的实体指针,值已经被替换成了新buff的uid。而旧buff的uid,就这么被弄丢了...(有点类似内存泄露)
最后,不管光环能力通过什么手段试图结束buff,被结束的都只是新buff。而旧buff,由于并不处于m_targetMap字典上,已经脱离了光环能力的控制...
从以上过程分析来看,该bug触发范围比测试中更广
不止适用于无敌单位,隐身单位同样适用
由于空这个能给敌方加无敌的单位的存在,几乎所有单位都能触发这个bug
最后是修复方案:
方案1:在OnRealExit函数中添加从m_invalidTarget列表中移除指定实体的代码。这样一来其它问题全部迎刃而解。如果怕占用内存过多但话可以把_DoTargetExit函数改成Bool返回值,用于判断尝试获取m_targetMap中键为指定实体项的值是否成功,只有不成功时才会执行OnRealExit函数中从m_invalidTarget列表中移除指定实体的代码。
方案2:加一个变量判断是否已经离开光环范围且未进入。如果已经离开范围则_DoTargetCheckAndEnter函数返回0。
最后的最后:
这个bug一定要及时修复
一旦这种能够影响大环境的bug被当成了常态,后果不堪设想
本文原载于NGA:https://ngabbs.com/read.php?tid=22812882
作者为本人
此专栏以CC BY-NC-SA 4.0协议发布