Minecraft 生物 AI 機(jī)制詳解(二)
在上一篇專欄中我們討論了目標(biāo)系統(tǒng)的原理,在這篇文章中我們會講述另一套 AI 系統(tǒng),也就是記憶行為系統(tǒng)的原理。
一. 生物記憶
既然是記憶行為系統(tǒng),那么就肯定有記憶這個(gè)東西,生物記憶就是用來記錄生物某一項(xiàng)具體信息使用的。例如村民需要記住自己床的位置,否則在睡覺時(shí)就找不到了。
生物記憶分為兩種,長期記憶和短期記憶。長期記憶可以序列化成 NBT 標(biāo)簽,也就是通過 data 命令能獲取的那些記憶,這些記憶在區(qū)塊卸載的時(shí)候也能被保存,區(qū)塊再次加載就能還原回來。但是短期記憶不同,這些記憶不能序列化為 NBT 標(biāo)簽,區(qū)塊卸載后就會立刻丟掉。長期記憶可以在 wiki 找到(https://minecraft.fandom.com/zh/wiki/%E7%94%9F%E7%89%A9%E8%AE%B0%E5%BF%86)。
很多記憶都有“有效期”,過了一段時(shí)間之后記憶會自動忘記。
代碼中獲得和抹除記憶由 Brain 類的 setMemory、setMemoryWithExpiry 和 eraseMemory 負(fù)責(zé)。
二. 行為
與目標(biāo)系統(tǒng)不同,在記憶行為系統(tǒng)中生物的動作由行為定義。行為的基類是 BehaviorControl,它的定義如下:
getStatus 是獲取行為狀態(tài)的方法,行為只有兩種狀態(tài):STOPPED(停止)和 RUNNING(正在運(yùn)行)。tryStart 是嘗試執(zhí)行行為,如果行為成功開始執(zhí)行返回 true。tickOrStop 是每刻執(zhí)行一次行為并決定是否停止。doStop 是立刻停止。
這個(gè)接口一共有 4 個(gè)直接子類,接下來將對這四個(gè)子類進(jìn)行詳細(xì)說明。
1. Behavior
所有使用記憶行為系統(tǒng)的生物的持續(xù)行為都是這個(gè)類的子類,它將 BehaviorControl 中定義的行為生命周期進(jìn)行了進(jìn)一步的細(xì)化。
持續(xù)行為的啟動需要檢查兩個(gè)條件:記憶是否滿足條件、子類定義的額外條件 checkExtraStartConditions 是否滿足。如果這兩個(gè)條件都滿足,持續(xù)行為才能啟動。持續(xù)行為擁有一個(gè)結(jié)束時(shí)間戳,當(dāng)時(shí)間超出這個(gè)時(shí)間戳后行為會自動停止,而這個(gè)持續(xù)時(shí)間由子類定義。如果子類沒有定義,則使用 60 游戲刻,即持續(xù)行為默認(rèn)只會持續(xù) 3 秒。
持續(xù)行為初始化時(shí)會指定需要或不需要什么記憶。記憶具有三種狀態(tài):VALUE_PRESENT(記憶存在)、VALUE_ABSENT(記憶不存在)和 REGISTERED(記憶已注冊)。只有生物的記憶滿足持續(xù)行為定義的每一條記憶限制,這個(gè)持續(xù)行為才能啟動。例如下方持續(xù)行為的初始化代碼:
這個(gè)持續(xù)行為就需要 JOB_SITE 這個(gè)記憶存在,且 LOOK_TARGET 記憶已經(jīng)注冊。如果生物不具有 JOB_SITE 記憶,則持續(xù)行為不能執(zhí)行。而 LOOK_TARGET 的存在與否并不重要,它在生物生成時(shí)就已經(jīng)注冊了,所以條件肯定滿足。
持續(xù)行為可以繼續(xù)的條件是:時(shí)間沒有超過結(jié)束時(shí)間戳且由子類定義的 canStillUse 返回 true。如果條件不滿足則直接 doStop。
2. DoNothing
聽名字就知道這個(gè)類代表什么都不做,不修改記憶也不進(jìn)行任何操作,但這不意味著生物“停止思考”。與持續(xù)行為一樣,它具有結(jié)束時(shí)間戳,具體時(shí)長由初始化時(shí)的參數(shù)決定。
3. GateBehavior
這個(gè)行為可以“挑選”出某些行為執(zhí)行。
初始化的第一個(gè)參數(shù)仍然是記憶條件,第二個(gè)參數(shù)代表這個(gè)行為結(jié)束后要抹除的記憶,第三個(gè)和第四個(gè)參數(shù)代表這個(gè)行為的運(yùn)作模式,最后一個(gè)參數(shù)代表這個(gè)行為可以選擇的行為列表。
行為列表 behaviors 是一個(gè)帶有權(quán)重的列表。列表內(nèi)的元素在被打亂時(shí)可以按照權(quán)重隨機(jī)生成數(shù)字,按照生成的數(shù)字進(jìn)行重新排序得到打亂后的列表,而元素權(quán)重越大元素排在前面的概率也就越大。
orderPolicy 決定行為列表是否要打亂。如果為 ORDERED,則不進(jìn)行打亂,使用初始化時(shí)的列表順序;如果為 SHUFFLED,則進(jìn)行按權(quán)重的打亂。
runningPolicy 決定要啟動執(zhí)行多少行為。如果為 RUN_ONE,則只會執(zhí)行行為列表中第一個(gè)能執(zhí)行的行為;如果為 TRY_ALL,則會嘗試啟動行為列表中的所有行為。
GateBehavior 有一個(gè)子類,RunOne。它默認(rèn)使用 SHUFFLED 和 RUN_ONE 兩個(gè)常量,即打亂并只執(zhí)行一個(gè)。
下面使用豬靈空閑游走時(shí)的 AI 進(jìn)行舉例。
從上面的代碼可以看出,豬靈在空閑游走時(shí)只會選擇一個(gè)行為,其中隨機(jī)游走、跟隨其他豬靈、跟隨手上有喜愛物品的 AI 權(quán)重相同,而停止不動權(quán)重較小。
GateBehavior 的結(jié)束條件是所有被執(zhí)行的行為都結(jié)束,RunOne 的結(jié)束條件也類似。
4. OneShot
這個(gè)行為在啟動后會立刻停止,是用于實(shí)現(xiàn)“觸發(fā)器”的。
這個(gè)類是抽象類,它的唯一實(shí)現(xiàn)在 BehaviorBuilder 里面。
這個(gè)方法傳入的參數(shù)是將 BehaviorBuilder.Instance 實(shí)例轉(zhuǎn)變?yōu)?App 實(shí)例的函數(shù),這個(gè)函數(shù)中需要寫明觸發(fā)器的觸發(fā)條件和執(zhí)行代碼。由于方法較多,且大多具有相似性,我們以例子作為說明,下方的代碼取自 SetWalkTargetFromLookTarget。
首先來說第一層 lambda,參數(shù)是 BehaviorBuilder.Instance。它的 group 方法內(nèi)定義了這個(gè)觸發(fā)器的條件,使用方法 absent、present 和 registered 描述指定記憶的狀態(tài)。group 內(nèi)可以有多個(gè)記憶條件,但是不能超過 datafixerupper 庫定義的最大數(shù)量。apply 或 point 方法內(nèi)是觸發(fā)器的執(zhí)行代碼,這兩個(gè)的不同主要在 apply 是必須定義 group 的,而 point 不需要。
第二層 lambda 的參數(shù)是 memoryAccessor1 和 memoryAccessor2。它們的類型都是 MemoryAccessor,這是一個(gè)用于獲取和修改記憶的轉(zhuǎn)換類。這兩個(gè)對象分別對應(yīng)了 group 內(nèi)兩個(gè)記憶,即第一個(gè)參數(shù)代表了生物的 WALK_TARGET 記憶,第二個(gè)代表 LOOK_TARGET。group 內(nèi)定義的條件與這里的參數(shù)息息相關(guān),記憶條件在這里全部轉(zhuǎn)換為 MemoryAccessor,并且一一對應(yīng)。
最里層的 lambda 就是具體執(zhí)行的代碼,參數(shù)是 ServerLevel 對象、操作的生物和時(shí)間,要求返回一個(gè)布爾值代表觸發(fā)是否成功,決定 OneShot 行為是否真正執(zhí)行成功。
所以上方的代碼可以這么解釋:如果生物沒有行走目標(biāo)但具有看向的目標(biāo),且滿足 entityPredicate 的條件時(shí),生物會把看向的目標(biāo)作為行走的目標(biāo)。
OneShot 還有各種簡單創(chuàng)建方式。
三. 感知器
和目標(biāo)系統(tǒng)一樣,記憶行為系統(tǒng)也有探測其他實(shí)體的類。目標(biāo)系統(tǒng)中是 TargetGoal 負(fù)責(zé)這一工作,而記憶行為系統(tǒng)中是 Sensor 和它的子類完成這些工作,并且它的功能從選擇攻擊對象擴(kuò)展到了選擇生物各種行為的對象。
感知器有兩個(gè)重要的方法:requires 代表了這個(gè)感知器需要什么記憶,這些記憶將在之后的行為調(diào)度器內(nèi)部定義;tick 是感知器每刻執(zhí)行的代碼。
感知器不是每刻都能成功執(zhí)行,而是每 scanRate 刻才能真正執(zhí)行 doTick 一次。scanRate 通常為 20,即 1 秒鐘探測一次。
四. 行為調(diào)度
現(xiàn)在來到了這篇專欄的重頭戲:這些行為是怎么被安排在一起的呢?
首先,我們需要介紹生物的“活動”,代碼里面對應(yīng)了 Activity 這個(gè)類。生物的活動不止一種,比如核心活動、空閑活動等等。在同一時(shí)刻也不一定只有一種活動在進(jìn)行,比如核心活動就可以和空閑活動一起進(jìn)行,但是非核心活動只能有一個(gè)。
每個(gè)生物都有兩種重要的活動:一是核心活動,這些活動會一直保持活躍,代碼里面對應(yīng)的是 coreActivities 和設(shè)置它的 setCoreActivities;另一個(gè)是默認(rèn)活動,如果在之后的計(jì)算過程中生物想要從一種非核心活動轉(zhuǎn)移到另一種非核心活動時(shí)條件不滿足,則會把這個(gè)活動設(shè)置為活躍活動,在默認(rèn)情況下是 IDLE。
添加活動時(shí)需要使用 addActivityAndRemoveMemoriesWhenStopped 和它的簡單版本方法。
其中第一個(gè)參數(shù)就是添加的活動;第二項(xiàng)是活動內(nèi)的行為列表,內(nèi)部是優(yōu)先級 - 行為的鍵值對;第三項(xiàng)是行為的記憶條件;第四項(xiàng)是活動結(jié)束后需要清除的記憶。
以悅靈的 AI 舉例,下方是悅靈 AI 的初始化。
從這一段代碼我們可以看出:
悅靈只有一個(gè)核心活動 CORE,沒有記憶條件,內(nèi)部所有行為的優(yōu)先級都為 0。
悅靈只有一個(gè)非核心活動 IDLE,同時(shí)它也是默認(rèn)活動,沒有記憶條件,內(nèi)部有 5 個(gè)不同優(yōu)先級的行為。
生物的非核心活動有多種設(shè)置方式。第一種是直接設(shè)置,調(diào)用 setActiveActivityIfPossible(如果目標(biāo)活動條件滿足則設(shè)置為目標(biāo)條件,如果不滿足設(shè)置為默認(rèn)活動)或 setActiveActivityToFirstValid(按順序嘗試設(shè)置活動,如果條件都不滿足則不改變當(dāng)前活躍的非核心活動);第二種是通過日程表設(shè)置,如果當(dāng)前時(shí)間的活動條件滿足則轉(zhuǎn)換,否則轉(zhuǎn)換為默認(rèn)活動。
簡單介紹完活動后,我們可以正式開始行為調(diào)度的講解了。下面是 Brain 類的 tick 方法。
第一行是用于忘記過時(shí)記憶的,第二行是調(diào)用感知器,第三行第四行是行為調(diào)度的代碼。可以看到在生物 AI 計(jì)算的時(shí)候計(jì)算順序是刷新記憶->調(diào)用感知器->啟動未運(yùn)行行為->執(zhí)行行為。第一行代碼將在下一部分講解,第三行第四行的方法如下:
從上面可以看出行為調(diào)度其實(shí)非常的簡單:按照優(yōu)先級排序所有的行為,并按照優(yōu)先級從高到低逐一嘗試啟動對應(yīng)活動正在活躍的不在運(yùn)行的行為。而行為的停止不受此處行為調(diào)度的影響,只有在行為本身決定停止時(shí)才會停止(只有村民有一個(gè)例外可以主動停止全部行為)。
下面仍然使用悅靈的 AI 舉例。
悅靈的核心活動只有 CORE,分別為 Swim(游泳)、AnimalPanic(驚慌狀態(tài)的逃竄)、LookAtTargetSink(看向 LOOK_TARGET)、MoveToTargetSink(向 WALK_TARGET 移動)和兩個(gè) CountDownCooldownTicks(LIKED_NOTEBLOCK_COOLDOWN_TICKS 和 ITEM_PICKUP_COOLDOWN_TICKS 的倒計(jì)時(shí))。
悅靈的非核心活動只有 IDLE,按照優(yōu)先級先后各個(gè)行為如下:
觸發(fā)器行為 GoToWantedItem,記憶條件為 WALK_TARGET、LOOK_TARGET 和?ITEM_PICKUP_COOLDOWN_TICKS 已注冊,且 NEAREST_VISIBLE_WANTED_ITEM 存在。效果是:如果 ITEM_PICKUP_COOLDOWN_TICKS 不存在、手上有物品、物品在世界邊界內(nèi)且物品距離悅靈 32 格內(nèi),悅靈會飛向想要物品的位置。
持續(xù)行為 GoAndGiveItemsToTarget,持續(xù)時(shí)間 20 刻,記憶條件為 WALK_TARGET、LOOK_TARGET 和?ITEM_PICKUP_COOLDOWN_TICKS 已注冊。效果是:如果悅靈物品欄內(nèi)有對應(yīng)物品,會嘗試飛向要將這個(gè)物品投擲出去的位置;如果距離投擲位置小于 3 格,則將物品欄內(nèi)物品投出去。
觸發(fā)器行為 StayCloseToTarget,記憶條件為 LOOK_TARGET 已注冊且 WALK_TARGET 不存在。效果是如果悅靈在物品投擲位置 16 格外會嘗試飛向物品的投擲位置。
觸發(fā)器行為 SetEntityLookTargetSometimes,記憶條件為 LOOK_TARGET 不存在且 NEAREST_VISIBLE_LIVING_ENTITIES 存在。效果是偶爾看向 6 格內(nèi)的生物。
挑選行為 RunOne,分別為:
權(quán)重為 2 的觸發(fā)器行為 RandomStroll,記憶條件為 WALK_TARGET 不存在,效果是隨機(jī)游走。
權(quán)重為 2 的觸發(fā)器行為 SetWalkTargetFromLookTarget,記憶條件是 LOOK_TARGET 存在但 WALK_TARGET 不存在,效果是飛向看向的地方。
權(quán)重為 1 的 DoNothing,沒有條件。
感覺很復(fù)雜?舉幾個(gè)例子就好理解了!
假設(shè)悅靈手上沒有物品,那么第一個(gè)行為的條件不滿足,第二個(gè)第三個(gè)不能啟動,第四個(gè)可能滿足,第五個(gè)前兩個(gè)可能滿足。這樣的效果是,悅靈偶爾看向最近的生物,會隨機(jī)飛行或飛向看向生物的位置,有時(shí)候可能停止在原地。
如果玩家給了悅靈物品,此時(shí)第二個(gè)行為不能啟動,其他行為都有可能滿足。
假如悅靈發(fā)現(xiàn)了一個(gè)對應(yīng)物品且完成了冷卻,第一個(gè)行為條件滿足可以啟動,WALK_TARGET 被設(shè)置,這時(shí)剩下的行為條件不滿足、不能啟動或是 DoNothing,表面上看就是悅靈飛向了對應(yīng)物品的位置。
如果悅靈已經(jīng)拿到了對應(yīng)物品,此時(shí)第二個(gè)行為可以啟動。由于第二個(gè)行為優(yōu)先級低于第一個(gè)行為,也就是執(zhí)行更晚,因此它可以覆蓋掉第一個(gè)行為對 WALK_TARGET 的修改,但是又因?yàn)閷?WALK_TARGET 的修改只發(fā)生在這個(gè)行為啟動的時(shí)候,而第一個(gè)行為是觸發(fā)器行為,每刻都可以啟動,然后立刻停止,所以在這個(gè)行為進(jìn)行中時(shí)第一個(gè)行為也可以把 WALK_TARGET 改到其他位置上去。所以我們可以看到悅靈是這樣處理的:如果悅靈投擲物品的位置距離悅靈比較遠(yuǎn),那么它就會先收集物品直到物品欄滿,再飛向指定位置投出物品;如果距離較近,那么悅靈可能邊收集物品邊投擲物品。
五. 舉例:村民與鐵傀儡生成
下面我們講講這整套系統(tǒng)的使用,以村民在驚慌狀態(tài)下生成鐵傀儡舉例。
先說說村民的驚慌狀態(tài),它對應(yīng)的是 PANIC 活動,而這個(gè)活動由 VillagerPanicTrigger 觸發(fā)。
如果村民周圍有恐嚇生物(由感知器?VillagerHostilesSensor 寫入到 NEAREST_HOSTILE)或受到了攻擊(由感知器 HurtBySensor 寫入 HURT_BY),則村民進(jìn)入驚慌活動。當(dāng)前面兩種條件都不滿足,這個(gè)持續(xù)行為就會停止。
那么鐵傀儡呢?鐵傀儡生成寫在了 VillagerPanicTrigger 持續(xù)行為的 tick 里面。
可以看到村民是每當(dāng)游戲時(shí)間可以被 100 整除時(shí)嘗試生成鐵傀儡,即每 5 秒嘗試生成一次。當(dāng)然這不是每次都能成功的,spawnGolemIfNeeded 的具體代碼如下。
從上面的代碼中,可以看出鐵傀儡生成的條件:
這個(gè)村民和以它的碰撞箱為中心擴(kuò)展 10 格的范圍內(nèi)至少有其他兩個(gè)村民也滿足下面的條件,即最少需要三個(gè)滿足下面條件的村民。
村民必須在 24000 游戲刻,即 20 分鐘內(nèi)睡過一次覺。
村民不存在記憶 GOLEM_DETECTED_RECENTLY。
如果上面的條件滿足了,那么上述范圍內(nèi)的所有村民(不管有沒有滿足后兩條條件)都會獲得過期時(shí)間為 600 游戲刻的 GOLEM_DETECTED_RECENTLY 記憶,并且一個(gè)鐵傀儡會在以判定村民腳部為中心的 17×13×17 的范圍內(nèi)生成。
GOLEM_DETECTED_RECENTLY 記憶的獲取途徑不止這一個(gè),在 GolemSensor 中也會嘗試尋找以村民的碰撞箱擴(kuò)展 16 格范圍內(nèi)的鐵傀儡,如果探測到了也會獲得過期時(shí)間為 600 游戲刻的 GOLEM_DETECTED_RECENTLY 記憶。
由于鐵傀儡必須在 GOLEM_DETECTED_RECENTLY 不存在時(shí)生成,而這兩種方式給的記憶過期時(shí)間都為 600 游戲刻,所以鐵傀儡生成的最短時(shí)鐘就是 600 刻,30 秒了。。。嗎?
在 22w12a 前,確實(shí)如此,這個(gè)記憶會在第 600 刻被清除,所以村民在這時(shí)是可以成功生成鐵傀儡的;但是在 22w12a 及以后,這個(gè)記憶在第 600 刻仍然存在,在第 601 刻才被消除,而因?yàn)樯设F傀儡是每 5 秒嘗試一次,所以最短時(shí)鐘是 35 秒而不是 30 秒。那么這是為什么呢?
這其實(shí)是因?yàn)?Mojang 在設(shè)計(jì)“過期時(shí)間”上的問題。在 22w12a 之前,記憶是先減時(shí)間后清除;在 22w12a 及之后是先清除后減時(shí)間。這導(dǎo)致過期時(shí)間這個(gè)定義發(fā)生了變化,在 22w12a 之前,它其實(shí)指的是這個(gè)記憶在第幾刻被清除,而此刻過期時(shí)間為 0 是無效值,如果此刻過期時(shí)間為 1 就代表在下一刻被清除;但是經(jīng)過 22w12a 這次改變,導(dǎo)致它的定義變成了從此刻開始到記憶清除中間要經(jīng)過多少刻,也就是此刻過期時(shí)間為 0 不是無效值,并且它代表下一刻被清除,或者說莫名其妙的加了額外的 1 刻延遲。

