国产av一二三区|日本不卡动作网站|黄色天天久久影片|99草成人免费在线视频|AV三级片成人电影在线|成年人aV不卡免费播放|日韩无码成人一级片视频|人人看人人玩开心色AV|人妻系列在线观看|亚洲av无码一区二区三区在线播放

網(wǎng)易首頁(yè) > 網(wǎng)易號(hào) > 正文 申請(qǐng)入駐

游戲AI行為決策——HTN(分層任務(wù)網(wǎng)絡(luò))

0
分享至

【USparkle專(zhuān)欄】如果你深懷絕技,愛(ài)“搞點(diǎn)研究”,樂(lè)于分享也博采眾長(zhǎng),我們期待你的加入,讓智慧的火花碰撞交織,讓知識(shí)的傳遞生生不息!

這是侑虎科技第1855篇文章,感謝作者狐王駕虎供稿。歡迎轉(zhuǎn)發(fā)分享,未經(jīng)作者授權(quán)請(qǐng)勿轉(zhuǎn)載。如果您有任何獨(dú)到的見(jiàn)解或者發(fā)現(xiàn)也歡迎聯(lián)系我們,一起探討。(QQ群:793972859)

作者主頁(yè):

https://home.cnblogs.com/u/OwlCat

一、前言

Hierarchical Task Network(分層任務(wù)網(wǎng)絡(luò)),簡(jiǎn)稱(chēng)HTN,與行為樹(shù)、GOAP一樣,也是一種行為決策方法。在《地平線:零之曙光》、《變形金剛:塞伯坦的隕落》中都有用它來(lái)制作游戲敵人的AI。比起其它行為決策方法,HTN有個(gè)十分鮮明的特點(diǎn):推演。

HTN允許我們把要做的事以高度復(fù)雜的「復(fù)合任務(wù)」來(lái)表示,而不是單單一個(gè)行為。什么意思呢?無(wú)論是有限狀態(tài)機(jī)狀態(tài)的轉(zhuǎn)換,還是行為樹(shù)節(jié)點(diǎn)的切換,大多時(shí)候只是從一個(gè)執(zhí)行動(dòng)作變?yōu)閳?zhí)行另一個(gè)動(dòng)作。而HTN的一次規(guī)劃,可以一口氣規(guī)劃出包含好幾個(gè)動(dòng)作的「復(fù)合任務(wù)」,你看到它做出的新動(dòng)作,也不過(guò)是之前就計(jì)劃好的一部分。

這么看來(lái),好像還有點(diǎn)預(yù)知未來(lái)的味道呢,說(shuō)得越來(lái)越玄乎了,直接來(lái)看看它的運(yùn)行邏輯吧!

PS:據(jù)后續(xù)反饋,分享一個(gè)有應(yīng)用HTN的項(xiàng)目的代碼[1](也可以用該gitee倉(cāng)庫(kù)[2])HTN使用的主要部分在該項(xiàng)目的Script/Characters/Enemy部分,有需要的可以參考看看這個(gè)HTN的實(shí)際使用。

二、運(yùn)行邏輯

HTN的整體結(jié)構(gòu)框架如下:

別怕,看著復(fù)雜而已,相信你能夠理解的:

1. 任務(wù)

首先,和其它行為決策方法一樣,角色內(nèi)部有存儲(chǔ)一系列要做的事。在有限狀態(tài)機(jī)中是「狀態(tài)」,行為樹(shù)中是「動(dòng)作節(jié)點(diǎn)」,而HTN中是「任務(wù)(Task)」。但要注意,HTN的「任務(wù)」十分特殊,它不只是單一的動(dòng)作,可能包含多個(gè)動(dòng)作,總的可以分為三種:「復(fù)合任務(wù)」、「方法」以及「原子任務(wù)」。

  • 原子任務(wù):是最簡(jiǎn)單的任務(wù),只是單一的動(dòng)作,像「奔跑」、「跳躍」等就算是原子任務(wù)。通常也不建議把一個(gè)原子任務(wù)設(shè)計(jì)得太復(fù)雜。

  • 復(fù)合任務(wù):只理解為是多個(gè)原子任務(wù)組合成的,并不完全正確。復(fù)合任務(wù)是由多個(gè)「方法」組合而成的,而每次執(zhí)行復(fù)合任務(wù),只會(huì)選擇組成它的眾多「方法」之一來(lái)執(zhí)行,就像行為樹(shù)的選擇節(jié)點(diǎn)一樣。

  • 方法:方法是HTN讓角色行動(dòng)豐富的關(guān)鍵,一個(gè)方法可以由多個(gè)「原子任務(wù)」或「復(fù)合任務(wù)」組合而成。在「方法」的幫助下,我們可以自然且清晰地構(gòu)建豐富的行為。以「砍樹(shù)」為例,可以構(gòu)造成這個(gè)樣子:

