最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

pvzclass是如何實(shí)現(xiàn)的?pvzclass源代碼初步分析(11)events 組件 下

2021-11-08 18:01 作者:__W1thoutD0ubt  | 我要投稿

上篇說(shuō)到,events 組件擴(kuò)充了 pvzclass 的判定功能,方便不擅長(zhǎng)高級(jí)代碼的創(chuàng)作者實(shí)現(xiàn)復(fù)雜功能。對(duì)于一些創(chuàng)作者而言,events 組件無(wú)疑是一件利器。

然而,events 組件也受人詬病,被批“性能還是欠佳,還有其他潛在的問題”,甚至有人想要“把pvzclass?events組件的代碼重寫一遍”。

為何 events 組件會(huì)受到如此批判?本篇會(huì)給出一份解答。

強(qiáng)烈建議閱讀本文前先閱讀上一篇專欄。

注:本文以2021.10.8更新的版本為準(zhǔn)。

Events?組件運(yùn)行機(jī)制

這是上篇遺漏的內(nèi)容。本篇補(bǔ)上。

Events 組件的運(yùn)行機(jī)制其實(shí)很簡(jiǎn)單,可以概括為三步:

  1. 從 PVZ 本體抓取數(shù)據(jù);

  2. 比對(duì)數(shù)據(jù),判定事件是否發(fā)生;

  3. 將注冊(cè)于對(duì)應(yīng)事件類型的所有函數(shù)依次觸發(fā)。

上述過程由 EventHandler 一手包辦。

上篇提到的 events(.h/.cpp) 實(shí)現(xiàn)了第三步內(nèi)容,而 LevelEvent.cpp 和下文將要分析的剩余三份源代碼則完成了前兩步。

雖然看上去一切都很好,但是別忘了,EventHandler 的原理是高頻比較內(nèi)存變化。如果注冊(cè)的函數(shù)效率不高,就會(huì)拖累 EventHandler ,使其有誤判的風(fēng)險(xiǎn)。

Events 組件的代碼編寫,更導(dǎo)致它的三大缺點(diǎn):判定條件不嚴(yán)謹(jǐn)、判定算法不高效、對(duì)不了解底層的創(chuàng)作者不友好。

注:最近有部分更新優(yōu)化了判定算法,“不高效”的問題相對(duì)沒那么嚴(yán)重了,但這個(gè)算法缺乏能驗(yàn)證其效力的應(yīng)用程序,未必可靠。

下面就上述瑕疵,對(duì)剩下的三份源代碼文件一一分析。

ProjectileEvent.cpp

顧名思義,這個(gè)文件包含的代碼都與子彈有關(guān)。

開頭部分是文件引用,以及為 vector 特制的 GetAllProjectiles() 。

需要注意的是,GetAllProjectiles() 生成的動(dòng)態(tài)數(shù)組中,子彈按照在 PVZ 中的序號(hào)從小到大排列,而且這個(gè)序號(hào)正常情況下不會(huì)變化。

這種有序性對(duì)于寫程序有一定的幫助作用。

剩下的就是 UpdateProjectiles(),用于收集子彈數(shù)據(jù),并判定是否有事件發(fā)生的函數(shù)。

首先,EventHandler 會(huì)考慮是否重置變量(這些變量用于防止重復(fù)觸發(fā)事件):

清空的條件僅僅是 Address 為空,這意味著重開可能會(huì)導(dǎo)致誤判,沒能重置臨時(shí)變量。

實(shí)際上關(guān)卡重開長(zhǎng)期以來(lái)都是 Events 組件潛在的漏洞,但是修復(fù)進(jìn)展不大,僅僅是有一個(gè)新的重開事件而已。

然后,EventHandler 獲取一份新的子彈名單。舊名單的子彈數(shù)目記為 lastn, 新名單的子彈數(shù)目記為 nown 。

接下來(lái),EventHandler 判定“子彈發(fā)射”和“子彈生成”兩種事件是否發(fā)生。

