【我?guī)旺椊切辀ug】可能是方舟開服以來最大的惡性bug:無限持續(xù)的鈣質(zhì)化見過嗎?
前言和bug簡介
這個(gè)系列內(nèi)容大概是科學(xué)分析一些游戲bug或"bug"的原理并給出可能的解決方案
往期內(nèi)容歡迎查看本人專欄
本期的內(nèi)容只有一個(gè),但非常重要
如果不及時(shí)修復(fù)可能會(huì)造成極其嚴(yán)重的后果
那就是:在一定條件下,光環(huán)buff可以無視范圍無限持續(xù)
最典型的例子就是無限時(shí)長的鈣質(zhì)化
這個(gè)bug最早可以找到的記錄是是B站UP主是只熊_JP的視頻
隨后由B站UP主TouringFroog在此視頻中對(duì)觸發(fā)條件進(jìn)行了更進(jìn)一步的研究
這個(gè)bug的應(yīng)用可以參考B站UP主泥萌都是托的視頻
大致觸發(fā)條件是:
對(duì)處于無敵狀態(tài)的目標(biāo)施加光環(huán)buff,隨后使目標(biāo)離開光環(huán)范圍。目標(biāo)無敵結(jié)束后會(huì)在范圍外受到光環(huán)效果。使敵人再次進(jìn)入光環(huán)范圍,光環(huán)即可無限且無視范圍地持續(xù)。


光環(huán)能力的簡介
光環(huán)能力(這里不包含全場(chǎng)范圍的光環(huán))在程序中為AuraAbility類
效果大致為對(duì)進(jìn)入一定范圍內(nèi)所有符合一定條件的目標(biāo)無差別地施加buff
由于能力類型為被動(dòng),因此不能設(shè)定開啟和關(guān)閉,只能設(shè)置attach(添加)和detach(撤銷)
使用光環(huán)的技能有:
拜松2,錫蘭2,夜鶯3,深海色2,塞雷婭3,鈴蘭3,年3,閃靈3,地靈2,初雪12,空12,月禾12,梅爾水獺1,巫戀娃娃2
使用光環(huán)的天賦有:
溫蒂水炮2,稀音小車1,鈴蘭2,斷崖1,萊恩哈特1,月禾1,W2,巫戀1,莫斯提馬2,拜松1,詩懷雅1,銀灰2,夜鶯1,閃靈1,推進(jìn)之王2,初雪1,可頌1,艾斯黛爾1
其它的光環(huán)有:
空的特性,反隱器,吹風(fēng)機(jī),無人機(jī)干擾器,補(bǔ)給站,留聲機(jī)充能,御4,寒霜,技術(shù)偵察兵
這類光環(huán)技能添加的buff其本身全部被設(shè)定為了無限的持續(xù)時(shí)間

由于持續(xù)時(shí)間為無限,因此buff的結(jié)束需要通過光環(huán)能力本身進(jìn)行控制
例如絕大部分光環(huán)buff,會(huì)在目標(biāo)退出光環(huán)范圍或能力被撤銷時(shí)被移除
那么,如果光環(huán)能力對(duì)buff的撤銷機(jī)制出現(xiàn)了問題,則buff持續(xù)無限時(shí)長是理所當(dāng)然的結(jié)果

光環(huán)能力的大致邏輯
首先,光環(huán)能力擁有生效范圍,它需要檢測(cè)進(jìn)入范圍的目標(biāo)和離開范圍的目標(biāo)
這個(gè)功能依賴于unity引擎自帶的碰撞檢測(cè)完成
實(shí)體和光環(huán)能力都擁有碰撞箱
兩個(gè)碰撞箱撞在一起時(shí),unity引擎觸發(fā)一個(gè)事件,這個(gè)時(shí)候光環(huán)能力會(huì)嘗試給這個(gè)碰撞箱的擁有者加上光環(huán)buff
兩個(gè)碰撞箱分離時(shí),unity引擎也觸發(fā)一個(gè)事件,這個(gè)時(shí)候光環(huán)能力會(huì)嘗試將這個(gè)碰撞箱的擁有者身上的光環(huán)buff結(jié)束
其次,光環(huán)能力擁有生效條件,它只能對(duì)符合條件的目標(biāo)添加buff
這個(gè)功能由TargetValidator實(shí)現(xiàn)
TargetValidator大概就是用于篩選給定目標(biāo)的條件是否符合設(shè)定的條件
然后,由于光環(huán)能力需要在能力被撤銷和目標(biāo)脫離范圍時(shí)撤銷指定實(shí)體身上的指定buff
因此,光環(huán)能力需要一個(gè)字典(鍵值對(duì)),用于登記被光環(huán)影響的實(shí)體,和實(shí)體身上光環(huán)buff的uid(其中鍵為實(shí)體指針,值為一個(gè)由主要由buff uid構(gòu)成的類)