方法的執(zhí)行,會(huì)逐一判斷組成的「復(fù)合任務(wù)」和「原子任務(wù)」是否滿足條件,只要有一個(gè)不滿足,這個(gè)方法便會(huì)被放棄,它有點(diǎn)像行為樹(shù)中的順序節(jié)點(diǎn)。

這里要多說(shuō)一嘴,「復(fù)合任務(wù)」和「方法」只會(huì)在HTN的規(guī)劃階段被執(zhí)行。所謂「規(guī)劃階段」,就是根據(jù)「世界狀態(tài)」來(lái)決定該做什么事,規(guī)劃時(shí)會(huì)把要做的「復(fù)合任務(wù)」和「方法」統(tǒng)統(tǒng)分解成一個(gè)個(gè)「原子任務(wù)」。也就是說(shuō),最終角色實(shí)際執(zhí)行的都是「原子任務(wù)」。

2. 世界狀態(tài)

在游戲常用的決策行為算法中,只有GOAP和HTN有用到「世界狀態(tài)」。其實(shí)這是更接近傳統(tǒng)人工智能的設(shè)計(jì)方式(GOAP和HTN也確實(shí)是由傳統(tǒng)人工智能轉(zhuǎn)變來(lái)的),還是以「砍樹(shù)」為例,想要讓一個(gè)角色去砍樹(shù),他就得知道:哪里有樹(shù)、哪里有電鋸、電鋸有多少油……這些做事的前提都可以歸為「世界狀態(tài)」的一員,反過(guò)來(lái)說(shuō),世界狀態(tài)就是這類(lèi)「前提條件」的集合,它們共同構(gòu)成了HTN任務(wù)規(guī)劃的基礎(chǔ)。

在規(guī)劃階段,角色會(huì)復(fù)制一份「世界狀態(tài)」的副本用于個(gè)人判斷并選出可執(zhí)行的任務(wù),就好像是偵探拿著照片進(jìn)行腦補(bǔ)推斷一樣。這個(gè)過(guò)程不會(huì)影響真正的「世界狀態(tài)」。而在選出了可執(zhí)行的任務(wù)后,就會(huì)將它分解成一系列「原子任務(wù)」挨個(gè)執(zhí)行。有些(或者說(shuō)大多數(shù))「原子任務(wù)」執(zhí)行完成后會(huì)對(duì)「世界狀態(tài)」造成一定影響,比如開(kāi)槍會(huì)減少?gòu)椝帞?shù),鋸?fù)陿?shù)會(huì)減少樹(shù)木數(shù)量等等。但要注意,這里的影響就不再是“腦補(bǔ)”的啦,而是真正改變「世界狀態(tài)」的某些值。就像是部隊(duì)制定完計(jì)劃后,就開(kāi)始正式行動(dòng)了。

3. 總結(jié)

通過(guò)上述兩大點(diǎn),我想已經(jīng)能大概弄清楚HTN的運(yùn)行邏輯了吧(如果還是很懵,可以看看這個(gè)視頻[3]相關(guān)部分的介紹):根據(jù)世界狀態(tài)來(lái)選擇要執(zhí)行的任務(wù),再將選好的任務(wù)分解為一個(gè)個(gè)原子任務(wù)來(lái)執(zhí)行,而原子任務(wù)執(zhí)行完后又會(huì)影響世界狀態(tài)。一旦分解出的原子任務(wù)都執(zhí)行完了,又或者某個(gè)原子任務(wù)的執(zhí)行條件突然不能滿足了,就重新選擇,重復(fù)這個(gè)步驟。這就是HTN大體的運(yùn)行邏輯了。

三、代碼實(shí)現(xiàn)

這次代碼實(shí)現(xiàn)同樣參考了Steve Rabin的《Game AI Pro》[4],相比之前我們實(shí)現(xiàn)的行為樹(shù),這次所要寫(xiě)的類(lèi)不會(huì)太多(除去注釋的話就更少了)。

1. 世界狀態(tài)

世界狀態(tài)實(shí)現(xiàn)的難點(diǎn)在于:

1. 狀態(tài)數(shù)據(jù)的類(lèi)型是多種多樣的,該用什么來(lái)統(tǒng)一保存?

2. 狀態(tài)數(shù)據(jù)會(huì)時(shí)時(shí)變化,如何保證存儲(chǔ)的數(shù)據(jù)也會(huì)同步更新?

對(duì)于問(wèn)題1,我們可以用 的字典來(lái)解決。畢竟C ,Object類(lèi)是所有數(shù)據(jù)類(lèi)型的老祖宗。那問(wèn)題2呢,假設(shè)用這種字典存儲(chǔ)了某個(gè)角色的血量,那這個(gè)角色就算血量變成0了,字典里存儲(chǔ)的也只是剛存進(jìn)去時(shí)的那個(gè)值而不是0。而且反過(guò)來(lái),我們修改字典里的這個(gè)血量值,也不會(huì)影響實(shí)際角色的血量……除非,這些值能像屬性一樣……

