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

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

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

0
分享至

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

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

作者主頁(yè):

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

一、前言

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

HTN允許我們把要做的事以高度復(fù)雜的「復(fù)合任務(wù)」來(lái)表示,而不是單單一個(gè)行為。什么意思呢?無(wú)論是有限狀態(tài)機(jī)狀態(tài)的轉(zhuǎn)換,還是行為樹節(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)」,行為樹中是「動(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í)行,就像行為樹的選擇節(jié)點(diǎn)一樣。

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

方法的執(zhí)行,會(huì)逐一判斷組成的「復(fù)合任務(wù)」和「原子任務(wù)」是否滿足條件,只要有一個(gè)不滿足,這個(gè)方法便會(huì)被放棄,它有點(diǎn)像行為樹中的順序節(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)的),還是以「砍樹」為例,想要讓一個(gè)角色去砍樹,他就得知道:哪里有樹、哪里有電鋸、電鋸有多少油……這些做事的前提都可以歸為「世界狀態(tài)」的一員,反過(guò)來(lái)說(shuō),世界狀態(tà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)」造成一定影響,比如開槍會(huì)減少?gòu)椝帞?shù),鋸?fù)陿鋾?huì)減少樹木數(shù)量等等。但要注意,這里的影響就不再是“腦補(bǔ)”的啦,而是真正改變「世界狀態(tài)」的某些值。就像是部隊(duì)制定完計(jì)劃后,就開始正式行動(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)的行為樹,這次所要寫的類不會(huì)太多(除去注釋的話就更少了)。

1. 世界狀態(tài)

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

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

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

對(duì)于問(wèn)題1,我們可以用 的字典來(lái)解決。畢竟C ,Object類是所有數(shù)據(jù)類型的老祖宗。那問(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。分別用值類型為System.Action和System.Func的字典就可以了。

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

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

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

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

 >();         set_WorldState = new Dictionary

 >();     }     //添加一個(gè)狀態(tài),需要傳入狀態(tài)名、讀取函數(shù)和寫入函數(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ò)寫入字典修改的         set_WorldState[key].Invoke(value);     }     //讀取某個(gè)狀態(tài)的值,利用泛型,可以將獲取的object轉(zhuǎn)為指定的類型     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ù)類接口

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

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

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

3. 原子任務(wù)

原子任務(wù)是一個(gè)抽象類,相當(dāng)于行為樹中的動(dòng)作節(jié)點(diǎn),用于開發(fā)者自定義的最小單元任務(wù)。一般就是像「開火」、「奔跑」之類的簡(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)行邏輯,交給具體類實(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ù)和「方法」類似,只不過(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)的是不是方法類,是的話才添加         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;     } }

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

6. 規(guī)劃器

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

public classHTNPlanner {     //最終分解完成的所有原子任務(wù)存放的列表     public Stack FinalTasks {  get; privateset; }     //分解過(guò)程中,用來(lái)緩存被分解出的任務(wù)的棧,因?yàn)轭愋透鳟?,故用IBaseTask類型     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類型                 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)是失?。ㄒ婚_始默認(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;     } }

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

8. 構(gòu)造器

構(gòu)造器會(huì)自帶規(guī)劃器和執(zhí)行器,并將任務(wù)的創(chuàng)建打包成函數(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)單畫圖,示意一下構(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í)際開發(fā)實(shí)現(xiàn)的具體原子行為?,F(xiàn)在再來(lái)看,發(fā)現(xiàn)還是有問(wèn)題的,HTN擅長(zhǎng)規(guī)劃,其實(shí)并不擅長(zhǎng)時(shí)時(shí)決策,所以在實(shí)際開發(fā)時(shí),建議與有限狀態(tài)機(jī)結(jié)合。將受傷、死亡這類需要時(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ù)合決策模型并不罕見,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ū)分,其它類似情況同理。例如:

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í)和使用心得寫出了這篇文章。

參考

[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ú)到的見解或者發(fā)現(xiàn)也歡迎聯(lián)系我們,一起探討。(QQ群: 793972859 )。

近期精彩回顧

【萬(wàn)象更新】

【萬(wàn)象更新】

【厚積薄發(fā)】

【學(xué)堂上新】

特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(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)推薦
山東小伙娶只有8歲智商的新娘,笑的合不攏嘴,網(wǎng)友:賺大了!

山東小伙娶只有8歲智商的新娘,笑的合不攏嘴,網(wǎng)友:賺大了!

觀察鑒娛
2026-02-01 15:02:49
牡丹花下死!不顧央視警告頂風(fēng)作案 與劉濤傳緋聞的楊爍 終究夢(mèng)醒了

牡丹花下死!不顧央視警告頂風(fēng)作案 與劉濤傳緋聞的楊爍 終究夢(mèng)醒了

手工制作阿殲
2026-03-01 18:10:21
成都部分中小學(xué)已公布2026年春假放假時(shí)間

成都部分中小學(xué)已公布2026年春假放假時(shí)間

愛(ài)看頭條
2026-03-01 16:42:03
金龜子一家7口為外孫慶生,王寧看外孫好寵溺,元寶越長(zhǎng)越像爺爺

金龜子一家7口為外孫慶生,王寧看外孫好寵溺,元寶越長(zhǎng)越像爺爺

打小我就醜
2026-03-01 20:35:52
胡春華發(fā)表署名文章

胡春華發(fā)表署名文章

社評(píng)
2025-10-31 10:11:37
福建一市局主要領(lǐng)導(dǎo)調(diào)整(附簡(jiǎn)歷)

福建一市局主要領(lǐng)導(dǎo)調(diào)整(附簡(jiǎn)歷)