判定事件是否發(fā)生的代碼

這一部分代碼確認(rèn)新名單上哪些子彈是舊名單上沒有的。如果新名單上有部分子彈是舊名單上沒有的,那么 EventHandler 就認(rèn)為這是新生成的子彈,并確認(rèn)應(yīng)該觸發(fā)哪個(gè)事件。

只要比對(duì)頻率夠高,就能保證子彈的產(chǎn)生一定會(huì)被某次名單比對(duì)察覺。

在舊版本的 pvzclass 中,Events 組件采取的枚舉方式,是分別順序枚舉子彈 A 和子彈 B ,判斷兩者是否相同。

因?yàn)槊麊蔚挠行蛐裕@種枚舉方法的時(shí)間復(fù)雜度是 O(n2) 的,而且一定會(huì)跑滿。

目前版本已經(jīng)優(yōu)化了這一枚舉方法,原理也很簡(jiǎn)單。

根據(jù) GetAllProjectiles() 的代碼,我們可以確定子彈的序號(hào)和子彈的基址存在線性關(guān)系,而且對(duì)于同一關(guān)卡這一線性關(guān)系是穩(wěn)定的,因此基址可以作為一個(gè)判據(jù)(當(dāng)然,比對(duì)頻率得足夠高)。

如果在某次比對(duì)中,子彈 B 的基址大于子彈 A 的基址,則序號(hào)比 B 大的所有子彈,其基址必然也大于 A 的。那么序號(hào)比 B 大的所有子彈便沒有與 A 比較基址的必要,可以略去。

因此,每次從舊名單中枚舉子彈(和新名單的子彈配對(duì))時(shí),只需枚舉到不小于當(dāng)前子彈基址的子彈就可以了。

而名單本就是有序的,連排序都不用,直接按順序枚舉就行,時(shí)間復(fù)雜度降至 O(n)。

上面的圖片展示的,就是這一算法的具體實(shí)現(xiàn)。

以下是確認(rèn)發(fā)生的事件是“子彈發(fā)射”(源自某一植物)還是“子彈生成”(不源于某一植物,其他方式生成)

判定事件發(fā)生類型的代碼

說(shuō)白了,就是看看場(chǎng)上哪個(gè)植物離子彈最近(根據(jù)曼哈頓距離判定),誰(shuí)最近就是誰(shuí)發(fā)射的。

如果場(chǎng)上沒有植物,說(shuō)明這個(gè)子彈是生成的。

原版中確實(shí)可能發(fā)生這種情況,在無(wú)植物時(shí)豌豆僵尸發(fā)射僵尸豌豆就是一例。

但如果場(chǎng)上有植物,這顆僵尸豌豆可能會(huì)被誤認(rèn)為是植物發(fā)射的。

除此之外,修改器設(shè)計(jì)的新效果也可能會(huì)導(dǎo)致誤判。

這就是我前面所說(shuō)“判定條件不嚴(yán)謹(jǐn)”的體現(xiàn)。

再然后,EventHandler 判定“子彈擊中僵尸”是否發(fā)生。

這一部分獲取了一張新的子彈名單。不同于前面名單的是,這張名單可能包括實(shí)際上已經(jīng)消失的子彈。

判定異同

這一部分的原理很簡(jiǎn)單,如果子彈還在場(chǎng),就將對(duì)應(yīng)的 lastDead 重置為 false。

如果子彈不在場(chǎng),就將離它最近(按曼哈頓距離計(jì))的僵尸視為它擊中的單位,并且將 lastDead 設(shè)為 true,防止重復(fù)觸發(fā)。

當(dāng)然,子彈真的擊中僵尸時(shí),這一判定是可靠的。

但是如果子彈只是飛出了屏幕呢(同樣消失,但不是通過擊中僵尸消失的)?這時(shí)明明沒擊中僵尸的子彈卻還是會(huì)觸發(fā)這一事件,只要場(chǎng)上還有僵尸。