這是可以做到的!但要用到兩個(gè)字典,一個(gè)用來(lái)模仿屬性的get,一個(gè)用來(lái)模仿屬性的set。分別用值類(lèi)型為System.Action和System.Func的字典就可以了。

到這里我得再說(shuō)一下,如果對(duì)于上面這幾段話中的一些名詞你有些許疑惑的話,就該再學(xué)習(xí)一下C,否則你可能不能理解世界狀態(tài)類(lèi)的實(shí)現(xiàn):

//世界狀態(tài)只有一個(gè)即可,我們將其設(shè)為靜態(tài)類(lèi) publicstaticclassHTNWorld {     //讀 世界狀態(tài)的字典     privatestaticreadonly Dictionary

 > get_WorldState;     //寫(xiě) 世界狀態(tài)的字典     privatestaticreadonly Dictionary

 > set_WorldState;     static HTNWorld()     {         get_WorldState = new Dictionary

 >();         set_WorldState = new Dictionary

 >();     }     //添加一個(gè)狀態(tài),需要傳入狀態(tài)名、讀取函數(shù)和寫(xiě)入函數(shù)     public static void AddState(string key, Func
getter, Action setter)     {         get_WorldState[key] = getter;         set_WorldState[key] = setter;     }     //根據(jù)狀態(tài)名移除某個(gè)世界狀態(tài)     public static void RemoveState(string key)     {         get_WorldState.Remove(key);         set_WorldState.Remove(key);     }     //修改某個(gè)狀態(tài)的值     public static void UpdateState(string key, object value)     {         //就是通過(guò)寫(xiě)入字典修改的         set_WorldState[key].Invoke(value);     }     //讀取某個(gè)狀態(tài)的值,利用泛型,可以將獲取的object轉(zhuǎn)為指定的類(lèi)型     public static T GetWorldState

 (string key)     {         return (T)get_WorldState[key].Invoke();     }     //復(fù)制一份當(dāng)前世界狀態(tài)的值(這個(gè)主要是用在規(guī)劃中)     public static Dictionary

  CopyWorldState()     {         var copy = new Dictionary

 ();         foreach(var state in get_WorldState)         {             copy.Add(state.Key, state.Value.Invoke());         }         return copy;     } }







2. 任務(wù)類(lèi)接口

「復(fù)合任務(wù)」、「方法」和「原子任務(wù)」它們有共通之處,我們把這些共通之處以接口的形式提煉出來(lái),可以簡(jiǎn)化我們?cè)谝?guī)劃環(huán)節(jié)的代碼邏輯。

//用于描述運(yùn)行結(jié)果的枚舉(如果有看上一篇行為樹(shù)的話,也可以直接用行為樹(shù)的EStatus) public enum EStatus {     Failure, Success, Running,  } public interface IBaseTask {     //判斷是否滿足條件     bool MetCondition(Dictionary

 worldState);     //添加子任務(wù)     void AddNextTask(IBaseTask nextTask); }

3. 原子任務(wù)

原子任務(wù)是一個(gè)抽象類(lèi),相當(dāng)于行為樹(shù)中的動(dòng)作節(jié)點(diǎn),用于開(kāi)發(fā)者自定義的最小單元任務(wù)。一般就是像「開(kāi)火」、「奔跑」之類(lèi)的簡(jiǎn)單動(dòng)作。值得注意的是,這里的條件判斷和執(zhí)行影響都要分兩種情況,一種是規(guī)劃時(shí),一種是實(shí)際執(zhí)行時(shí),因?yàn)橐?guī)劃時(shí)我們使用的并不是真正的世界狀態(tài),而是一份模擬的世界狀態(tài)副本。

public abstractclassPrimitiveTask : IBaseTask {     //原子任務(wù)不可以再分解為子任務(wù),所以AddNextTask方法不必實(shí)現(xiàn)     void IBaseTask.AddNextTask(IBaseTask nextTask)     {         thrownew System.NotImplementedException();     }     ///      /// 執(zhí)行前判斷條件是否滿足,傳入null時(shí)直接修改HTNWorld     ///      /// 用于plan的世界狀態(tài)副本     public bool MetCondition(Dictionary

 worldState = null)     {         if(worldState == null)//實(shí)際運(yùn)行時(shí)         {             return MetCondition_OnRun();         }         else//模擬規(guī)劃時(shí),若能滿足條件就直接進(jìn)行Effect         {             if(MetCondition_OnPlan(worldState))             {                 Effect_OnPlan(worldState);                 returntrue;             }             returnfalse;         }     }     protected virtual bool MetCondition_OnPlan(Dictionary

 worldState)     {         returntrue;     }     protected virtual bool MetCondition_OnRun()     {         returntrue;     }     //任務(wù)的具體運(yùn)行邏輯,交給具體類(lèi)實(shí)現(xiàn)     public abstract EStatus Operator();     ///      /// 執(zhí)行成功后的影響,傳入null時(shí)直接修改HTNWorld     ///      /// 用于plan的世界狀態(tài)副本     public void Effect(Dictionary

 worldState = null)     {         Effect_OnRun();     }     protected virtual void Effect_OnPlan(Dictionary

 worldState)     {         ;     }     protected virtual void Effect_OnRun()     {         ;     } }




