<blockquote id="g5mpq"><rt id="g5mpq"></rt></blockquote>

    1. <pre id="g5mpq"></pre>
      <i id="g5mpq"><legend id="g5mpq"></legend></i>
      浪漫女家教主演:黛比地区:台湾 ,日本jiZz,爸爸的种子在线观看,特别的酒店2免费,哇嘎在线,荒野渔夫高清免费观看,新有菜在线免费观看,哇嘎美国
      網易首頁 > 網易號 > 正文 申請入駐

      游戲AI行為決策——GOAP(目標導向型行為規劃)

      0
      分享至


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

      這是侑虎科技第1889篇文章,感謝作者狐王駕虎供稿。歡迎轉發分享,未經作者授權請勿轉載。如果您有任何獨到的見解或者發現也歡迎聯系我們,一起探討。(QQ群:793972859)

      作者主頁:

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

      一、前言

      像先前提到的有限狀態機、行為樹、HTN,它們實現的AI行為,雖說能針對不同環境作出不同反應,但應對方法是寫死了的。有限狀態機終究是在幾個狀態間進行切換、行為樹也是根據提前設計好的樹來搜索……你會發現,游戲AI角色表現出的智能程度,終究與開發者的設計結構有關,就有限狀態機而言,各個狀態如何切換很大程度上就影響了AI智能的表現。

      那有沒有什么決策方法,能夠僅需設計好角色需要的動作,而它自己就能合理決定要選擇哪些動作完成目標呢?這樣的話,角色AI的行為智能程度會更上一層樓,畢竟它不再被寫死的決策結構束縛;我們在添加更多AI行為時,也可以簡單地直接將它放在角色需要的動作集里就好,減少了工作量,不必像行為樹那樣,還要考慮節點間的連接。

      沒錯,GOAP(目標導向型行為規劃)就可以做到。但請注意,并不是說GOAP就比其它決策方法好,后面也會提到它的缺點。選擇何種決策方法還得根據實際項目和自身需求

      PS:本教程需要你具備以下前提知識:

      1. 知道數據結構、堆/優先隊列、棧、圖。

      2. 知道A星尋路的流程,如不了解可看此視頻[1]。

      3. 基本的位運算與位存儲(能做到理解Unity中的Layer和LayerMask的程度就行)。

      二、運行邏輯

      我們來看個簡單的尋路問題:你能找到從A到B的最短路線嗎?注意,道路是單向的。


      聰明如你,這并不難找到:


      現在,加大難度,假設每條道路口都有一個門,紅色表示門關上了,藍色表示門開著,你還能找出可達成的最短A到B路線嗎?


      同樣不難:


      這樣就足夠了,GOAP的規劃就是這么一個過程。只是把每個節點都當成一個狀態,每條道路都當作一個動作、道路長度作為動作代價、路口的門作為動作執行條件,然后像你這樣尋找出一條可以執行的最短「路線」,并記錄下途徑的道路(注意,不是節點),這樣就得到了「動作序列」,再讓AI角色逐一執行。GOAP中的圖會長成下面這樣(只畫出了一條路的樣子,但相信你們能舉一反三的):


      GOAP就是在不斷執行「從現有狀態到目標狀態」,上圖中的「現有狀態」「目標狀態」分別就是「餓」和「飽」。請注意,雖說用了不同形狀,但中間的那些橢圓節點,比如「在上網」,也是和「餓」、「飽」同類別的存在。也就是說「在上網」也可以作為現有狀態或目標狀態。

      可想而知,只要狀態夠多,動作夠多,AI就能做出更復雜的動作。雖說這對其它決策方法也成立,但GOAP不需要我們手動設置各動作、狀態之間的關系,它能自行規劃出要做的一系列動作,更省事且更智能,甚至可以規劃出超出原本設想但又合理的動作序列。

      希望我講明白了它的運作(如果還是感覺有點不懂,可以看看這個視頻[2]),下面一起來實現一個簡單的GOAP進一步了解吧!順帶提一下,在Unity資源商店有免費的GOAP插件,并且做了可視化處理以及多線程優化,各位真的想將GOAP運用于項目的話,更推薦去學習使用成熟的插件。

      三、代碼實現

      本文「世界狀態」的實現參考了GitHub上一C語言版本的GOAP[3]。

      1. 世界狀態

      所謂「世界狀態」其實就是存儲所有的狀態放在一塊兒的合集。而狀態其實還有一個隱藏身份——動作條件。是的,狀態也充當了動作的執行條件,比如之前圖中的條件「有流量」,它其實也是一個狀態。

      世界狀態會因自然因素變化,比如「飽」會隨著時間流逝而變「餓」;也會因角色自身的一些動作導致變化,比如一個角色多運動,也會使「飽」變「餓」。

      問題在于:

      1. GOAP規劃需要時時獲取最新的狀態,才能保證規劃結果的合理性(否則餓暈了還想著運動);

      2. 「世界狀態」中有些狀態是「共享」的,比如之前說的時間,但還有一些狀態是私有的,比如「飽」,是我飽、你飽還是他飽?在一個合集里該如何區分?

      如果你看過上一篇關于HTN的文章的話,你會發現這是如此的眼熟。不過沒看過也沒關系,我們將采取一種新的實現「世界狀態」的方法——原子表示

      PS:在傳統人工智能Agent中,對于環境的表示方式有三種:


      1. 原子表示(Atomic):就是單純描述某個狀態有無,通常每個狀態都只用布爾值(True/False)表示就可以,比如「有流量」。

      2. 要素化表示(Factored):進一步描述狀態的具體數值,這時,狀態可以有不同的類型,可以是字符串、整數、布爾值……在HTN中,我們就是用這種方式實現的。

      3. 結構化表示(Structured):再進一步,每個狀態不但描述具體數值,還存儲于其它數據的連接關系,就像數據結構中「圖」的節點那樣。

      接下來將采用位存儲的方式進行原子表示,因為借助位運算可以方便且高效地實現比較,還省空間。缺點就是有些難懂,所以,我希望你了解如int、long的二進制存儲方式或者Unity中LayerMask,再來看以下內容。當然,這段代碼之后我也會做些舉例說明,這個類還繼承了三個接口,其用意也會在后面解釋:

      using System; using System.Collections.Generic; ///  /// 用位表示的世界狀態 ///  publicclassGoapWorldState : IAStarNode
      
       , IComparable
      
       , IEquatable
      
       {     publicconstint MAXATOMS = 64;//存儲的狀態數上限,由于用long類型存儲,最多就是64(long類型為64位整數)     publiclong Values//世界狀態值     {         get => values;         set => values = value;     }     publiclong DontCare//標記未被使用的位     {         get => dontCare;         set => dontCare = value;     }     publiclong Shared => shared;//判斷共享狀態位     public GoapWorldState Parent { get; set; }     publicfloat SelfCost { get; set; }     publicfloat GCost { get; set; }     publicfloat HCost { get; set; }     publicfloat FCost => GCost + HCost;     privatereadonly Dictionary
      
       namesTable;//存儲各個狀態名字與其在values中的對應位,方便查找狀態     privateint curNamsLen;//存儲的已用狀態的長度     privatelong values;     privatelong dontCare;     privatelong shared;     ///      /// 初始化為空白世界狀態     ///      public GoapWorldState()     {         //賦值0,可將二進制位全置0;賦值-1,可將二進制位全置1         namesTable = new Dictionary
      
       ();         values = 0L; //全置0,意為世界狀態默認為false         dontCare = -1L; //全置1,意為世界狀態的位全沒有被使用         shared = -1L; //將shard的位全置1         curNamsLen = 0;     }     ///      /// 基于某世界狀態的進一步創建,相當于復制狀態設置但清空值     ///      public GoapWorldState(GoapWorldState worldState)     {         namesTable = new Dictionary
      
       (worldState.namesTable);//復制狀態名稱與位的分配         values = 0L;         dontCare = -1L;         curNamsLen = worldState.curNamsLen;//同樣復制已使用的位長度         shared = worldState.shared;//保留狀態共享性的信息     }     ///      /// 根據狀態名,修改單個狀態的值     ///      /// 狀態名     /// 狀態值     /// 設置狀態是否為共享     ///  修改成功與否     public bool SetAtomValue(string atomName, bool value = false, bool isShared = false)     {         var pos = GetIdxOfAtomName(atomName);//獲取狀態對應的位         if (pos == -1) returnfalse;//如果不存在該狀態,就返回false         //將該位 置為指定value         var mask = 1L << pos;         values = value ? (values | mask) : (values & ~mask);         dontCare &= ~mask;//標記該位已被使用         if (!isShared)//如果該狀態不共享,則修改共享位信息         {             shared &= ~mask;         }         returntrue;//設置成功,返回true     }     public void Clear()     {         values = 0L;         namesTable.Clear();         curNamsLen = 0;         dontCare = -1L;     }     ///      /// 通過狀態名獲取單個狀態在Values中的位,如果沒包含會嘗試添加     ///      /// 狀態名     ///  狀態所在位          private int GetIdxOfAtomName(string atomName)     {         if(namesTable.TryGetValue(atomName, outint idx))         {             return idx;         }         if(curNamsLen < MAXATOMS)         {             namesTable.Add(atomName, curNamsLen);             return curNamsLen++;         }         return-1;     }     //——————————三個接口需要實現的函數——————————     public float GetDistance(GoapWorldState otherNode)     {     }     public List   GetSuccessors(object nodeMap)     {     }     public int CompareTo(GoapWorldState other)     {     }     public bool Equals(GoapWorldState other)     {     }     public override int GetHashCode()     {     } }
      
      
      
      
      
      

      我們以添加兩個狀態為例,相信看了這個,你會更容易理解相關函數的內容。雖說總共有64位世界狀態,但這里只看4位:


      將世界狀態分為「私有」和「共享」,我們就可以讓角色更新「私有」部分,而全局系統更新「共享」部分。當需要角色規劃時,我們就用位運算將該角色的「私有」與世界的「共享」進行整合,得到對于這個角色而言的當前世界狀態。這樣對于不同角色,它們就能得到對各自的而言的世界狀態啦!

      如果去除注釋,這個類的內容其實并不多,在使用時幾乎只要用到SetAtomValue函數,像這樣:

      worldState = new GoapWorldState(); worldState.SetAtomValue("血量健康", true); worldState.SetAtomValue("大半夜", false, true);


      接下來就是那三個接口了,首先是IAStarNode ,前文稍提過:「世界狀態」是圖中的結點,「動作」都是圖中的邊,這是我用以輔助「泛用A星搜索器」的結點接口,本文就不贅述了,只要知道:繼承了這個類,都可以作為A星搜索中的結點,從而參與搜索。完整代碼如下:

      using System.Collections.Generic; publicinterfaceIAStarNode
      
        whereT : IAStarNode
      
       {     public T Parent { get; set; }//父節點,通過泛型使它的類型與具體類一致     publicfloat SelfCost { get; set; }//自身單步花費代價     publicfloat GCost { get; set; }//記錄g(n),距初始狀態的代價     publicfloat HCost { get; set; }//記錄h(n),距目標狀態的代價     publicfloat FCost { get; }//記錄f(n),總評估代價     ///      /// 獲取與指定節點的預測代價     ///      public float GetDistance(T otherNode);     ///      /// 獲取后繼(鄰居)節點     ///      /// 尋路所在的地圖,類型看具體情況轉換,     /// 故用object類型     ///  后繼節點列表     public List   GetSuccessors(object nodeMap);     /* IComparable實現的CompareTo函數,主要用于優先隊列的比較;         一般比較可用以下函數     public int CompareTo(AStarNode other)     {         var res = (int)(FCost - other.FCost);         if(res == 0)             res = (int)(HCost - other.HCost);         return res;     }*/     /* IEquatable實現的Equals函數,可以自定義HashSet和Dictionary的Contains判斷依據(但同樣要重寫GetHashCode)        以及在尋路時用于比對某點是否為終點,可以根據類的特點自行繼承 */ }
      
      

      這段代碼的注釋也說明了另外兩個接口的用意。

      2. 動作

      我們之前說過,動作包含一個「前提條件」,其實和HTN一樣,它還包含一個「行為影響」,相當于之前圖中道路指向的橢圓表示的狀態。它們也都是世界狀態,注意是世界狀態,而不是單個狀態!

      為什么不設置成單個?首先,「前提條件」和「行為影響」本身就可能是多個狀態組合成的,用單個不合適;其次,將它們也設置成世界狀態(64位的long類型),方便進行統一處理與位運算。Unity中的Layer也是這樣的。

      只有當前世界狀態與「前提條件」對應位的值相同時,才算滿足前提條件,這個動作才有被選擇的機會。而動作一旦執行成功,世界狀態就會發送變化,對應位上的值會被賦值為「行為影響」所設置的值。

      ///  /// Goap動作,也是Goap圖中的邊 ///  publicclassGoapAction {     publicint Cost{ get; privateset; } //動作代價,作為AI規劃的依據     public GoapWorldState Precondition => precondition;     public GoapWorldState Effect => effect;     privatereadonly GoapWorldState precondition; //動作得以執行的前提條件     privatereadonly GoapWorldState effect; //動作成功執行后帶來的影響,體現在對世界狀態的改變     ///      /// 根據給定世界狀態樣式創建「前提條件」和「行為影響」,     /// 這為了讓它們的位與世界狀態保持一致,方便進行位運算     ///      /// 作為基準的世界狀態     /// 動作代價     public GoapAction(GoapWorldState baseState, int cost = 1)     {         Cost = cost;         precondition = new GoapWorldState(baseState);         effect = new GoapWorldState(baseState);     }     ///      /// 判斷是否滿足動作執行的前提條件     ///      /// 當前世界狀態     ///  是否滿足前提     public bool MetCondition(GoapWorldState worldState)     {         var care = ~precondition.DontCare;         return (precondition.Values & care) == (worldState.Values & care);     }     //---------------------------------------------------------------     ///      /// 判斷世界狀態是否可由執行影響導致     ///      /// 當前世界狀態     ///  是否能導致     public bool MetEffect(GoapWorldState worldState)     {         var care = ~effect.DontCare;         return (effect.Values & care) == (worldState.Values & care);     }     //----------------------------------------------------------------     ///      /// 動作實際執行成功的影響     ///      /// 實際世界狀態     public void Effect_OnRun(GoapWorldState worldState)     {         worldState.Values = ((worldState.Values & effect.DontCare) | (effect.Values & ~effect.DontCare));     }     ///      /// 設置動作前提條件,利用元組,方便一次性設置多個     ///      public GoapAction SetPrecontidion(params (string, bool)[] atomName)     {         foreach(var atom in atomName)          {             precondition.SetAtomValue(atom.Item1, atom.Item2);         }         returnthis;     }     ///      /// 設置動作影響     ///      public GoapAction SetEffect(params (string, bool)[] atomName)     {         foreach (var atom in atomName)         {             effect.SetAtomValue(atom.Item1, atom.Item2);         }         returnthis;     }     public void Clear()     {         precondition.Clear();         effect.Clear();     } }

      你可能發現了這個動作類的奇怪之處——它沒有像OnRunning或OnUpdate之類的動作執行函數,這樣一來要如何執行動作?是的,這個類主要是用來充當圖的邊,來連接各個狀態,它會作為 字典中的值,并于一個動作名字符串綁定。我們會通過動作名,再查找另一個同樣以動作名為鍵、但值為事件的字典,找到對應的事件,這個事件才是真正運行的動作函數。

      這樣豈不多此一舉?其實這是為了提高GOAP圖的重用性。如果GOAP中的道路并不是真正的動作函數,而是用了動作名來標記。那么我們可以為多個角色設計同一種動作,但不同的表現。比如「攻擊」動作,在弓箭手中就是射擊函數,槍手中就是開火函數……這樣一來,即便不同角色都可以使用同一張GOAP圖,不用重復創建(除非有特殊需求)。

      這樣是GOAP的一般做法,只用少數GOAP圖,而不同角色可以共同使用一張GOAP圖來進行互不干擾的規劃。這可以省很多代碼量,試想在有限狀態機中,不做特殊處理你都無法讓不同敵人共用「攻擊」狀態,就得不斷寫大同小異的代碼。GOAP的這種將結構與邏輯分離的做法,就可以很方便地復用結構或進行定制化設計,也是其優勢之一。

      PS:GOAP圖也得用「圖」這一數據結果存儲,而這種數據結構在C# 中是沒有提供的,得自己實現,這里我給個簡單的,方便后續其他功能(如果你有自己的一套,也可以用自己的,只是后續文章中相應的函數要進行替換):

      public classMyGraph
      
       {     publicreadonly HashSet NodeSet; //節點列表     publicreadonly Dictionary > NeighborList; //鄰居列表     publicreadonly Dictionary<(TNode, TNode), List > EdgeList; //邊列表     public MyGraph()     {         NodeSet = new HashSet ();         NeighborList = new Dictionary >();         EdgeList = new Dictionary<(TNode, TNode), List >();     }     ///      /// 尋找指定節點     ///      ///  找到的節點,沒找到時返回null     public TNode FindNode(TNode node)     {         NodeSet.TryGetValue(node, out TNode res);         return res;     }     ///      /// 尋找指點起、終點之間直接連接的所有邊     ///      /// 起點     /// 終點     ///  找到的邊,沒找到時返回null     public List   FindEdge(TNode source, TNode target)     {         var s = FindNode(source);         var t = FindNode(target);         if (s != null && t != null)         {             var nodePairs = (s, t);             if (EdgeList.ContainsKey(nodePairs))             {                 return EdgeList[nodePairs];             }         }         returnnull;     }     ///      /// 添加節點,用HashSet,包含重復檢測     ///      public bool AddNode(TNode node)     {         return NodeSet.Add(node);     }     ///      /// (前提是邊兩端結點已添加進圖)添加指定邊,含空節點判斷、重復添加判斷     ///      /// 邊起點     /// 邊終點     /// 指定邊     ///  添加成功與否     public bool AddEdge(TNode source, TNode target, TEdge edge)     {         var s = FindNode(source);         var t = FindNode(target);         if (s == null || t == null)             returnfalse;         var nodePairs = (s, t);         if(!EdgeList.ContainsKey(nodePairs))         {             EdgeList.Add(nodePairs, new List ());         }         var allEdges = EdgeList[nodePairs];         if(!allEdges.Contains(edge))         {             allEdges.Add(edge);             if(!NeighborList.ContainsKey(source))             {                 NeighborList.Add(source, new List ());             }             NeighborList[source].Add(target);             returntrue;         }         returnfalse;     }     ///      /// 移除指定節點     ///      ///  移除成功與否     public bool RemoveNode(TNode node)     {         return NodeSet.Remove(node);     }     ///      /// 移除指定起、終點的指定邊     ///      /// 邊起點     /// 邊終點     /// 指定邊     ///  移除成功與否     public bool RemoveEdge(TNode source, TNode target, TEdge edge)     {         var allEdges = FindEdge(source, target);         return allEdges != null && allEdges.Remove(edge);     }     ///      /// 移除指定起、終點的所有邊     ///      /// 邊起點     /// 邊終點     ///  移除成功與否     public bool RemoveEdgeList(TNode source, TNode target)     {         return EdgeList.Remove((source, target));     }     ///      /// 獲取指定節點可抵達的所有鄰居節點     ///      public List   GetNeighbor(TNode node)     {         NeighborList.TryGetValue(node, out List res);         return res;     }     ///      /// 獲取指定節點所延伸出的所有邊     ///      public List   GetConnectedEdge(TNode node)     {         var resEdge = new List ();         var neighbor = GetNeighbor(node);         for(int i = 0; i < neighbor.Count; ++i)         {             var curEdgeList = EdgeList[(node, neighbor[i])];             for(int j = 0; j < curEdgeList.Count; ++j)             {                 resEdge.Add(curEdgeList[j]);             }         }         return resEdge;     } }
      

      3. A星節點

      接下來要實現的就是那三個接口所需的函數了,這三個接口其實都是為了方便尋找「路徑」,GOAP會采用啟發式搜索,就像A星尋路所用的那樣。所謂「啟發式搜索」就是有按照一定「啟發值」進行的搜索,它的反面就是「盲目搜索」,如深度優先搜索、廣度優先搜索。啟發式搜索需要設計「啟發函數」來計算「啟發值」。

      在A星尋路中,我們通過計算「當前位置離起點的距離 + 當前位置離終點的距離」做為啟發值來尋找最短路徑;類似的,在我們實現的這個GOAP中,我們會通過計算「起點狀態至當前狀態累計的動作代價+ 當前狀態與目標狀態的相關度」作為啟發值。

      累計代價,也相當于與起始狀態的「距離」;與目標狀態的相關度,在世界狀態類中已經說明了,就是比較當前狀態與目標狀態的有效位的值有多少是相同的,通常相同的越多就越接近。當然,思路不唯一,可以搜索《數據挖掘》相關的文章,了解更多關于數據相關度的計算。

      PS:在尋路時,常需要選取已探索過的節點中具有最小啟發值的節點。用遍歷倒也能做到,但總歸效率不高,故可以用「堆」,也就是「優先隊列」

      //堆屬于常用數據結構中的一種,我默認大家都會了,原理就不加以注釋說明了 publicclassMyHeap
      
        whereT : IComparable
      
       {     publicint CurLength {get; privateset;}     publicreadonlyint capacity;     publicbool IsFull => CurLength == capacity;     publicbool IsEmpty => CurLength == 0;     public T Peak => heapArr[0];     privatereadonlybool isReverse;     privatereadonly T[] heapArr;     privatereadonly Dictionary int> idxTable; //記錄結點在數組中的位置,方便查找     public MyHeap(int size, bool isReverse = false)     {         CurLength = 0;         capacity = size;         heapArr = new T[size];         idxTable = new Dictionary int>();         this.isReverse = isReverse;     }     public void Push(T value)     {         if(!IsFull)         {             if (idxTable.ContainsKey(value))                 idxTable[value] = CurLength;             else                 idxTable.Add(value, CurLength);             heapArr[CurLength] = value;             Swim(CurLength++);         }     }     public void Pop()     {         if(!IsEmpty)         {             idxTable[heapArr[0]] = -1;             heapArr[0] = heapArr[--CurLength];             idxTable[heapArr[0]] = 0;             Sink(0);         }     }     public bool Contains(T value)     {         return idxTable.ContainsKey(value) && idxTable[value] > -1;     }     public T Find(T value)     {         return Contains(value) ? heapArr[idxTable[value]] : default;     }     public void Clear()     {         idxTable.Clear();         CurLength = 0;     }     private void Swim(int index)     {         int father;         while(index > 0)         {             father = (index - 1) / 2;             if(IsBetter(heapArr[index], heapArr[father]))             {                 SwapValueByIndex(father, index);                 index = father;             }             elsereturn;         }     }     private void Sink(int index)     {         int best, left = index * 2 + 1, right;         while(left < CurLength)         {             right = left + 1;             best = right < CurLength && IsBetter(heapArr[right], heapArr[left]) ? right : left;             if(IsBetter(heapArr[best], heapArr[index]))             {                 SwapValueByIndex(best, index);                 index = best;                 left = index * 2 + 1;             }             elsereturn;         }     }     private void SwapValueByIndex(int i, int j)     {         (heapArr[j], heapArr[i]) = (heapArr[i], heapArr[j]);         idxTable[heapArr[i]] = i;         idxTable[heapArr[j]] = j;     }     private bool IsBetter(T v1, T v2)     {         return isReverse ^ v1.CompareTo(v2) < 0;     } }
      
      

      三個接口所需的函數實現如下:

      ///  /// 用位表示的世界狀態 ///  publicclassGoapWorldState : IAStarNode
      
       , IComparable
      
       , IEquatable
      
       {     ……     ///      /// 計算該世界狀態與指定世界狀態的差異度     ///      public float GetDistance(GoapWorldState otherNode)     {         var care = otherNode.dontCare ^ -1L;         var diff = (values & care) ^ (otherNode.values & care);         int dist = 0; //統計有多少位是不同的,以表示差異度         for (int i = 0; i < MAXATOMS;++i)         {             /*diff的位不為1,則表示不同*/             if ((diff & (1L << i)) != 0)                 ++dist;  // 差異越多,距離越大         }         return dist;     }     public List   GetSuccessors(object nodeMap)     {         var goapActionSet = nodeMap as GoapActionSet;         var actionMap = goapActionSet.actionGraph;         var res = actionMap.GetNeighbor(this);         //根據找到的動作,對抵達下個結點的代價進行計算         for(int i = 0; i < res.Count; ++i)         {             res[i].SelfCost = goapActionSet.actionSet[actionMap.FindEdge(this, res[i])[0]].Cost;         }         return res;     }     public int CompareTo(GoapWorldState other)     {         var res = (int)(FCost - other.FCost);         if(res == 0)             res = (int)(HCost - other.HCost);         return res;     }     public bool Equals(GoapWorldState other)     {         /*后文提及的所使用的A星搜索器中,總是「動作的條件」對比「當前的世界狀態」,即currentNode.Equals(target)         如「動作的條件」:餓-true,而「當前的世界狀態」:餓-true,累-true,困-true;顯然此時世界狀態應當滿足條件         這樣可以避免當前世界狀態過于“包容”卻被誤判不滿足*/         return (values & ~dontCare) == (other.values & ~dontCare);     }     public override int GetHashCode()     {         return HashCode.Combine(values & ~dontCare, dontCare);     } }
      
      
      

      4. 動作集

      照理說,動作集不過是動作的合集,單獨將它也制成一個類,是為了方便「動作序列」規劃,主要體現在GetPossibleTrans函數,根據傳入的節點的世界狀態,在合集中遍歷出「前提條件」滿足的動作:

      using System.Collections.Generic; publicclassGoapActionSet {     public MyGraph string> actionGraph; // 動作與狀態構成的圖     privatereadonly Dictionary
      
       actionSet;     public GoapActionSet()     {         actionGraph = new MyGraph string>();         actionSet = new Dictionary
      
       ();     }     public GoapAction this[string idx]     {         get => actionSet[idx];     }     ///      /// 添加動作至動作集合中     ///      /// 動作名     /// 對應動作     ///  動作集,方便連續添加     public GoapActionSet AddAction(string actionName, GoapAction newAction)     {         actionSet.Add(actionName, newAction);         actionGraph.AddNode(newAction.Effect);         actionGraph.AddNode(newAction.Precondition);         actionGraph.AddEdge(newAction.Effect, newAction.Precondition, actionName);         returnthis;     }     ///      /// 返回兩個狀態轉化的動作名     ///      /// 起點狀態     /// 狀態后的狀態     ///  所需執行動作名     public string GetTransAction(GoapWorldState from, GoapWorldState to)     {         return actionGraph.FindEdge(from, to)[0];     } }
      
      

      5. A星尋路

      一切條件都準備好了,現在實現下用來「尋路」的類。首先,我們會進行反向搜索,意思是說,我們不會「起始狀態-->目標狀態」,而是「目標狀態-->起始狀態」,如果成功找到,就將得到的動作序列逆向執行。

      為什么這么麻煩?其實恰恰相反,這還是一種簡化。如果真的「起始狀態-->目標狀態」,未必最終會找到目標狀態(因為有可能能抵達的動作暫時條件不滿足);但反向搜索,必定會包含目標狀態,也一定會找到一條路(因為總會抵達一個當前已經符合的世界狀態,否則就是設計的有問題了),只不過可能不是最短的。

      我們也能接受這種結果,雖說非最優解,但這種不確定因素,也變相讓AI增加了點隨機性,更接近真實決策情況。

      它的整體搜索過程和A星尋路是一樣的,直接用「泛用A星搜索器」即可:

      using System; using System.Collections.Generic; using JufGame.Collections.Generic; ///  /// A星搜索器,T_Node額外實現IComparable用于優先隊列的比較,實現IEquatable用于HashSet和Dictionary等同一性的判斷 ///  ///  搜索的圖類 ///  搜索的節點類 publicclassAStar_Searcher
      
        whereT_Node: IAStarNode
      
       , IComparable
      
       , IEquatable
      
       {     privatereadonly HashSet closeList; //探索集     privatereadonly MyHeap openList; //邊緣集     privatereadonly T_Map nodeMap;//搜索空間(地圖)     public AStar_Searcher(T_Map map, int maxNodeSize = 200)     {         nodeMap = map;         closeList = new HashSet ();         //maxNodeSize用于限制路徑節點的上限,避免陷入無止境搜索的情況         openList = new MyHeap (maxNodeSize);     }     ///      /// 搜索(尋路)     ///      /// 起點     /// 終點     /// 返回生成的路徑     public void FindPath(T_Node start, T_Node target, Stack pathRes )     {         T_Node currentNode;         pathRes.Clear();//清空路徑以備存儲新的路徑         closeList.Clear();         openList.Clear();         openList.PushHeap(start);         while (!openList.IsEmpty)         {             currentNode = openList.Top;//取出邊緣集中最小代價的節點             openList.PopHeap();             closeList.Add(currentNode);//擬定移動到該節點,將其放入探索集             if (currentNode.Equals(target) || openList.IsFull)//如果找到了或圖都搜完了也沒找到時             {                 GenerateFinalPath(start, currentNode, pathRes);//生成路徑并保存到pathRes中                 return;             }             UpdateList(currentNode, target);//更新邊緣集和探索集         }     }     private void GenerateFinalPath(T_Node startNode, T_Node endNode, Stack pathStack )     {         pathStack.Push(endNode);//因為回溯,所以用棧儲存生成的路徑         var tpNode = endNode.Parent;         while (!tpNode.Equals(startNode))         {             pathStack.Push(tpNode);             tpNode = tpNode.Parent;         }         pathStack.Push(startNode);     }     private void UpdateList(T_Node curNode, T_Node endNode)     {         T_Node sucNode;         float tpCost;         bool isNotInOpenList;         var successors = curNode.GetSuccessors(nodeMap);//找出當前節點的后繼節點         if(successors == null)         {             return;         }         for (int i = 0; i < successors.Count; ++i)         {             sucNode = successors[i];             if (closeList.Contains(sucNode))//后繼節點已被探索過就忽略                 continue;             tpCost = curNode.GCost + sucNode.SelfCost;             isNotInOpenList = !openList.Contains(sucNode);             if (isNotInOpenList || tpCost < sucNode.GCost)             {                 sucNode.GCost = tpCost;                 sucNode.HCost = sucNode.GetDistance(endNode);//計算啟發函數估計值                 sucNode.Parent = curNode;//記錄父節點,方便回溯                 if (isNotInOpenList)                 {                     openList.PushHeap(sucNode);                 }             }         }     } }
      
      
      
      

      6. 代理器

      我們最后創建一個「代理器」,它用來整合了上述內容,并統籌運行:

      public enum EStatus {     Failure, Success, Running, Aborted, Invalid } publicclassGoapAgent {     privatereadonly GoapActionSet actionSet; //動作集     publicreadonly GoapWorldState curSelfState; //當前自身狀態,主要是存儲私有狀態     privatereadonly AStar_Searcher goapAStar;     privatereadonly Dictionary
      
       actionFuncs;  //各動作名字對應的動作函數     private Stack
      
       actionPlan;//存儲規劃出的動作序列     private Stack path;     private EStatus curState;//存儲當前動作的執行結果     privatebool canContinue;//是否能夠繼續執行,記錄動作序列全部是否執行完了     private GoapAction curAction;//記錄當前執行的動作     private Func curActionFunc; //記錄當前運行的動作函數     ///      /// 初始化代理器     ///      /// 世界狀態,用來復制成自身狀態     /// 動作集     public GoapAgent(GoapWorldState baseWorldState, GoapActionSet actionSet)     {         curSelfState = new GoapWorldState(baseWorldState)         {             DontCare = baseWorldState.DontCare         };         actionFuncs = new Dictionary
      
       ();         actionPlan = new Stack
      
       ();         this.actionSet = actionSet;         goapAStar = new AStar_Searcher ( this.actionSet);         path = new Stack ();     }     ///      /// 修改自身狀態值     ///      public bool SetAtomValue(string stateName, bool value)     {         return curSelfState.SetAtomValue(stateName, value);     }     ///      /// 為動作名設置對應的動作函數     ///      public void SetActionFunc(string actionName, Func func )     {         actionFuncs.Add(actionName, func);     }     ///      /// 規劃GOAP并運行     ///      ///      ///      public void RunPlan(GoapWorldState curWorldState, GoapWorldState goal)     {         UpdateSelfState(curWorldState);//將自身的私有狀態與世界的共享狀態融合,得到真正的「當前世界狀態」         if (curState == EStatus.Failure) //當前狀態為「失敗」,就表示動作執行失敗         {             //那就重新規劃,找出新的動作序列             actionPlan.Clear();             goapAStar.FindPath(goal, curSelfState, path);             //通過狀態序列得到動作序列             path.TryPop(outvar cur);             while(path.Count != 0)             {                 actionPlan.Push(actionSet.GetTransAction(cur, path.Peek()));                 cur = path.Pop();             }         }         if(curState == EStatus.Success)//執行結果為「成功」,表示動作順利執行完         {             curAction.Effect_OnRun(curWorldState); //動作就會對全局世界狀態造成影響             /*這同樣要更新自身狀態,以防這次改變的是「私有」狀態,全局世界狀態可是只維護「共享」部分。             所以需要自身狀態也記錄下這次影響,即便是共享狀態也沒關系,反正下次會與世界的共享狀態融合*/             curAction.Effect_OnRun(curSelfState);         }         //如果執行結果不是「運行中」,就表示上個動作要么成功了,要么失敗了。都該取出動作序列中新的動作來執行         if (curState != EStatus.Running)         {             canContinue = actionPlan.TryPop(outstring curActionName);             if (canContinue)//如果成功取出動作,就根據動作名,選出對應函數和動作             {                 curActionFunc = actionFuncs[curActionName];                 curAction = actionSet[curActionName];             }         }         curState = canContinue && curAction.MetCondition(curSelfState) ? curActionFunc() : EStatus.Failure;     }     ///      /// 中斷當前Goap執行     ///      public void AbortedGoapCurState()     {         curState = EStatus.Aborted;     }     ///      /// 更新自身狀態的共享部分與當前世界狀態同步     ///      private void UpdateSelfState(GoapWorldState curWorldState)     {         curSelfState.Values = (curWorldState.Values & curWorldState.Shared) | (curSelfState.Values & ~curWorldState.Shared);     } }
      
      
      
      

      注意,代碼里的這個部分,因為A星搜索得到的是結點——也就是狀態,但我們所需要的是鏈接狀態的動作,所以要再「加工」一下:

      goapAStar.FindPath(goal, curSelfState, path); //通過狀態序列得到動作序列 path.TryPop(out var cur); while(path.Count != 0) {     actionPlan.Push(actionSet.GetTransAction(cur, path.Peek()));     cur = path.Pop(); }

      這個類中,RunPlan函數與上一期的HTN中的基本一樣。但我想可能有些人還不大明白UpdateSelfState函數是如何融合自身狀態與世界狀態的,我就簡單舉個例:


      可以看到得到的值,恰好保留了世界狀態的共享部分和自身狀態的私有部分。其實這也并非「恰好」,這樣的位運算理應得到這樣的結果才是。你也可以自己動手嘗試一些值或者用更多位的數來驗證。

      四、尾聲

      GOAP的缺點主要是在設計難度上,它的設計相較FSM、行為樹那些不那么直接,你需要把控好動作的條件和影響對應的狀態,比其它決策方法更費腦子些。因為GOAP沒有顯示的結構,如何定義好一個狀態,使它能在邏輯層面合理地成為一個動作的前提條件,又能成為另一個動作條件的影響結果(比如「有流量」,想想看,將其做為條件可以設計什么動作?作為影響結果又應該怎么設計呢?)是比較考驗開發人員的架構設計的。但毋庸置疑的是,在面對較復雜的AI時,它的代碼量一定是小于FSM、行為樹和HTN的。而且添加和減少動作也不需要進行過多代碼修改,只要將新行動加入到動作集或將欲剔除的動作從動作集中刪去就可以,這也是它沒有顯式結構的好處。

      這里也簡單用上文所學內容做一個簡單的太空射擊飛船敵人的AI:gitee項目[4]

      在EnemyConfig中為敵人指定了GOAP圖并共用,一個非常簡單的敵人邏輯(只是用GOAP實現了而已):當敵人健康時會嘗試瞄準玩家后射擊,當玩家弱勢(無力)時,敵人追擊玩家;當敵人自身不安全時會退避并以較低命中率的方式射擊:

      goal = new GoapWorldState(WarSpaceManager.worldState); goal.SetAtomValue("擊殺玩家", true); actionSet = new GoapActionSet(); actionSet .AddAction("低命中射擊", new GoapAction(WarSpaceManager.worldState, 1)     .SetPrecontidion(("安全區內", true))     .SetEffect(("擊殺玩家", true))) .AddAction("追擊", new GoapAction(WarSpaceManager.worldState, 4)     .SetPrecontidion(("彈藥充足", true), ("玩家無力", true))     .SetEffect(("擊殺玩家", true))) .AddAction("瞄準玩家", new GoapAction(WarSpaceManager.worldState, 3)     .SetPrecontidion(("瞄準就緒", false))     .SetEffect(("瞄準就緒", true))) .AddAction("射擊", new GoapAction(WarSpaceManager.worldState, 2)     .SetPrecontidion(("瞄準就緒", true))     .SetEffect(("擊殺玩家", true))) .AddAction("躲避", new GoapAction(WarSpaceManager.worldState, 1)     .SetPrecontidion(("安全", false))     .SetEffect(("安全區內", true)));

      到這里就結束了。

      參考:

      [1] A星尋路的流程視頻


      https://www.bilibili.com/video/BV147411u7r5?p=1&vd_source=c9a1131d04faacd4a397411965ea21f4

      [2] 視頻


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

      [3] C語言版本的GOAP


      https://github.com/stolk/GPGOAP

      [4] gitee項目

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

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

      近期精彩回顧

      【萬象更新】

      【萬象更新】

      【萬象更新】

      【萬象更新】

      特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

      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.

      相關推薦
      熱點推薦
      盤點NBA現役6大頂級大前鋒,西卡上榜,濃眉哥第2,第1無懸念

      盤點NBA現役6大頂級大前鋒,西卡上榜,濃眉哥第2,第1無懸念

      弄月公子
      2026-07-18 17:46:27
      韓紅基金會向富得流油的醫院捐贈,網友怒批:公益不應是錦上添花

      韓紅基金會向富得流油的醫院捐贈,網友怒批:公益不應是錦上添花

      阿玲詩話
      2026-07-18 09:59:34
      國羽女雙混雙晉級日公決賽!賈一凡張殊賢85分鐘險勝,陳雨菲退賽

      國羽女雙混雙晉級日公決賽!賈一凡張殊賢85分鐘險勝,陳雨菲退賽

      排球黃金眼
      2026-07-18 17:38:30
      億萬家產也沒用!69歲趙本山近況曝光,兒子不爭氣 女兒婚禮上熱

      億萬家產也沒用!69歲趙本山近況曝光,兒子不爭氣 女兒婚禮上熱

      墨印齋
      2026-06-28 13:27:13
      中國腫瘤發病風向“劇變”

      中國腫瘤發病風向“劇變”

      鈦媒體APP
      2026-07-16 16:37:18
      誰接盤了縣城名媛

      誰接盤了縣城名媛

      犀利辣椒
      2026-07-18 06:21:48
      她明明是喬冠華的千金,卻被繼母百般刁難,連保姆都敢給她罪受

      她明明是喬冠華的千金,卻被繼母百般刁難,連保姆都敢給她罪受

      落雪聽梅a
      2026-07-17 19:50:10
      泰國總理抵達上海,專機引人矚目:是全球已罕見的四引擎專機

      泰國總理抵達上海,專機引人矚目:是全球已罕見的四引擎專機

      霽寒飄雪
      2026-07-18 09:53:43
      “擦邊”,一夜終結

      “擦邊”,一夜終結

      中國新聞周刊
      2026-07-17 12:11:28
      世界杯擴軍64個隊,誰是最大的阻礙者?

      世界杯擴軍64個隊,誰是最大的阻礙者?

      有馬體育
      2026-07-18 11:30:39
      38座煉油廠超八成被毀!普京處境進退兩難,俄首次出現內部分歧

      38座煉油廠超八成被毀!普京處境進退兩難,俄首次出現內部分歧

      有牙的兔紙
      2026-07-18 00:18:51
      1985年老山前線,軍部首長們在食堂剛端起碗,一串子彈竟掃了進來

      1985年老山前線,軍部首長們在食堂剛端起碗,一串子彈竟掃了進來

      青途歷史
      2026-07-15 13:42:07
      這回,小粉紅的“愛國戲碼”演砸了

      這回,小粉紅的“愛國戲碼”演砸了

      壹家言
      2026-07-17 14:06:42
      廣西洪水,印度網友犀利嘲諷:領先500年,會造飛行汽車又如何?

      廣西洪水,印度網友犀利嘲諷:領先500年,會造飛行汽車又如何?

      小徐講八卦
      2026-07-18 09:43:38
      349票碾壓!英國新首相還沒進門,先喊出大膽計劃:北方設10號?

      349票碾壓!英國新首相還沒進門,先喊出大膽計劃:北方設10號?

      阿晪美食
      2026-07-18 11:58:09
      陳震搶先曬榮耀Robot Phone真機:4D云臺主攝一鍵啟動!支持360°追蹤

      陳震搶先曬榮耀Robot Phone真機:4D云臺主攝一鍵啟動!支持360°追蹤

      快科技
      2026-07-18 13:53:08
      哈蘭德輸球不輸人!女友深V吊帶很露骨,孩子都兩歲了為何不結婚

      哈蘭德輸球不輸人!女友深V吊帶很露骨,孩子都兩歲了為何不結婚

      胡一舸南游y
      2026-07-18 17:13:15
      大勢已定!再過一年,退休人員必須直面的3個養老真相

      大勢已定!再過一年,退休人員必須直面的3個養老真相

      一口娛樂
      2026-07-18 05:46:18
      特朗普來了,門票瘋了,中場秀也來了!世界杯決賽放飛自我

      特朗普來了,門票瘋了,中場秀也來了!世界杯決賽放飛自我

      黑鷹觀軍事
      2026-07-17 14:47:34
      天災困住阿根廷,便宜了西班牙?決賽還沒踢,天平已經歪了

      天災困住阿根廷,便宜了西班牙?決賽還沒踢,天平已經歪了

      兮兮說故事
      2026-07-18 09:26:02
      2026-07-18 18:15:00
      侑虎科技UWA incentive-icons
      侑虎科技UWA
      游戲/VR性能優化平臺
      1599文章數 987關注度
      往期回顧 全部

      科技要聞

      WAIC2026看什么?這份"不迷路"攻略請收好

      頭條要聞

      男子八戰清華終上岸:曾被女子騙100多萬 為賺錢做保潔

      頭條要聞

      男子八戰清華終上岸:曾被女子騙100多萬 為賺錢做保潔

      體育要聞

      德尚是非典型法國人 14年執教留下豐厚遺產

      娛樂要聞

      大S給具俊曄留遺產是昏頭?實際上她清醒得很

      財經要聞

      股民當街砍博主!韓國股市 終極大屠殺

      汽車要聞

      把中國超跑賣到英國,比亞迪正在被世界看見

      態度原創

      數碼
      教育
      旅游
      公開課
      軍事航空

      數碼要聞

      Audeze推出全球首款主動降噪平板耳機Maxwell 2 ANC

      教育要聞

      日常英語:和粗魯外賣小哥的沖突

      旅游要聞

      第六屆“半馬蘇河”文化旅游節開幕蘇州河歷史文化展同日亮相上歷博

      公開課

      李玫瑾:為什么性格比能力更重要?

      軍事要聞

      美軍連續七晚空襲伊朗

      無障礙瀏覽 進入關懷版 主站蜘蛛池模板: 指尖传出的真挚热情未增减第一季| 欧式少女第16集全集剧情介绍视频| 保罗雪莉美国2| 杀狼花全集在线观看| 女秘书的目的在线观看| 和女部长一起去公司研学日剧叫什么| 大头儿子小头爸爸全集连续播放| 女律师的堕落伦理电影| 吴映洁 纪亚文| 天使情人| 高压监狱2在线看| 神探狄仁杰第四部44| 天海翼短发女教师| 笑功震武林快播| 潮与虎 动漫| 国产免费观看高清电视剧在线| 熊出没过年动画片| 新还珠格格54| 一念天堂电影高清免费观看| 乡情电影| 插曲免费高清完整版| 黑海夺金| 电影买保险的D杯女 | 女忍者忍法帖2| 神汉流氓| 刑警之海外行动全部演员表| 死不张扬的离奇命案| 《倔强退魔师》1-4集在线观看免费里 | 来自妈妈的绣感2国语版叫什么名字| 受戒电影完整版在线观看| 战狼六免费观看完整版高清| 地铁跑酷bug| 信仰电视剧在线观看全集免费| 钢铁铿锵免费观看| 飞刀又见飞刀百度云| 交换上司的秘书免费观看| 扫毒1国语版免费观看| 跟部长出差的日子在线免费| 美国伦理蜜桃5| 《咱们结婚吧》| 巜商务旅行同行戴帽子的电影| 年轻的母亲在线3中文版| 高中生补习班| 山东| 泰国评选10大最美中国女演员| 屠宰场娃娃完整版高清播放| sdde-403| 灵魂符咒| 苏明明电影《暗夜》| 《人生导师》电影在线观看免费| 装台免费版在线观看| 《私人航空》法国电影在线播放视频 | 为了皇帝迅雷下载| 猛虎震地| 吸血鬼日记第四季第五集| 泰剧情丝万缕| 急速飞车电影| 清朝电视剧| 花嫁高柳家1-6集免费观看| 蜜桃熟了| 《法国空姐2020法版》中文版| 刃牙道:无敌武士未删减 | 猫女的诅咒在线观看免费高清版| 怀宁县| 亲爱的爸妈| 《不被疼爱上司的夫人》| 平凡的荣耀全集| 光能使者国语版全集| 我比破军帅| 完美世界剧场版在线观看高清| (女学生的滋味)HD| 电视剧大哥| 美景之屋6电视剧免费观看全集在线高清| 少林搭棚大师| 江河水电视剧剧情介绍| 澡堂老板家的男人们国语| 美发屋特别待遇2| 曹老板的18个秘书续集| 唾弃你的坟墓1复仇电影| 坎贝奇无憾完整版片段| 快播电影u影一族| 坏妻子便利店被欺负后的故事梗概| 日版情定大饭店| 世上只有妈妈好电影免费完整版| 昆明理工大学怎么样| 毕福剑歌曲100首| 女儿复仇满天星未删减版| 天雷一部之春花秋月 电视剧| 木下凛凛子写真在线电影| 太平公主秘史分集介绍| 《年轻女教师3》在线HD| 非缘勿扰 下载| 败犬女王| 美女脱去衣游戏| 和空姐一起的日子剧情| 我的大叔韩剧免费观看| 东北往事之黑道风云20年2部| 《魔力宝贝监狱》| 毛阿敏北京演唱会| 潜罪犯粤语| 尼格买提复盘穿帮过程| 西行纪5季免费观看樱花| 水电工的艳遇中字| 怪医黑杰克| 特邀送货员在线播放| 漂亮的妈妈7中字开头7个字| 奥伊米亚康村| 保卫萨拉热窝| 雪中悍刀行2免费观看完| 法制频道现场直播| 五年级上册习作三| 布拉芙夫人 电影| xxxx美国| 《再去温泉吧!》第一季| 罗莉星克菜尔电影在线观看| 我在精神病院学斩神动漫| 谈判官电视剧免费观看完整版 | 中字HD为丈夫的下属的成就| 澧州大鼓| 姐姐的女朋友5| 广场舞尕撒拉| 全员加速中第3季免费观看完整版| 一路向西在线观看电影完整版| 我非笼鸟未删减完整版在线观看| 银瓶梅电视剧| 美猴王动画片免费观看完整版| 《灭火行动》免费观看| 高清《浮图缘》免费观看全集完整版 | 全村女人村长随便曰| 白毛女选段| 战狼4免费999加拿大版| 斩神之凡尘神域| 金瓶梅电视剧免费观看全集高清下集粤语版 | 激斗战车国语| 一生一世| 曹云金官场斗| 剑破虚空短剧免费观看| 三女炕上交换玩| 画江湖之不良人在线观看| 神鬼战士1满天星版百度网盘资源| 岳母的诱惑 电影| 女超人麦乐迪下载| 巴基斯坦地图中文版| 和前任出差| 黑鸭子电视剧全集| 新来的教练| 最后的维京人全集观看| 三个傻boy| 新金瓶梅高清在线| 罪与罚作者| 西部牛仔满天星免费版| 蘑菇影视妈妈的职业 经典| 换伴| 房探007登录| 明星换脸免费观看方法 | 男女一起愁愁视频免费观看高清完整版| 回家顺子| 高压监狱2高压法版| 马与人电影在线观看免费播放| r片| 格里高利教皇合唱团| 美国版荷尔蒙2| 玫瑰的故事40集免费观看| v酒店在线| 薛平贵王宝钏| 食人鱼事件电视剧全集| 樱桃红了电视剧38集| 谢思埸3米跳板夺冠| 子夜归在线观看| 星空在线观看全集免费高清| 西行纪第五季免费完整版全集| 布拉芙夫人 电影| 囧妈免费播出| 斗罗大陆121| 啄木鸟满天星电影壮志凌云在线观看| 张学良新传| 《美丽小蜜蜂10姐姐叫》中文字幕高清完整版免费在线观看-八戒影视 | 无法呼吸的夏天| 我爱你中国伴奏下载| 小城大事免费观看| 蜜爱傻妃大结局| 电视剧布衣神相| 前复权后复权| 妈妈的男朋友完整版| 高清《月老》电影完整剧情| 妙手电视剧| 何家驹快播| 重症外伤中心韩剧全集免费播放| 危机航线免费观看2024 | 爱丫爱丫在线国语高清电视剧| 惊天魔盗团2资源| 女超人韩国在线观看电影| 新金瓶梅2qvod| 奸嬷者| 金瓶梅1一5集高清视频手机免费 | 奔跑吧兄弟第五季在线观看| 菊色宫先锋影音| 辘轳女人和井全26集| 《需要爸爸播种》英文名怎么写| 霜花店qvod| 色先锋影音| 溏心风暴3| 猪猪侠大结局| 妻子成为临时模特的日本电影| 高清《寂寞的寡妇》电影| 纯纯欲动HD在线动漫| 姐姐的朋友3在完整有限中字第5| 就是要你爱上我2| 我本千金电视剧免费观看| 出包女王第四季樱花动漫在线播放| 百练成仙| 尸家重地 国语| 韩剧《嫌疑人》在线观看 | 同学聚会的目的电影| 青春荷尔蒙3| 亲爱的设计师电视剧免费观看| 高压监狱完整版在线观看完整免费高清原声满天星2023 | 营口市| 金马奖55届完整版高清| 坐公交车失去了第一次| 《疯狂农场3》免费观看中文版| 日本h电影| 战地黄花电视剧| overflow樱花动漫第一季| 蜜桃成时| 特殊治疗6| 春桃的战争下载| 恨君不似江楼月免费观看全集| 相川步美| 巨蜥斗巨蟒| 长相思第60集完整版免费下载| 传奇故事| 极道鲜师电影| 洪兴十三妹| 《高压监狱6》法国完整版| 小欢喜电视剧完整版免费观看| 电视剧离婚女人| 与鬼同眠| 欧美电影私人航空| c字裤内衣秀全透明| 《新上任的秘书| 单恋双城粤语版电视剧在线观看 | 不完美的受害人| 分娩按摩2| 《特殊飞行》电影| 女儿国3在线看完整版| 雪豹38集| 塔河县| 爱的边界国语版| 好好感受本王| 圣母在上动漫| 侏罗纪公园 2013 电影| 澳门人在线看免费观看| 鬼剧院之惊青艳女郎| 锵锵锵锵锵锵锵免费看完整版 | 战狼6大牛免费播放全部时间| 色戒删除的13分钟| 暴躁老妈高清全集免费观看全集 | 青盲电视剧全集50| 电影私人航空完整版播放 | 女超人法国版有字幕吗| 黑客帝国在线看| 《激战丛林》海泽摩尔电影在线观看| 三上翔子| 西班牙掌中之物完整版免费观看| 一路向西2在线观看| 人猿泰山1995| 乔布斯生平简介| 男生女生一愁愁愁电视剧在线观30分钟| 变形金刚7| 《北越暴行》免费观看高清| 勾心电视剧| 扫黑风暴28全集免费播放| 半碗村传奇免费观看| 五道口宏楠怎么死的| 身份互换:真豪门主全集免费 | 韩 r| 默杀 电影| 巜人妻初次按摩2在线播放| 瞳孔中的暗杀者普通话版免费| 1—5集免费普通话版金| 护土与老板在办公室BD中文版| 日本《乳房按摩》在线| 临时天堂中文版| 在爱中疯狂| 贵州卫视瘦身男女| 十二强电影在线观看| 大漠枪神1一45集免费高清观看 | 斯巴达第4季无删完整版| 背着善宰跑电视剧免费观看| 初体验5在线观看整板| 《手机》免费观看全集| 御狐之绊| 最美孝心少年2020颁奖典礼| 战至巅峰第三季2024年播出| 九品芝麻官免费| 明日方舟动漫| 黄子华栋笃笑| 沉香如屑电视剧结局| 女子亲卫队完整免费高清原声满天星 | 喜欢你时风好甜| 凌云壮志(欧美)满天星斯托亚| 高清《沸腾人生》免费观看| 《金花瓶》免费观看完整版电视剧| 肖申克的救赎 电影| 爱我多深| 我们的父辈第一季全3集| 档案2013| 韩国三级男按摩师在线观看| 乡村爱情8西瓜影音| 秘密女搜查官在线观看第6集| 大风歌全集| 木下凛凛子免费观看平台| 黑袍纠察队4| 广灵县| 唐朝诡事录二西行在线播放| 年轻的母亲5电影| 严肃的月光| 蓝宝石之谜| 黄色仓库日韩| 流星花园 泰版观看全集| 空姐5免费观看完整版电视剧| 瓦尼塔斯的手记动漫在线观看| 五月天激情小说| 父母爱情续集免费全集| 全是肉的糙汉文1v1| 澳门人在线看免费观看| 陈梦说就是要赢给所有人看| 别墅轮换3攻略完整版观看| 刘老根1| 李卫当官第二部| 再向虎山行电视剧免费版| 潜入搜救官| 无法无天满天星完整版| 冬之蝉下载| 老表发钱寒电影| 陪读妈妈的交换在线观看| 地爆天星无删减完整版| 昼颜1993版电影完整版观看| 意大利《美丽的丽莎》| 刀剑乱舞花丸第二季| 壮志凌云女版啄木鸟满天星在线观看免费版| 闵度允韩剧中文在线观看| 电影《北越暴行》免费播放| 万物理论迅雷下载| 小小电影免费观看高清版 | 惊变未删减| 镇雄县| 《第一次亲密接触》韩剧| 香港电影免费观看| 爷爷吃我小扇贝| 女王之刃哪集最h| 牙医姊妹在线观看免费高清| 维多利亚一号周楚楚| 伊比利亚三部曲在线观看| 东北二人转正戏| 渔夫荒野台湾版在线观看西瓜视频| 小巷人家电视剧免费版观看| 青青河边草免费看第一集高清| 情事 2002 电影| 步步惊心在线| 说唱梦工厂2024在线观看| 妹妹学着点:打脸绿茶短剧全集| 七根心全集在线播放免费| 越南女兵满天星在线观看| 中医经筋捆扎疗法视频| 《军事不当行为》免费观看法国| 东莞东坑新东泰| 《妻子5》免费高清电视剧| 电车魔女| 正阳门下全集下载| 长乐曲免费观看| 棋逢对手剧情| 新的哥哥2在线观看| 高清《历史的天空》电视剧| 女版大波唐三藏在线观看| 聚会的目的在线播放4| 《售楼小姐》的秘密视频| 银饰 电影| 电影《美容院的特殊待遇》5演员表| 疯狂汉堡| 《产科医生》电视剧40集| 警察锅哥第二季免费观看| 步步惊心下载rmvb| 战狼6大牛高清完整版下载| 天雷一部之春花秋月 电视剧 | 《有翡》电视剧| 非常有喜全集| 失落魔法电影在线观看| 崛起之第三帝国| 眼泪女王| 韩 r| 新水浒传 高清| 平凡之路电视剧| 侵略机器电影| 人工少女2攻略| 鱼夫淫史三级片| 蛇经常吐舌头是因为什么呢| 内黄县| 高清《未婚男女的效率恋爱》电视剧| 村干部忘记关摄像头| 军师联盟2虎啸龙吟在线| 重生1986| 诛烬枭亡 电影免费观看| 绝代双骄在线观看| 平凡的荣耀全集| 私人航空2免费观看全集| 神探狄仁杰第四部| 蛇姬恋电影完整版免费观看 | 法国《少女农场》1976| 《杀死比尔2》电影| 黑白配中文高清免费观看| 仁心俱乐部电视剧免费观看| 爱我就别想太多在线观看电视剧| 电影冷山在线看高清免费播放| 雏妓在线观看完整版| 美国伦理按摩| 父贵荣华短剧全集| 白峰作品在线观看电影免费版 | 平安县| 热带雨林的暴笑生活| 马克思主义基本原理概论试题| 《卖保险的女销售》2| 判处勇者刑全集观看| 插曲的痛30集全免费帅哥| 西野翔ed2k| 男模走秀| 偷情五日| 女生黏糊糊的水哄睡| 和喜玉兰免费观看完整版高清| 最佳爱情国语版全集| 天赐之女电影完整版在线观看免费| 飞轮海mv花絮| 《赤壁》电影| 桃色凶器未删节| 《爱你的基蒂 第三季》| 汶上县| 女人公敌一共多少集| 一夜不了情| 凯.帕克 电影大合集在线观看 | 《部长出差的日子》电影中文字幕 | 电影《完美的售后服务》叶爱来| 迷你特工队x| 爸爸的种子播种2啥时候播| 冰冷少帅荒唐妻全文免费阅读| 马戏小子| 17·3电影免费观看完整版| 爱人 韩版| 三年大片观看完整版免费高清 | 遇见王沥川全集免费观看云播| 男女一起愁愁愁的电影有哪些| 私密肥大| 《爱我几何》原版| 斗罗大陆第149集| 无忧渡40集全免费播放电视剧 | 人间世第十集| 千与千寻免费| 特邀外卖员免费在线观看| 无名女尸| 纯真博物馆全集观看| 小鬼当家中文版电影在线观看| 北大荒电视剧| 《她被搬运工侵犯》在线| 比悲伤更悲伤的事| 《情不自禁》叶玉卿在线| 三年中国国语免费观看中文版下载-百度电影高清电影在线-B032AV | 甄嬛传免费版| 护士贝蒂| 魔力宝贝监狱 电影 完整版| 孩子王电影| 《同居的目的》电影| 成全动漫观看免费高清| 时光代理人第四集| 年轻的小瘦子5| 飞跃疯人院| 电视剧《雪迷宫》32集播放| 少女满天星| 二人转全集正戏| 电视剧潜行狙击| 千金赤子板栗1985日本| 日本阅兵| 全家换着睡大结局| 祝英台与梁山伯的电视剧| 神雕侠侣动画版国语| 女战狼6| 团鬼六电影免费版全集在线观看| 《无法满足》意大利| 语义错误动漫未增删| 炽道电视剧免费观看| 日韩r片| 美国爱情片真爱禁区片段| 饭店也疯狂| 熊出没之过年2部| 韩剧租客女主叫雪莉房东提成| 兰若寺电影| 《羞耻(克罗地亚版) 第三季》| 双生之我妹妹谁敢动短剧免费观看 | 美容院特殊服务5在线在线观看 | 越来越好村晚| 女超人麦乐迪免费浏览观看完整版网站 | 《品味人生》在线观看完整版高清| 国际刑警粤语| rhts 040| 越南电影《性的暴行完整版在线播放 | 一生何求完整版| 乳房影院| 岁月电视剧| 你是主人我是仆人| 绿草地免费| 始作俑者的意思| 唐伯虎vlog免费观看全集1446| 完美世界dj| 无憾坎贝奇电影完整版在线播放免费版 | 电影热辣滚烫今日上映| 公主把奶尖放入侍卫口中| 女篮坎贝奇无憾 在线观看 | 孙杨复出首秀| 私人航空电影法国在线观看 | 向往的生活第六季免费看| 日出免费| 法国《荣誉守则》免费观看| 温州仿真地图| 走向共和哪里可以看| 塔日酒店在线观看高清完整版 | 真破血全集免费看完整版| 莲花楼电视剧40集播放| 微笑妈妈| 韩国电影 在水池里做| 美人图在线观看高清完整版电影| 长安如梦电视剧免费观看完整版| 凯登克罗斯电影在线| 非诚勿扰第15期| xl司令免费全集观看| 三d肉蒲团在线观看| 离人心上电视剧| 朴亚珍《隐身帽子》原版| 鬼吹灯之黄皮子坟电影在线观看| 美剧我要爸爸的种子| 斗罗大陆第二季全集免费观看完整版| 堕落的女律师在线观看| 《兽行日寇》电影完整版免费观看| 雅的拼音| 解放大西南全集| 年经母8| 童年歌曲原唱播放| 高清原始战争| 妻子替丈夫还债给社长当秘书| 电影《顶级特工》| 武汉军运会开幕式直播| 终结者创世纪西瓜| 英雄联盟电视剧下载| 迷人的保姆 电影免费| 妈妈在等你电视剧免费观看全集| 泰剧星月情| 西昌市| 硅胶娃娃实战效果视频| 微笑妈妈电视剧免费完整版| 金银梅1-5普通话免费观看完整版 82板杨敏思版本1-5电视剧在线观看 | 古惑仔6国语高清免费| 坎贝奇无憾全集码名| 再战江湖国语高清| 《三大队》电视剧| 修理工艳遇日本| 电视剧风筝全集观看| 舒淇三级片灵与欲| 甜蜜罚罪无打马赛全集 | 怀9胞胎女子丈夫决定减胎| 美国《睫毛膏3》美国版| 欧美fXXX性| 赤裸的无名指| 兔子先生免费全集在线| 高清极寒潜袭| 龚玥菲新金瓶bt种子| 假面真情短剧免费观看| 分手后程医生哭出鼻涕泡短剧免费 | 韩国电影修理工的艳遇| 动物特工局| 《花间提壶方大厨》| 勇猛的维修工| 糖心在线观看免费高清| 日剧丈夫去上班| 非诚勿扰20170520| 私人助理在线看| 解读西游记| 牙医姐妹赤子板栗电影完整版播放| 床下有人3 电影| 南充属于几线城市| 小凤新婚下集在线播放最| 老爸的爱情电视剧免费观看| 新三国演义剧情介绍| 战狼6高清资源下载| 《贤惠阿姨》动漫免费观看| 巨星嫁到| 美味快递2中文版| 红顶商人胡雪岩高清| 柯坪县| 美国末日之战丧尸电影免费观看| 乐游原电视剧40集全免费观看| 国风按摩院在线观看| 动漫无双| 暗花国语| 原千岁电影全集在线观看| 心动密码全集观看| 这就是生活电视剧| 黑白配视频免费看| 许我耀眼电视剧更新到几集了| 仙逆年番第二部| 拖拉机司机| 狂飙免费观看完整版在线观看| 法国空乘10| 《绝密543》电视剧| 后宫甄嬛传3| 漂亮的保姆3在线播放西瓜| 想要父亲的种子电影在线观看 | 名侦探柯南全集目录| 传闻中的陈芊芊第一集| 灭火女孩在线观看完整免费高清原声高烧可以吃雪糕吗 | 韩国男按摩师吸乳房电影| 兽魂觉醒动漫免费观看高清| 丰满的继牳3小早川怜子bd电影| 天师钟馗94版| 好想做一次大结局在线观看| 夫人的侄子意大利| 夺取电视剧免费观看全集高清| 喜盈门电影免费播放| 李彩谭电影在线| 包青天老版| 炸天小姐在线完整版| 电视剧香格里拉| 摇滚狂花电视剧免费观看| 有菜和枫哥共同演出叫什么| 叶问前传| 长安三万里免费观看完整| 美国电影《要爸爸播种2》的导演是谁 | 女超人麦乐迪2013完整版| 陆贞传奇在线观看| 笔仙惊魂| 空蝉之森酒法子完整版在线观看| 火影忍者高清下载| 儿女英雄传主题曲| 《天字号监狱》完整版| 《年轻朋友的妻子3》| 热辣滚烫在线观看全集电影| 和部长一起出差的新婚妻子电影| 年轻丰满的继牳2| 午间天气预报| 《毒医肥尸》完整版免费观看高清电 | 七星碧玉刀| 回复术士的真人版第9集预告片| 是真爱还是希望泰剧在线观看| 纯真博物馆全集观看| 特派外卖员视频| 釜山行西瓜影音| 黑帮大佬和我的365天2| 战狼8姐弟国语高清免费看| 修罗武神第二季免费观看全集| 无耻第三季在线观看免费完整版| 《女部长通宵的引诱》电影| 后宫甄嬛传全集高清| 扑克 电影| 女律师的坠落未删减版观看| 烟火人家一共多少集| 父母爱情免费观看| 我的莫格利男孩在线观看| 大力女子姜南顺电视剧| 法国最年轻总统呼之欲出 | 水电工的艳遇电影| 疯丫头 第一部| 不死者之王在线观看| 漂亮的廋子3| 搁浅的意思| 3d 肉蒲团| 怀柔区| 战狼6欧式少女免费| 《花与蛇3》| 江湖故事完整版全集| 酒店1-50集免费观看完整版| 疾风知劲草下一句| 绝世武神无弹窗| 俄罗斯美女学院1-8季的剧情简介| 年轻的阿姨5中字在线播放| 公告牌音乐大奖| J情丛林| 历史的天空高清| 电视剧夫妻密码| 漫步云端国语版| 国产免费观看高清电视剧在线观看| 《哪吒1》免费观看完整版| 高清原始战争| 房奴试爱智恩电视剧开头原声| 巨轮粤语| 王牌对王牌第三季| 战狼4免费观看全片| 高清时光留声未删减| 电影报道| 维修工人的培训电影| 《妻子偿还债务》动漫| 新昌县| 天际歌曲原唱| 绀青之拳完整免费观看| 电视剧嫦娥| 隐身蓝色帽子完整版| 色既是空电影| 请回答1988第10集| 命运交响曲电视剧| 洪真英电影完整版免费观看| 安庆二模| 大内密探零零发免费版| 美国空乘免费完整版在线观看| 释放的激情| 熊出没之过年全集| 韩剧美美| 暴躁妹妹国语版bgm视频播放下载| 年经继拇| 跑男第九季| 女监三号完整免费高清原声满天星| 电视剧乡村名流| 迅雷哥手版电影| 女版特种兵满天星美版| 头脑特工队2国语版免费观看高清| 湘北小成| 黛比浪漫女家教第6集剧情简介| 色戒完整未删减版在线观看| 赶尸谭艳| 可疑的美容院在线免费观看| 综合色色网修理工的艳遇| 我被在教室强了好爽在线观看 | 小燕子一家人| 柳州女子喝醉被| 日本电影《姐妹牙医》在线播放| 敢死队1| 杀破狼3免费观看完整国语版| 韩国电影和部长一起出差| 碧蓝幻想动漫| 林妙可歌唱祖国| 朴雅珍《蓝色隐形帽子》 | 双女任务电影在线观看| 镖人动画第一季在线观看| 兰州老肥熟全程露脸| 金瓶梅韩国| 母亲爱上儿子的同学》电影| 恋爱前规则 电影| 田中柠檬| 无颜之月第五集你会回来感谢我的| 战狼6欧式少女所有视频 | 刻舟求剑电影| 电影远山的呼唤| 芷江| 人猿泰山1995成人版意大利在线观看完| 直播:男子双杠决赛| 胖子视界| 黄色仓库视频| 玉观音27集完整版免费| 盾牌第七季| 头号前妻 电视剧| 儿媳妇味道| 蛮好的人生全集| 杭州杀妻案细节| 《沧元图》2在线观看| 电影《雪恋》完整在线观看| 燃烧电视剧| 出手短剧全集免费播放在线观看| 乳房影院| 再见 少年电影| 泰国电影晚娘2| 乐在其中 途我自在| 大学生按摩推油3| 状王宋世杰2粤语| 梦在海这边电视剧| 电影双女任务| 联姻对象是肉食系警官| 张大仙全球首吐| 小欢喜电视剧完整版免费观看| 朋友换麦子3:普通话版| 刘玥演的电影免费观看| 《淋漓尽致3》| 上党梆子打金枝| 义姐是不是良女第二集观看| 脱口秀第五季在线观看免费| 《画皮》丁程鑫版| 莎 劳伦| 特殊家政服务电视剧在线观看| 《大鱼海棠》| 刻在你心底的名字免费观看完整版| 阿瓦人民唱新歌| 苍空电影| 迷魂记免费| 《骄阳伴我》| 晒晒我们家12岁儿子的小鸡| 《玉女心经3之阴阳和合》免费舒淇| 黑道大佬第1季免费观看完整版| 斗罗大陆184集| 稻城县| 满天星父亲的秘书马莉萨卡| 我的丑娘续集| 《伊藤润二惊选集·富江》 | 苹果完整版在线观看| 播放线路1线路2线路3线路4| 美国小蜜桃| 我会好好的电影免费观看 | 捉鬼敢死队女版满天星| 钱作怪国语高清| 电视剧海天之恋| 万界独尊无垢剑武魂小说| 替夫还债电影高清完整版| 《世外》| 薛平贵与王宝钏电视剧下载| 妓房乱动事件| 美味快递2在线观看免费版电视剧| 秋雪漫过冬30集免费观看电视剧| 致命弯道4高清全集| 灰猎犬号电影高清版免费观看| 朋友互换麦子在线观看 | 《分娩按摩》在线观看完整版高清| 爱转角遇到爱全集| 纯真岁月| 名侦探柯南vs鲁邦三世| 美国女按摩师2无删减版电影观看 HD版泰山《激战丛林》完整版免费免费 | 十二金刚| 克罗斯凯登电影合集| 厕所花子vs倔强的驱魔师| 仙逆123集| 青岛一酒店疑闪爆| 牝教师4免费在线观看| 丰乳肥臀电影| 一马换三羊电视剧| 天天向上20090109| 潍坊市| 插曲的痛30集全集免费播放电影日本| 大生意人电视猫| 台湾民众储存物资| 电视剧《三十而立》| 麦乐迪女超人免费在线观看| 痞子英雄2:黎明升起| 定格动画制作教程| 金瓶梅1一5集高清视频手机免费| 凌云壮志满天星2011版| 妻子的秘密韩国电视剧| 迪斯科舞曲| 盒子里的猫| 迷人的小婊子| 锤锤小矮人| runningman全集在线播放| 高清《绝密543》电视剧| 法国电影《军事不当行为》在线观看| 云中玫瑰国语版| 国模小树| 百分之九少年| 被安排和讨厌部长出差韩剧| 性的暴行2小时16分| 男生女生向前冲2024|