由此可見 Events 組件的判定有多么不嚴(yán)格。

最后,EventHandler 判定“子彈消失”是否發(fā)生。

因?yàn)樵砗芎?jiǎn)單(就是比對(duì)名單),而且優(yōu)化在前面也講過,這里就不再詳細(xì)解讀了。

綜上所述,ProjectileEvent.cpp 在判定規(guī)則方面存在較大問題。創(chuàng)作者在相關(guān)事件下注冊(cè)函數(shù)時(shí)應(yīng)當(dāng)尤其注意。

(或者直接去 Github 上提交更改)

ZombieEvent.cpp

這份文件包含的代碼與僵尸有關(guān)。

開頭和重置臨時(shí)變量的代碼與 ProjectileEvent.cpp 相似,這里略去。

與 GetAllProjectiles() 相同,GetAllZombies() 生成的僵尸名單也具有有序性。

ZombieEvent.cpp 中使用的防重復(fù)變量同樣使用 std::map<int, bool> 。

在 UpdateZombies() 中,EventHandler 依次進(jìn)行如下操作(除重置變量):

首先判定“僵尸被擊殺”是否發(fā)生。代碼如下:

因?yàn)榕卸l件太長(zhǎng),這里不全截下來(lái)

EventHandler 獲取一張新的僵尸名單(包括實(shí)際上已經(jīng)不在場(chǎng)的僵尸),通過僵尸的狀態(tài)確認(rèn)僵尸是否是因?yàn)楸粨魵⒍辉趫?chǎng)上。

這個(gè)過程中 EventHanler 通過 isPostedZombieDead 防止事件重復(fù)觸發(fā)。

接著,EventHandler 獲取一張新的僵尸名單,新名單上的僵尸數(shù)目記為 nown, 舊名單上僵尸的數(shù)目為 lastn。

然后,EventHandler 分析新名單上的僵尸,確認(rèn)剩余所有事件(除了“僵尸被擊殺”和“僵尸消失”)是否發(fā)生。

判定“僵尸生成”是否發(fā)生的算法過去是分別枚舉新舊名單上的僵尸,現(xiàn)在也使用了單調(diào)性優(yōu)化,時(shí)間復(fù)雜度降至 O(n)。

判定其余事件是否發(fā)生的原理和判定“僵尸被擊殺”事件是否發(fā)生的原理類似,比對(duì)條件是否成立,成立就視為事件發(fā)生,用臨時(shí)變量防止事件重復(fù)觸發(fā)。

這里對(duì)應(yīng)的代碼在文件第 59 行至第 94 行,不再展示了。

最后判定“僵尸消失”是否發(fā)生。這里的“消失”包括所有消失的情況,不只是前面提到的“被擊殺”,還包括其他移除情形(比如大嘴花的秒殺)。

因?yàn)樵砗芎?jiǎn)單(比對(duì)名單),這里略去。

ZombieEvent.cpp 的漏洞相對(duì)而言少一些。

PlantEvent.cpp

這份文件包含的代碼與植物相關(guān)。

開頭和重置臨時(shí)變量的代碼與 ProjectileEvent.cpp 相似,這里略去。

與 GetAllProjectiles() 相同,GetAllPlants() 生成的植物名單也具有有序性。

PlantEvent.cpp 中使用的防重復(fù)變量使用 std::map,另外有一些變量記錄舊名單上植物的屬性:

在 UpdatePlants() 中,EventHandler 依次進(jìn)行如下操作(除重置變量):

首先獲取一份新的植物名單,記為 plants。新名單上的植物數(shù)目記為 list_nun, 舊名單上植物的數(shù)目為 plant_num。

接著,EventHandler 判定“植物受傷”是否發(fā)生,順便刷新部分記錄植物屬性的變量,把“土豆雷出土”是否發(fā)生也判定一下。代碼如下圖:

“土豆雷出土”判定條件未截全