4. 方法

方法既可以添加「復(fù)合任務(wù)」又可以添加「原子任務(wù)」作組成的子任務(wù),所以我們用IBaseTask列表來(lái)存儲(chǔ);而方法的滿足與否,要看兩個(gè)條件,具體看代碼注釋吧:

public classMethod : IBaseTask {     //子任務(wù)列表,可以是復(fù)合任務(wù),也可以是原點(diǎn)任務(wù)     public List SubTask {  get; privateset; }     //方法的前提條件     privatereadonly Func

 condition;     public Method(Func

 condition)     {         SubTask = new List ();         this.condition = condition;     }     //方法條件滿足的判斷=方法本身前提條件滿足+所有子任務(wù)條件滿足     public bool MetCondition(Dictionary

 worldState = null)     {         /*         再?gòu)?fù)制一遍世界狀態(tài),用于追蹤每個(gè)子任務(wù)的Effect。方法有多個(gè)子任務(wù),         只要其中一個(gè)不滿足條件,那整個(gè)方法不滿足條件,之前子任務(wù)進(jìn)行Effect也不算數(shù)         因此用tpWorld記錄,待驗(yàn)證了方法滿足條件后(所有子任務(wù)均滿足條件),再?gòu)?fù)制回worldState         */         var tpWorld = new Dictionary

 (worldState);         if (condition())//方法自身的前提條件是否滿足         {             for (int i = 0; i < SubTask.Count; ++i)             {                 //一旦有一個(gè)子任務(wù)的條件不滿足,這個(gè)方法就不滿足了                 if(!SubTask[i].MetCondition(tpWorld))                 {                     returnfalse;                 }             }             //最終滿足條件后,再將各Effect導(dǎo)致的新世界狀態(tài)(tpWorld)給worldState             worldState = tpWorld;             returntrue;//如果子任務(wù)全都滿足了,那就成了!         }         returnfalse;     }     //添加子任務(wù)     public void AddNextTask(IBaseTask nextTask)     {         SubTask.Add(nextTask);     } }




5. 復(fù)合任務(wù)

復(fù)合任務(wù)和「方法」類(lèi)似,只不過(guò)只能添加「方法」作為子任務(wù)。

public classCompoundTask : IBaseTask {     //選中的方法     public Method ValidMethod { get; privateset; }     //子任務(wù)(方法)列表     privatereadonly List methods;     public CompoundTask()     {         methods = new List ();     }     public void AddNextTask(IBaseTask nextTask)     {         //要判斷添加進(jìn)來(lái)的是不是方法類(lèi),是的話才添加         if (nextTask is Method m)         {             methods.Add(m);         }     }     public bool MetCondition(Dictionary

 worldState)     {         for (int i = 0; i < methods.Count; ++i)         {             //只要有一個(gè)方法滿足前提條件就可以             if(methods[i].MetCondition(worldState))             {                 //記錄下這個(gè)滿足的方法                 ValidMethod = methods[i];                 returntrue;             }         }         returnfalse;     } }

到這里,基本的組件類(lèi)就全部完成了,對(duì)比行為樹(shù)那章,代碼量很少對(duì)吧?接下來(lái)就是有關(guān)構(gòu)造的類(lèi)了。

6. 規(guī)劃器

規(guī)劃器的要點(diǎn)在于對(duì)「復(fù)合任務(wù)」的分解,這里提一下,一個(gè)HTN會(huì)保證有一個(gè)復(fù)合任務(wù)作為根任務(wù),就和行為樹(shù)的根節(jié)點(diǎn)一樣。分解也是由此開(kāi)始:

public classHTNPlanner {     //最終分解完成的所有原子任務(wù)存放的列表     public Stack FinalTasks {  get; privateset; }     //分解過(guò)程中,用來(lái)緩存被分解出的任務(wù)的棧,因?yàn)轭?lèi)型各異,故用IBaseTask類(lèi)型     privatereadonly Stack taskOfProcess;     privatereadonly CompoundTask rootTask;//根任務(wù)     public HTNPlanner(CompoundTask rootTask)     {         this.rootTask = rootTask;         taskOfProcess = new Stack ();         FinalTasks = new Stack ();     }     //規(guī)劃(核心)     public void Plan()     {         //先復(fù)制一份世界狀態(tài)         var worldState = HTNWorld.CopyWorldState();         //將存儲(chǔ)列表清空,避免上次計(jì)劃結(jié)果的影響         FinalTasks.Clear();         //將根任務(wù)壓進(jìn)棧中,準(zhǔn)備分解         taskOfProcess.Push(rootTask);         //只要棧還沒(méi)空,就繼續(xù)分解         while(taskOfProcess.Count > 0)         {             //拿出棧頂?shù)脑?            var task = taskOfProcess.Pop();             //如果這個(gè)元素是復(fù)合任務(wù)             if(task is CompoundTask cTask)             {                 //判斷是否可以執(zhí)行                 if(cTask.MetCondition(worldState))                 {                     /*如果可以執(zhí)行,就肯定有可用的方法,                     就將該方法的子任務(wù)都?jí)喝霔V?,以便繼續(xù)分解*/                     var subTask = cTask.ValidMethod.SubTask;                     foreach(var t in subTask)                     {                         taskOfProcess.Push(t);                     }                     /*通過(guò)上面的步驟我們知道,能被壓進(jìn)棧中的只有                     復(fù)合任務(wù)和原子任務(wù),方法本身并不會(huì)入棧*/                 }             }             else//否則,這個(gè)元素就是原子任務(wù)             {                 //將該元素轉(zhuǎn)為原子任務(wù),因?yàn)樵臼荌BaseTask類(lèi)型                 var pTask = task as PrimitiveTask;                 //再將該原子任務(wù)加入存放分解完成的任務(wù)列表                 FinalTasks.Push(pTask);             }         }     } }

7. 執(zhí)行器

執(zhí)行器的關(guān)鍵在于如何確認(rèn)一個(gè)原子任務(wù)是否執(zhí)行完成,并且要在執(zhí)行完成后產(chǎn)生影響并切換到下一個(gè)原子任務(wù)。

public classHTNPlanRunner {     //當(dāng)前運(yùn)行狀態(tài)     private EStatus curState;     //直接將規(guī)劃器包含進(jìn)來(lái),方便重新規(guī)劃     privatereadonly HTNPlanner planner;     //當(dāng)前執(zhí)行的原子任務(wù)     private PrimitiveTask curTask;     //標(biāo)記「原子任務(wù)列表是否還有元素、能夠繼續(xù)」     privatebool canContinue;     public HTNPlanRunner(HTNPlanner planner)     {         this.planner = planner;         curState = EStatus.Failure;     }     public void RunPlan()     {         //如果當(dāng)前運(yùn)行狀態(tài)是失?。ㄒ婚_(kāi)始默認(rèn)失?。?        if(curState == EStatus.Failure)         {             //就規(guī)劃一次             planner.Plan();         }         //如果當(dāng)前運(yùn)行狀態(tài)是成功,就表示當(dāng)前任務(wù)完成了         if(curState == EStatus.Success)         {             //讓當(dāng)前原子任務(wù)造成影響             curTask.Effect();         }         /*如果當(dāng)前狀態(tài)不是「正在執(zhí)行」,就取出新一個(gè)原子任務(wù)作為當(dāng)前任務(wù)         無(wú)論失敗還是成功,都要這么做。因?yàn)槿绻鞘?,肯定在代碼運(yùn)行到這         之前,已經(jīng)進(jìn)行了一次規(guī)劃,理應(yīng)獲取新規(guī)劃出的任務(wù)來(lái)運(yùn)行;如果是因         為成功,那也要取出新任務(wù)來(lái)運(yùn)行*/         if(curState != EStatus.Running)         {             //用TryPop的返回結(jié)果判斷規(guī)劃器的FinalTasks是否為空             canContinue = planner.FinalTasks.TryPop(out curTask);         }         /*如果canContinue為false,那curTask會(huì)為null也視作失?。ㄆ鋵?shí)應(yīng)該是「全部         完成」,但全部完成和失敗是一樣的,都要重新規(guī)劃)。所以只有當(dāng)canContinue && curTask.MetCondition()都滿足時(shí),才讀取當(dāng)前原子任務(wù)的運(yùn)行狀態(tài),否則就失敗。*/         curState = canContinue && curTask.MetCondition() ? curTask.Operator() : EStatus.Failure;     } }

差不多所有東西都完成了,為了方便使用,我們和上篇寫(xiě)行為樹(shù)時(shí)一樣,也做一個(gè)構(gòu)造器。

8. 構(gòu)造器

構(gòu)造器會(huì)自帶規(guī)劃器和執(zhí)行器,并將任務(wù)的創(chuàng)建打包成函數(shù)。也和上篇行為樹(shù)一樣,用棧的方式描述構(gòu)建過(guò)程,提供一定可視化。

public partialclassHTNPlanBuilder {     private HTNPlanner planner;      private HTNPlanRunner runner;     privatereadonly Stack taskStack;     public HTNPlanBuilder()     {         taskStack = new Stack ();     }     private void AddTask(IBaseTask task)     {         if (planner != null)//當(dāng)前計(jì)劃器不為空         {             //將新任務(wù)作為構(gòu)造棧頂元素的子任務(wù)             taskStack.Peek().AddNextTask(task);         }         else//如果計(jì)劃器為空,意味著新任務(wù)是根任務(wù),進(jìn)行初始化         {             planner = new HTNPlanner(task as CompoundTask);             runner = new HTNPlanRunner(planner);         }         //如果新任務(wù)是原子任務(wù),就不需要進(jìn)棧了,因?yàn)樵尤蝿?wù)不會(huì)有子任務(wù)         if (task isnot PrimitiveTask)         {             taskStack.Push(task);         }     }     //剩下的代碼都很簡(jiǎn)單,我相信能直接看得懂     public void RunPlan()     {         runner.RunPlan();     }     public HTNPlanBuilder Back()     {         taskStack.Pop();         returnthis;     }     public HTNPlanner End()     {         taskStack.Clear();         return planner;     }     public HTNPlanBuilder CompoundTask()     {         var task = new CompoundTask();         AddTask(task);         returnthis;     }     public HTNPlanBuilder Method(System.Func

 condition)     {         var task = new Method(condition);         AddTask(task);         returnthis;     } }

我還是來(lái)簡(jiǎn)單畫(huà)圖,示意一下構(gòu)建棧的運(yùn)作過(guò)程吧:

  • 加入一個(gè)復(fù)合節(jié)點(diǎn)0后:

  • 往這個(gè)復(fù)0加一個(gè)方法作為一個(gè)子任務(wù):

  • 如果要向復(fù)0再加一個(gè)方法,就要調(diào)用Back函數(shù),再添加:

總之,用Back調(diào)整棧頂?shù)脑兀覀兛梢宰杂傻乜刂菩氯蝿?wù)作為誰(shuí)的子任務(wù)。而且通過(guò)縮進(jìn)可以較直觀的看到HTN的整個(gè)結(jié)構(gòu),例如下面這樣:

//節(jié)選自我某個(gè)小游戲里的一個(gè)小怪的行動(dòng) protected override void Start() {     base.Start();     trigger = Para.HeathValue * 0.5f;     hTN.CompoundTask()             .Method(() => isHurt)                 .Enemy_Hurt(this)                 .Enemy_Die(this)                 .Back()             .Method(() => curHp <= trigger)                 .Enemy_Combo(this, 3)                 .Enemy_Rest(this, "victory")                 .Back()             .Method(() => HTNWorld.GetWorldState

 ("PlayerHp") > 0)                 .Enemy_Check(this)                 .Enemy_Track(this, PlayerTrans)                 .Enemy_Atk(this)                 .Back()             .Method(() => true)                 .Enemy_Idle(this, 3f)             .End(); }

上述中的Enemy_Check、Enemy_Atk都是實(shí)際開(kāi)發(fā)實(shí)現(xiàn)的具體原子行為。現(xiàn)在再來(lái)看,發(fā)現(xiàn)還是有問(wèn)題的,HTN擅長(zhǎng)規(guī)劃,其實(shí)并不擅長(zhǎng)時(shí)時(shí)決策,所以在實(shí)際開(kāi)發(fā)時(shí),建議與有限狀態(tài)機(jī)結(jié)合。將受傷、死亡這類(lèi)需要時(shí)時(shí)反饋的事交給狀態(tài)機(jī),HTN本身也可以放進(jìn)一個(gè)狀態(tài),來(lái)進(jìn)行復(fù)雜行為。而不是像我這樣,將受傷、死亡也當(dāng)成原子任務(wù),因?yàn)檫@樣做就要你為各個(gè)行為設(shè)計(jì)受傷中斷,代碼就會(huì)比較繁冗。