金臺(tái)資訊
2026-03-01 20:24:57
張儷的小腳,萌值直接爆表!

張儷的小腳,萌值直接爆表!

情感大頭說(shuō)說(shuō)
2026-03-02 00:20:29
俄軍已經(jīng)動(dòng)了,中國(guó)幫不幫伊朗?中方三句話,沒(méi)一句是美國(guó)想聽的

俄軍已經(jīng)動(dòng)了,中國(guó)幫不幫伊朗?中方三句話,沒(méi)一句是美國(guó)想聽的

凡知
2026-02-27 15:50:11
新加坡大滿貫賽:太遺憾!國(guó)乒男單3:4惜敗,無(wú)緣沖擊男單冠軍

新加坡大滿貫賽:太遺憾!國(guó)乒男單3:4惜敗,無(wú)緣沖擊男單冠軍

國(guó)乒二三事
2026-03-01 11:56:32
這就是公開辱華的后果!取消冠軍頭銜只是開始,職業(yè)生涯也全毀了

這就是公開辱華的后果!取消冠軍頭銜只是開始,職業(yè)生涯也全毀了

阿鳧愛(ài)吐槽
2025-12-17 17:24:39
王一博聊天記錄被扒,與女友國(guó)外隱婚生子,買50萬(wàn)私密部位洗護(hù)液

王一博聊天記錄被扒,與女友國(guó)外隱婚生子,買50萬(wàn)私密部位洗護(hù)液

花哥扒娛樂(lè)
2026-03-01 16:56:10
原來(lái)李莉就是孫濤的老婆,難怪孫濤能成“春晚釘子戶”,每年都上

原來(lái)李莉就是孫濤的老婆,難怪孫濤能成“春晚釘子戶”,每年都上

孤城落日
2026-01-30 22:01:27
大量外國(guó)人涌入中國(guó)!不是來(lái)旅游,而是來(lái)?yè)屨贾袊?guó)廉價(jià)的醫(yī)療資源

大量外國(guó)人涌入中國(guó)!不是來(lái)旅游,而是來(lái)?yè)屨贾袊?guó)廉價(jià)的醫(yī)療資源

趣文說(shuō)娛
2026-03-01 16:15:38
寶可夢(mèng)30周年限定黑白玩偶引發(fā)搶購(gòu)潮,日本多家門店陷入混亂

寶可夢(mèng)30周年限定黑白玩偶引發(fā)搶購(gòu)潮,日本多家門店陷入混亂

IT之家
2026-03-01 08:03:05
打起來(lái)了,中國(guó)賺了

打起來(lái)了,中國(guó)賺了

風(fēng)風(fēng)順
2026-03-02 00:00:04
孫志浩肝癌晚期,將50億資產(chǎn)里的豪宅與股份盡數(shù)轉(zhuǎn)至女兒梧桐妹

孫志浩肝癌晚期,將50億資產(chǎn)里的豪宅與股份盡數(shù)轉(zhuǎn)至女兒梧桐妹

陳意小可愛(ài)
2026-03-01 10:33:40
英國(guó)《衛(wèi)報(bào)》:特朗普的愚蠢之戰(zhàn)

英國(guó)《衛(wèi)報(bào)》:特朗普的愚蠢之戰(zhàn)

魏城看天下
2026-03-01 04:13:43
雷軍直播再提新一代SU7門把手:極端情況下,大小電池同時(shí)斷電,門把手依然保留純機(jī)械解鎖能力

雷軍直播再提新一代SU7門把手:極端情況下,大小電池同時(shí)斷電,門把手依然保留純機(jī)械解鎖能力

時(shí)代財(cái)經(jīng)
2026-02-28 10:46:20
馬刺89-114慘敗尼克斯,揭露了三個(gè)不爭(zhēng)事實(shí)!

馬刺89-114慘敗尼克斯,揭露了三個(gè)不爭(zhēng)事實(shí)!

君子一劍似水流年
2026-03-02 06:10:33
哈妹內(nèi)衣沒(méi)了

哈妹內(nèi)衣沒(méi)了

名人茍或
2026-03-01 06:06:59
2026-03-02 06:44:49
侑虎科技UWA incentive-icons
侑虎科技UWA
游戲/VR性能優(yōu)化平臺(tái)
1552文章數(shù) 986關(guān)注度
往期回顧 全部

游戲要聞

《GTA6》ID已添加PS數(shù)據(jù)庫(kù) 游戲預(yù)購(gòu)將要開啟?

頭條要聞

伊朗多位軍事指揮官確認(rèn)死亡 名單公布

頭條要聞

伊朗多位軍事指揮官確認(rèn)死亡 名單公布

體育要聞

火箭輸給熱火:烏度卡又輸斯波教練

娛樂(lè)要聞

黃景瑜 李雪健坐鎮(zhèn)!38集犯罪大劇來(lái)襲

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

中東局勢(shì)升級(jí) 如何影響A股、黃金和原油

科技要聞

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

汽車要聞

理想汽車2月交付26421輛 歷史累計(jì)交付超159萬(wàn)輛

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

教育
旅游
數(shù)碼
親子
房產(chǎn)

教育要聞

寧夏大學(xué)外國(guó)語(yǔ)學(xué)院揭秘!96.2%高落實(shí)率

旅游要聞

春雨落瘦西湖,梅花一開,才是江南真春天!

數(shù)碼要聞

曝蘋果WWDC 26將推Core AI框架取代Core ML并公布多項(xiàng)AI功能

親子要聞

帶娃看醫(yī)生,聽懂這幾句話少走90%彎路!

房產(chǎn)要聞

濱江九小也來(lái)了!集齊海僑北+哈羅、寰島...江東教育要炸了!

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