這個(gè)改動會讓基于原先時(shí)鐘的刷鐵機(jī)效率減半,因?yàn)閮纱螘r(shí)鐘才有一次成功;如果修改并成功匹配時(shí)鐘,效率也會相比原先降低 14.3% 左右,解決辦法只有加單元。
六. 村民行為
接下來我們講一個(gè)生物整體 AI 的例子,就是村民的行為。由于村民有不同的類型,在這里我們只挑成年村民做例子。
村民使用日程表決定當(dāng)前的活動。所有成年村民都使用 VILLAGER_DEFAULT 日程表,它的定義如下:
可以看出,村民的日程如下:
00010 - 02000(6:36 - 8:00):空閑活動。
02000 - 09000(8:00 - 15:00):工作活動(如果為無業(yè)或傻子則為空閑活動)。
09000 - 11000(15:00 - 17:00):聚集活動(如果村莊聚集點(diǎn)不存在則為空閑活動)。
11000 - 12000(17:00 - 18:00):空閑活動。
12000 - 00010(18:00 - 次日 6:36):休息活動。
先從空閑活動說起,空閑活動的定義如下。
可以看出,村民在空閑活動下有下面這些行為:
優(yōu)先級最高的行為有:走向其他村民(權(quán)重最大)、走向可繁殖的村民、走向貓、隨機(jī)游走、走向看向的地方和在床上跳躍(成年村民此行為不能啟動)。上面這些行為只能啟動一個(gè),互相排斥。
優(yōu)先級較低的有:送給有村莊英雄的玩家禮物、看向玩家、給玩家展示交易物品、村民間丟物品和村民繁殖。這些行為可以并行,但是有些行為觸發(fā)后會使其他行為記憶不滿足。
優(yōu)先級更低的是看向其他實(shí)體。
優(yōu)先級最低的是更新日程表。
村民的工作活動如下:
可以看出,村民在工作活動中是這樣的:
優(yōu)先級最高的是走向工作站點(diǎn),如果是失業(yè)村民這項(xiàng)無法啟動。
優(yōu)先級低一點(diǎn)的是送給村莊英雄禮物。
優(yōu)先級其次的是看向其他實(shí)體(偷懶是吧)和一個(gè)挑選行為。這些挑選行為失業(yè)村民都無法啟動,包括在工作站點(diǎn)工作(權(quán)重為 7)、游走向工作站點(diǎn)(權(quán)重為 10)、游走在工作站點(diǎn)周圍(權(quán)重為 4)、游走于可選工作站點(diǎn)(權(quán)重為 5)、收獲并種下作物(農(nóng)民權(quán)重為 2,其他村民為 5 但不可啟動)、使用骨粉催熟(農(nóng)民權(quán)重為 4,其他村民為 7)。
優(yōu)先級更低的是展示交易和看向玩家。
優(yōu)先級最低的是更新活動。
可以看出,不是農(nóng)民的村民也可以使用骨粉(很意外吧),甚至權(quán)重更高。村民在工作站點(diǎn)周圍不是一直呆著,而是游走于周圍,并有時(shí)回到工作站點(diǎn)工作。
村民的聚集活動如下:
可以看出村民在聚集活動時(shí)有下面的行為:
優(yōu)先級最高為走向村莊聚集點(diǎn)、在聚集點(diǎn)附近走動或與其他村民社交。
其次為送給村莊英雄禮物、驗(yàn)證周圍聚集點(diǎn)有效性和丟給其他村民物品。
更低的是展示交易物品和看向玩家。
再低一點(diǎn)的是看向其他實(shí)體。
最低的是更新活動。
聚集活動要求必須有村莊聚集點(diǎn),也就是鐘。如果不存在聚集點(diǎn),這段時(shí)間內(nèi)將變?yōu)榭臻e活動。
村民的休息活動如下:
村民的休息活動含有下面這些行為:
優(yōu)先級最高的是走回自己的床鋪,如果床不存在這項(xiàng)不能啟動。
優(yōu)先級其次是驗(yàn)證床的有效性和在床上睡覺。
優(yōu)先級再低一點(diǎn)是一個(gè)選擇行為,這個(gè)行為的啟動條件是床不存在。包括走向最近的床(無論是否被占用,權(quán)重為 1)、在不露天的位置徘徊(權(quán)重為 4)、走回最近的村莊(權(quán)重為 2)和什么也不做(權(quán)重為 2)。與它一個(gè)優(yōu)先級的是看向其他實(shí)體。
優(yōu)先級最低的是更新活動。
那么村民獲得工作的行為在哪里呢?其實(shí)它寫在了村民的核心活動里面。
核心活動中定義了村民最重要的行為,比如游泳、與門互動、看向和走向某個(gè)位置、變?yōu)轶@慌狀態(tài)、從床上蘇醒、對鐘聲起反應(yīng)和變?yōu)橐u擊活動。所有有關(guān)于村民就業(yè)的行為也在這里定義,比如驗(yàn)證工作站點(diǎn)和驗(yàn)證可能的工作站點(diǎn)、檢查競爭工作站點(diǎn)、獲取工作站點(diǎn)、轉(zhuǎn)讓工作站點(diǎn)和重置職業(yè)。村莊聚集點(diǎn)和床位的獲取也是在這里定義的。具體怎么處理工作站點(diǎn)在下一章節(jié)會涉及。
除了核心活動和四種日程活動外,村民還有四種活動,但都需要在某種情況下才會觸發(fā)。
驚慌活動只有在村民受恐嚇或者受傷時(shí)才會出現(xiàn),它的產(chǎn)生代碼在上文生成鐵傀儡中已經(jīng)說明。
在驚慌活動中,村民的速度會提升 50%,逃離恐嚇實(shí)體和傷害村民的實(shí)體。當(dāng)村民遠(yuǎn)離傷害實(shí)體 6 格且不存在恐嚇實(shí)體時(shí)驚慌活動自動解除。
如果玩家觸發(fā)了襲擊,那么村民會進(jìn)入襲擊活動或準(zhǔn)備襲擊活動。
準(zhǔn)備襲擊活動發(fā)生在襲擊將要開始和襲擊波次之間的時(shí)間內(nèi)。
可以看出,村民在襲擊間會鳴鐘,跑向鐘或快速的無目的的走動。
當(dāng)襲擊正在進(jìn)行或襲擊結(jié)束的一段時(shí)間內(nèi),村民進(jìn)入襲擊活動。
在這段代碼中,Mojang 寫錯(cuò)了一個(gè)方法的名字,raidExistsAndNotVictory 其實(shí)是 raidExistsAndVictory。上面的代碼說明:如果襲擊勝利村民會走出屋外找到露天的位置開始慶祝,如果襲擊在進(jìn)行中則去找可以躲藏的位置。
村民的最后一個(gè)活動是躲藏活動,當(dāng)鳴鐘后村民會進(jìn)入這個(gè)活動。
可以看出村民在躲藏活動中會盡快找到躲藏的位置,通常這個(gè)活動只會持續(xù) 300 刻。
七. 常見行為
下面將簡單介紹一些行為,這些行為被大多數(shù)使用這個(gè)系統(tǒng)的實(shí)體使用。
1. POI 類行為
POI,全稱 Point of Interest,也就是“興趣點(diǎn)”。它的作用是保存世界上一些特殊方塊的位置,以便于快速查找,并記錄這個(gè)方塊被多少個(gè)實(shí)體“占領(lǐng)”。
AcquirePoi
生物檢查周圍 POI 的行為是 AcquirePoi,它的具體代碼比較復(fù)雜。
AcquirePoi 要求了兩個(gè)記憶,一個(gè)是存儲認(rèn)領(lǐng) POI 位置的記憶,一個(gè)是存儲可能成為認(rèn)領(lǐng) POI 的位置的記憶。這個(gè)行為本身不會將 POI 位置信息寫入第一個(gè)記憶,而是寫入第二個(gè)記憶。如果兩個(gè)記憶是相同的,則這個(gè)行為只會在這個(gè)記憶不存在時(shí)可能執(zhí)行,并且等效于這個(gè)行為直接指定了生物認(rèn)領(lǐng)這個(gè) POI;如果兩個(gè)記憶不同,那么這個(gè)行為必須在這兩個(gè)記憶都不存在時(shí)可能執(zhí)行,并且在第二個(gè)記憶中的 POI 信息需要另一個(gè)行為寫入到第一個(gè)記憶。
以村民為例,村民的核心活動中使用了 3 次這個(gè)行為,分別用于:
HOME,也就是床位。
MEETING,村莊聚集點(diǎn)。
JOB_SITE、POTENTIAL_JOB_SITE,村民工作站點(diǎn)。
床位和聚集點(diǎn)的行為兩個(gè)記憶都相同,也就是發(fā)現(xiàn)之后就能立刻成功認(rèn)領(lǐng)。而工作站點(diǎn)的兩個(gè)記憶不同,將潛在工作站點(diǎn)變?yōu)檎焦ぷ髡军c(diǎn)需要?GoToPotentialJobSite (走向潛在的工作站點(diǎn),要求非核心活動必須是空閑、工作或玩耍)和 AssignProfessionFromJobSite 操作,而在這轉(zhuǎn)變的過程中村民可能會因?yàn)楦偁幨ミ@個(gè) POI 的認(rèn)領(lǐng)。
AcquirePoi 不是每刻都能成功執(zhí)行,它的掃描間隔在 21~40 刻左右,掃描范圍是以生物為中心半徑 48 格內(nèi)的離得最近的 5 個(gè)與生物要求匹配且不在冷卻時(shí)間內(nèi)的 POI。每次掃描會檢查生物能否找到一條可行路徑到達(dá)某個(gè) POI 的認(rèn)領(lǐng)半徑(通常為 1,鐘為 6)內(nèi)。如果找到了一條路徑,則這個(gè) POI 被生物認(rèn)領(lǐng),如果沒有找到任何路徑,則所有這些 POI 都會被標(biāo)記一個(gè) 40 到 80 刻的冷卻時(shí)間,代表在這段時(shí)間內(nèi)這些位置不會再次檢測。如果冷卻時(shí)間過后這些位置仍然不可到達(dá),那么會再次標(biāo)記冷卻,并且增加 40 到 80 刻的冷卻時(shí)間,直到冷卻時(shí)間到達(dá) 400 刻冷卻重置。
ValidateNearbyPoi
認(rèn)領(lǐng) POI 后,生物需要檢查這個(gè) POI 是否仍然存在,這就是 ValidateNearbyPoi 的工作。
當(dāng)生物與 POI 位于同一個(gè)維度,且距離小于 16 格時(shí)才會檢查 POI 是否存在。也就是說,如果一個(gè) POI 被破壞時(shí)生物距離它大于 16 格,生物就不會知道這個(gè) POI 已經(jīng)不存在了,直到生物回到原 POI 位置 16 格內(nèi)或找不到可達(dá)路徑超過一定時(shí)間(具體代碼在 SetWalkTargetFromBlockMemory)才會發(fā)現(xiàn) POI 已經(jīng)不存在并抹除記憶。