“狀態(tài)機(jī)+其它”的復(fù)合決策模型并不罕見(jiàn),GOAP也經(jīng)常以這種形式出現(xiàn)。

最后分享一些設(shè)計(jì)原子任務(wù)的心得:

1. 如果一個(gè)原子任務(wù)有一定的運(yùn)行過(guò)程,可以用一個(gè)bool值在Operator函數(shù)內(nèi)部判斷是否完成了動(dòng)作。

2. 因?yàn)槲覀兊氖澜鐮顟B(tài)是用字符串來(lái)讀取的,如果我們想獲取某個(gè)士兵的血量該怎么辦?有很多士兵在,該如何區(qū)分?可以用Unity的GetInstanceID()獲取唯一的ID+“血量”,組合成字符串來(lái)區(qū)分,其它類(lèi)似情況同理。例如:

HTNWorld.AddState(GetInstanceID() + "currentHp", () => currentHp, (v) => currentHp = (float)v); HTNWorld.AddState(GetInstanceID() + "IsHurt", () => isHurt, (v) => { isHurt = (bool)v; }); HTNWorld.AddState(GetInstanceID() + "IsDie", () => curHp <= 0, (v) => { });

其實(shí)真正要了解HTN還是應(yīng)當(dāng)自己上手使用,鄙人也只是結(jié)合個(gè)人的學(xué)習(xí)和使用心得寫(xiě)出了這篇文章。