這樣才能最大限度地確保撤銷buff機(jī)制的執(zhí)行
這個(gè)字典在程序中被命名為m_targetMap
最后,由于之前版本光環(huán)不會(huì)對(duì)隱身后進(jìn)入范圍的目標(biāo)生效(因?yàn)橹鞍姹竟猸h(huán)只在目標(biāo)進(jìn)入和離開時(shí)判定)
因此,光環(huán)需要一定頻率的更新
這樣才能保證隱身目標(biāo)進(jìn)入光環(huán)后解除隱身也能受到光環(huán)效果
根據(jù)程序設(shè)定,這個(gè)更新頻率是10ticks(即10幀)

程序分析:光環(huán)能力的詳細(xì)邏輯
目標(biāo)在進(jìn)入光環(huán)時(shí)會(huì)觸發(fā)AuraAbility類下的OnTriggerEnter2D函數(shù)
這個(gè)函數(shù)會(huì)獲取觸發(fā)碰撞的碰撞箱所屬實(shí)體,將此實(shí)體作為參數(shù)之一調(diào)用AuraAbility.TargetEnterExitHandler類下的OnTargetEnter函數(shù)
OnTargetEnter函數(shù)會(huì)觸發(fā)一堆事件(我也不知道具體干嘛的),然后以實(shí)體為參數(shù)喚起AuraAbility.TargetEnterExitHandler類下的OnRealEnter函數(shù)
OnRealEnter函數(shù)會(huì)以目標(biāo)實(shí)體為參數(shù)喚起AuraAbility類下的_DoTargetCheckAndEnter函數(shù),并判定其返回值,如果返回值為0(即添加失敗)則將目標(biāo)實(shí)體添加至名為m_invalidTarget的列表中
_DoTargetCheckAndEnter會(huì)調(diào)用VerifyTarget函數(shù)來判斷目標(biāo)是否符合TargetValidator的設(shè)定條件,符合時(shí)調(diào)用DealTargetTouched函數(shù)給目標(biāo)添加buff(添加成功時(shí)將目標(biāo)添加至m_targetMap中)并返回1,不符合時(shí)返回0
光環(huán)能力每幀會(huì)執(zhí)行OnTick函數(shù),該函數(shù)在attach狀態(tài)下會(huì)調(diào)用AuraAbility.TargetEnterExitHandler類下的OnTick函數(shù)
該函數(shù)每10幀會(huì)調(diào)用一次_UpdateInvalidTarget函數(shù)
_UpdateInvalidTarget函數(shù)會(huì)遍歷m_invalidTarget列表,以其中每個(gè)實(shí)體為參數(shù)喚起AuraAbility類下的_DoTargetCheckAndEnter函數(shù),并判定其返回值,如果返回值為1(即添加成功)則將該實(shí)體從列表中移除
目標(biāo)在離開光環(huán)時(shí)會(huì)觸發(fā)AuraAbility類下的OnTriggerExit2D函數(shù)
這個(gè)函數(shù)會(huì)獲取觸發(fā)碰撞的碰撞箱所屬實(shí)體,將此實(shí)體作為參數(shù)之一調(diào)用AuraAbility.TargetEnterExitHandler類下的OnTargetExit函數(shù)
OnTargetExit函數(shù)會(huì)觸發(fā)一堆事件(我也不知道具體干嘛的),然后以實(shí)體為參數(shù)喚起AuraAbility.TargetEnterExitHandler類下的OnRealExit函數(shù)
OnRealExit函數(shù)會(huì)以目標(biāo)實(shí)體為參數(shù)喚起AuraAbility類下的_DoTargetExit函數(shù)
_DoTargetExit會(huì)嘗試獲取m_targetMap中鍵為目標(biāo)實(shí)體項(xiàng)的值(其值為buff的uid),獲取成功時(shí)以此鍵值對(duì)為2個(gè)參數(shù)調(diào)用DealTargetLeft函數(shù),隨后從m_targetMap中移除此鍵值對(duì)
DealTargetLeft函數(shù)在removeBuffWhenTargetLeave設(shè)定為True時(shí)會(huì)移除目標(biāo)身上的指定buff
光環(huán)能力會(huì)被AuraAbility類下的DoDetach函數(shù)所撤銷
該函數(shù)執(zhí)行時(shí),(如果removeBuffWhenAbilityDetached設(shè)定為True,會(huì)遍歷整個(gè)m_targetMap并移除對(duì)應(yīng)實(shí)體上的對(duì)應(yīng)buff),隨后清空m_targetMap