對于床,這個(gè)行為有特殊的判定。如果床被其他生物或玩家先行占用,則生物也會認(rèn)為 POI 無效并抹除記憶,同時(shí)解除對這個(gè) POI 的占用。
YieldJobSite
POI 行為有很多,在這里最后再舉一個(gè)例子,這個(gè)行為和村民讓出工作站點(diǎn)有關(guān)。
當(dāng)一個(gè)無業(yè)村民找到潛在工作站點(diǎn)后,這個(gè)行為會掃描周圍的村民。如果周圍有無工作站點(diǎn)的有職業(yè)村民,且這個(gè)村民的職業(yè)與潛在工作站點(diǎn)匹配,并且這個(gè)村民可以到達(dá)這個(gè)工作站點(diǎn),那么這個(gè)無業(yè)村民就會讓出這個(gè)工作站點(diǎn)并終止行動。
這個(gè)行為曾經(jīng)在 22w45a 重構(gòu)了一次,但是由于 Mojang 的疏忽,將條件給反轉(zhuǎn)了,寫成了這樣:
這使得這個(gè)行為的運(yùn)行條件變成了“無工作站點(diǎn)有職業(yè)的村民”。如果有兩個(gè)同樣職業(yè)且沒有工作站點(diǎn)的村民在一起,在放下它們對應(yīng)工作方塊后,它們就會互相轉(zhuǎn)讓這個(gè)工作方塊。由于這個(gè)行為的優(yōu)先級比較低,代表了它在村民 AI 較后執(zhí)行,而互相讓出工作方塊會清除行走目標(biāo)和觀察目標(biāo),導(dǎo)致這兩個(gè)村民看起來像是被“凍結(jié)”了(而且這個(gè)效果非常明顯,會突然停下來)。這個(gè)漏洞對應(yīng) MC-258295,直到 23w03a 才被修復(fù),也就是說在 1.19.3 會受到這個(gè)漏洞的影響。
2. LookAtTargetSink 和 MoveToTargetSink
在上一篇專欄中我們說過生物控制器通常需要調(diào)用尋路系統(tǒng)或直接調(diào)用,可是在上面的代碼中我們看到的都是使用 WALK_TARGET 行走目標(biāo)記憶和 LOOK_TARGET 觀察目標(biāo)記憶。使用這兩種記憶并將這些數(shù)據(jù)傳遞給控制器和尋路系統(tǒng)的行為就是這兩個(gè)。
WALK_TARGET 是一個(gè)短期記憶,類型是 WalkTarget,它含有三個(gè)字段:target(目標(biāo)位置)、speedModifier(行走速度)和 closeEnoughDist(到達(dá)距離)。
生物到達(dá)行走目標(biāo)的判定是距離行走目標(biāo)的曼哈頓距離小于到達(dá)距離。行走目標(biāo)可以綁定在一個(gè)方塊上也可以是一個(gè)實(shí)體,所以在行為執(zhí)行時(shí)需要重新計(jì)算路徑。
如果行走目標(biāo)與上一次計(jì)算路徑時(shí)的位置距離 2 格以上,生物就會開始重新計(jì)算行走路徑。如果重新計(jì)算路徑后發(fā)現(xiàn)無法直接到達(dá)目標(biāo),就會獲得 CANT_REACH_WALK_TARGET_SINCE 記憶。這個(gè)記憶只有在生物成功到達(dá)目標(biāo)或重新計(jì)算找到了一條合適的路徑時(shí)才會清除,它影響其他的行為判斷是否放棄掉這個(gè)目標(biāo)。如果生物完全無法找到一條路徑,就會生成一個(gè)隨機(jī)游走終點(diǎn)并嘗試走向那個(gè)位置,在下一刻可能會繼續(xù)重新計(jì)算路徑。如果行為停止時(shí)生物因?yàn)榭ㄗ《鴽]有到達(dá)行走目標(biāo),那么這個(gè)生物會獲得不大于 40 刻的行走冷卻時(shí)間,在這段時(shí)間內(nèi)不會再嘗試行走。
生物觀察要比生物行走簡單得多,僅通過當(dāng)前位置讓生物追隨目標(biāo)就可以。
3. MeleeAttack
與目標(biāo)系統(tǒng)一樣,記憶行為系統(tǒng)也有近戰(zhàn)攻擊這個(gè)行為,但是它們的實(shí)現(xiàn)就不太一樣了。
可以看出當(dāng)生物攻擊目標(biāo)存在并且能探測到攻擊目標(biāo)時(shí)才能攻擊,如果生物本身可以使用遠(yuǎn)程攻擊武器而身上剛好有這種物品那么就不會進(jìn)行近戰(zhàn)攻擊。比如豬靈,在它拿著弩的時(shí)候它是不會近戰(zhàn)攻擊的,只會射箭;只有當(dāng)它不拿弩的時(shí)候它才會近戰(zhàn)攻擊。

這就是這篇專欄的全部內(nèi)容了。有問題可以在評論區(qū)指出。
混淆映射表:Mojang Mapping。
版本:1.19.3~23w05a。
反混淆器:Nickid2018/GitMCDecomp
反編譯器:CFR 0.152