參考

[1] 項(xiàng)目的代碼:

https://www.alipan.com/s/FbjejATyabd

[2] gitee倉(cāng)庫(kù):

https://gitee.com/OwlCat/some-projects-in-tutorials/tree/master/HTN_Game

[3] 視頻:

https://www.bilibili.com/video/BV1iG4y1i78Q/?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=c9a1131d04faacd4a397411965ea21f4

[4] 《Game AI Pro》:

http://www.gameaipro.com/

文末,再次感謝狐王駕虎 的分享, 作者主頁(yè):https://home.cnblogs.com/u/OwlCat, 如果您有任何獨(dú)到的見(jiàn)解或者發(fā)現(xiàn)也歡迎聯(lián)系我們,一起探討。(QQ群: 793972859 )。

近期精彩回顧

【萬(wàn)象更新】

【萬(wàn)象更新】

【厚積薄發(fā)】

【學(xué)堂上新】

特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶(hù)上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關(guān)推薦
熱點(diǎn)推薦
快船兩大首發(fā)傷退!小卡轟23+5,鵜鶘5人14+,難耐快船三鋒線55分

快船兩大首發(fā)傷退!小卡轟23+5,鵜鶘5人14+,難耐快船三鋒線55分

你的籃球頻道
2026-03-02 12:33:21
廣東近兩日持續(xù)降雨,冷空氣補(bǔ)貨!官方稱(chēng)濕度大不等于回南天