從“植物受傷”的判定條件中可以看出,這個(gè)事件實(shí)際上更像是“植物被近身攻擊”。因?yàn)樗鼘?duì)僵尸的判定范圍只有 100。

這個(gè)范圍對(duì)于啃咬和碾壓肯定是夠了,但是僵尸豌豆、籃球?qū)χ参锏膫臀幢啬軠?zhǔn)確記錄。

而且這個(gè)判定沒有篩去氣球僵尸,可能還會(huì)發(fā)生誤判。

這還只是判定不嚴(yán)格的潛在問題。

實(shí)際上,如果你眼睛夠尖,就能發(fā)現(xiàn)生命值判定和生命值記錄竟然使用不同的下標(biāo)!

這個(gè)漏洞導(dǎo)致的失配可能造成很嚴(yán)重的誤判,無(wú)論是誤判發(fā)生還是誤判不發(fā)生。

觸發(fā)這個(gè)漏洞并不困難。實(shí)際上,在 2021.10.8 更新?lián)Q掉示例代碼前,舊的示例代碼就可以展現(xiàn)這一判定漏洞。

因此,EventPlantDamage 實(shí)際上完全是不可靠的,直到有誰(shuí)修復(fù)上述問題為止。

剩下的代碼就沒啥問題了。

這一部分過后,EventHandler 判定“植物種下”和“植物升級(jí)”是否發(fā)生。

代碼開頭設(shè)了一個(gè) pardon。

通過觀察,可以看出 pardon 記錄了紫卡升級(jí)植物的位置。它的作用會(huì)在后面的部分展現(xiàn)。

當(dāng)然,Events 組件對(duì)紫卡的定義僅限于圖鑒最后一排的植物,忽略了創(chuàng)作者人為修改紫卡的可能。

同樣可以看出,紫卡植物的種植一定會(huì)觸發(fā)"植物升級(jí)"。

接下來(lái)判定的是“植物被擊殺”事件。

與前面判定“僵尸被擊殺”是類似的,但不同的是“植物被擊殺”事件可以記錄一切被移除的情況,而且事件會(huì)記錄植物被移除時(shí)的位置(包括坐標(biāo)和行列)。

最終判定“植物消失”事件。(總感覺和上一個(gè)事件有些重復(fù)?)

這里除了常規(guī)的消失判定外,還會(huì)參考之前 pardon 記錄的位置。

如果植物在 pardon 記錄過的位置上,就不會(huì)觸發(fā)消失判定。這篩掉了因紫卡植物種植而消失的植物。

還是那句話,只要比對(duì)夠快,就不會(huì)出 bug。

雖然 Events 組件有這樣那樣的瑕疵,但不可否認(rèn)的是,它確實(shí)為創(chuàng)作者提供了支持。

希望各位創(chuàng)作者在利用 Events 組件的過程中對(duì)組件的潛在問題加以注意,慎重運(yùn)用它的力量。

至此,pvzclass 的絕大部分內(nèi)容都已分析完畢。

《pvzclass是如何實(shí)現(xiàn)的?pvzclass源代碼初步分析》系列也將告一段落。

如果有新的內(nèi)容加入到 pvzclass 且能保證穩(wěn)定,本系列有可能繼續(xù)更新。

感謝你看到了最后。

pvzclass是如何實(shí)現(xiàn)的?pvzclass源代碼初步分析(11)events 組件 下的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
运城市| 青田县| 阜阳市| 南宁市| 钟山县| 那坡县| 婺源县| 天全县| 怀柔区| 富民县| 沙坪坝区| 上虞市| 乡城县| 常德市| 绵阳市| 盐津县| 沾化县| 瑞金市| 游戏| 河西区| 大同县| 静安区| 西宁市| 新河县| 波密县| 饶阳县| 龙里县| 昂仁县| 普格县| 禄丰县| 祁阳县| 波密县| 伊金霍洛旗| 罗江县| 南康市| 温泉县| 天水市| 德化县| 文山县| 临桂县| 古浪县|