bug的成因和分析
如果你仔細(xì)地閱讀了上面全部內(nèi)容,可以先試著按bug的觸發(fā)條件推一遍整個(gè)光環(huán)能力的邏輯,這樣應(yīng)該就差不多能明白問題出在哪了
首先,無敵單位進(jìn)入光環(huán)范圍時(shí),由于不符合大部分光環(huán)的TargetValidator的篩選條件,因此_DoTargetCheckAndEnter函數(shù)會(huì)返回0,單位會(huì)被添加至m_invalidTarget列表中
接著,該單位離開光環(huán)范圍時(shí),由于之前未被添加至m_targetMap中,因此無事發(fā)生
然后,精髓的來了。當(dāng)無敵解除后,光環(huán)能力執(zhí)行OnTick函數(shù)時(shí),由于目標(biāo)仍處在m_invalidTarget列表中,因此會(huì)被_DoTargetCheckAndEnter函數(shù)檢查條件。由于這個(gè)時(shí)候是符合條件的,因此會(huì)被添加buff,并被登記在m_targetMap字典中(即使處在范圍外)。隨后目標(biāo)從m_invalidTarget列表中移除
隨后,當(dāng)目標(biāo)再次進(jìn)入光環(huán)范圍內(nèi),會(huì)再次觸發(fā)OnTriggerEnter2D函數(shù)并被添加buff(但是因?yàn)橥鸼uff的默認(rèn)疊加策略buff只能表現(xiàn)出一個(gè)),并登記在m_targetMap字典中。但是注意,m_targetMap這玩意是個(gè)字典!因此在登記的時(shí)候,鍵還是不變的實(shí)體指針,值已經(jīng)被替換成了新buff的uid。而舊buff的uid,就這么被弄丟了...(有點(diǎn)類似內(nèi)存泄露)
最后,不管光環(huán)能力通過什么手段試圖結(jié)束buff,被結(jié)束的都只是新buff。而舊buff,由于并不處于m_targetMap字典上,已經(jīng)脫離了光環(huán)能力的控制...
從以上過程分析來看,該bug觸發(fā)范圍比測(cè)試中更廣
不止適用于無敵單位,隱身單位同樣適用
由于空這個(gè)能給敵方加無敵的單位的存在,幾乎所有單位都能觸發(fā)這個(gè)bug
最后是修復(fù)方案:
方案1:在OnRealExit函數(shù)中添加從m_invalidTarget列表中移除指定實(shí)體的代碼。這樣一來其它問題全部迎刃而解。如果怕占用內(nèi)存過多但話可以把_DoTargetExit函數(shù)改成Bool返回值,用于判斷嘗試獲取m_targetMap中鍵為指定實(shí)體項(xiàng)的值是否成功,只有不成功時(shí)才會(huì)執(zhí)行OnRealExit函數(shù)中從m_invalidTarget列表中移除指定實(shí)體的代碼。
方案2:加一個(gè)變量判斷是否已經(jīng)離開光環(huán)范圍且未進(jìn)入。如果已經(jīng)離開范圍則_DoTargetCheckAndEnter函數(shù)返回0。
最后的最后:
這個(gè)bug一定要及時(shí)修復(fù)
一旦這種能夠影響大環(huán)境的bug被當(dāng)成了常態(tài),后果不堪設(shè)想
本文原載于NGA:https://ngabbs.com/read.php?tid=22812882
作者為本人
此專欄以CC BY-NC-SA 4.0協(xié)議發(fā)布