廣東近兩日持續(xù)降雨,冷空氣補(bǔ)貨!官方稱(chēng)濕度大不等于回南天

南方都市報(bào)
2026-03-02 12:30:18
美軍瞞不住了!炸航母基地,炸11億美元雷達(dá),幾十萬(wàn)噸燃油被點(diǎn)燃

美軍瞞不住了!炸航母基地,炸11億美元雷達(dá),幾十萬(wàn)噸燃油被點(diǎn)燃

夢(mèng)史
2026-03-01 11:22:23
1200枚導(dǎo)彈從天而降,27座美軍基地被炸,美民眾:特朗普必須下臺(tái)

1200枚導(dǎo)彈從天而降,27座美軍基地被炸,美民眾:特朗普必須下臺(tái)

軍機(jī)Talk
2026-03-02 17:20:13
中國(guó)臺(tái)北球員林秉圣曬與朱俊龍等人合影:我隊(duì)友們太猛了

中國(guó)臺(tái)北球員林秉圣曬與朱俊龍等人合影:我隊(duì)友們太猛了

懂球帝
2026-03-01 20:37:47
航母被瞄準(zhǔn),560名美軍傷亡,后院又失火!有一件事特朗普算漏了

航母被瞄準(zhǔn),560名美軍傷亡,后院又失火!有一件事特朗普算漏了

肖茲探秘說(shuō)
2026-03-02 17:28:24
為什么科學(xué)家說(shuō):如果宇宙中只有人類(lèi),比找到外星人更可怕?

為什么科學(xué)家說(shuō):如果宇宙中只有人類(lèi),比找到外星人更可怕?

觀察宇宙
2026-03-01 20:46:19
比張鎮(zhèn)麟還差!中國(guó)男籃逆轉(zhuǎn)夜最失意之人:失誤被換下僅出場(chǎng)30秒

比張鎮(zhèn)麟還差!中國(guó)男籃逆轉(zhuǎn)夜最失意之人:失誤被換下僅出場(chǎng)30秒

肖茲探秘說(shuō)
2026-03-02 16:19:44
四川5名干部任副縣(市、區(qū))長(zhǎng)

四川5名干部任副縣(市、區(qū))長(zhǎng)

人民資訊
2026-03-02 14:23:16
沸騰!1.1億國(guó)產(chǎn)光刻機(jī)成交,美荷聯(lián)手施壓:自主研發(fā)錯(cuò)了?

沸騰!1.1億國(guó)產(chǎn)光刻機(jī)成交,美荷聯(lián)手施壓:自主研發(fā)錯(cuò)了?

數(shù)碼八叔
2026-03-01 16:43:09
深圳市中心正在悄悄“搬家”?這2個(gè)區(qū)域正在強(qiáng)勢(shì)崛起!

深圳市中心正在悄悄“搬家”?這2個(gè)區(qū)域正在強(qiáng)勢(shì)崛起!

阿離家居
2026-03-02 12:48:09
特朗普下達(dá)死命令,訪華前搞定伊朗,王毅接重要電話,多國(guó)已站隊(duì)

特朗普下達(dá)死命令,訪華前搞定伊朗,王毅接重要電話,多國(guó)已站隊(duì)

小影的娛樂(lè)
2026-03-02 16:55:55
伊朗前王儲(chǔ)巴列維宣布將返回伊朗領(lǐng)導(dǎo)革命

伊朗前王儲(chǔ)巴列維宣布將返回伊朗領(lǐng)導(dǎo)革命

一種觀點(diǎn)
2026-01-19 19:36:11
劉詩(shī)詩(shī)吳奇隆突然官宣!女方罕見(jiàn)發(fā)聲:一切早已注定...

劉詩(shī)詩(shī)吳奇隆突然官宣!女方罕見(jiàn)發(fā)聲:一切早已注定...

草莓解說(shuō)體育
2026-03-01 15:10:45
楊紫真的已經(jīng)瘦到天賦上限了,這也太牛了…

楊紫真的已經(jīng)瘦到天賦上限了,這也太牛了…

手工制作阿殲
2026-02-22 13:25:34
懂球帝X咪咕英超第28輪最佳進(jìn)球:炮彈頭球,謝什科攻克水晶宮

懂球帝X咪咕英超第28輪最佳進(jìn)球:炮彈頭球,謝什科攻克水晶宮

懂球帝
2026-03-02 17:51:07
26年三分榜前三都在黃蜂!波波維奇的徒孫,打造出新版海嘯兄弟

26年三分榜前三都在黃蜂!波波維奇的徒孫,打造出新版海嘯兄弟

你的籃球頻道
2026-03-02 13:45:17
是否需要輕松取勝?瓜帥:我跟球員說(shuō)要進(jìn)5個(gè),但他們沒(méi)聽(tīng)

是否需要輕松取勝?瓜帥:我跟球員說(shuō)要進(jìn)5個(gè),但他們沒(méi)聽(tīng)

懂球帝
2026-03-02 15:23:08
巴基斯坦是個(gè)什么樣的國(guó)家?

巴基斯坦是個(gè)什么樣的國(guó)家?

地緣與沖突
2026-03-02 11:14:05
1973年,毛主席問(wèn)楊振寧:萬(wàn)壽無(wú)疆科學(xué)嗎?楊振寧的回答,讓主席笑了

1973年,毛主席問(wèn)楊振寧:萬(wàn)壽無(wú)疆科學(xué)嗎?楊振寧的回答,讓主席笑了

寄史言志
2026-01-24 17:53:13
2026-03-02 18:16:49
侑虎科技UWA incentive-icons
侑虎科技UWA
游戲/VR性能優(yōu)化平臺(tái)
1552文章數(shù) 986關(guān)注度
往期回顧 全部

游戲要聞

年后首周值得關(guān)注!《魔獸》12.0搶先體驗(yàn)《劍靈》懷舊服重磅更新

頭條要聞

36歲副鎮(zhèn)長(zhǎng)開(kāi)會(huì)暈倒除夕當(dāng)天不幸離世 家中有3個(gè)孩子

頭條要聞

36歲副鎮(zhèn)長(zhǎng)開(kāi)會(huì)暈倒除夕當(dāng)天不幸離世 家中有3個(gè)孩子

體育要聞

“想要我簽名嗎” 梅西逆轉(zhuǎn)后嘲諷對(duì)手主帥

娛樂(lè)要聞

美伊以沖突爆發(fā),多位明星被困中東

財(cái)經(jīng)要聞

金銀大漲 市場(chǎng)仍在評(píng)估沖突會(huì)否長(zhǎng)期化

科技要聞

榮耀發(fā)布機(jī)器人手機(jī)、折疊屏、人形機(jī)器人

汽車(chē)要聞

國(guó)民SUV再添一員 瑞虎7L靜態(tài)體驗(yàn)

態(tài)度原創(chuàng)

房產(chǎn)
游戲
家居
數(shù)碼
健康

房產(chǎn)要聞

配套大升級(jí)!三亞灣,終于迎來(lái)一批頂豪酒店!

停擺800天仍堅(jiān)挺,《坦克世界》為何讓6000萬(wàn)軍迷念念不忘

家居要聞

萬(wàn)物互聯(lián) 享科技福祉

數(shù)碼要聞

AMD發(fā)布Ryzen AI PRO 400系列桌面處理器 針對(duì)AI計(jì)算優(yōu)化

轉(zhuǎn)頭就暈的耳石癥,能開(kāi)車(chē)上班嗎?

無(wú)障礙瀏覽 進(jìn)入關(guān)懷版