AI决策算法 之 GOAP (二)

http://blog.csdn.net/lovethRain/article/details/67634803


GOAP 的主要逻辑:

1.Agent的状态机初始化Idle状态

2.Idel状态根据IGoap提供的数据,通过Planer得到最优路线

3.Agent的状态机转换状态到PerformAction状态

4.PerformAction状态解析路线,执行路线的动作队列

5.如果动作需要到范围内,切换到MoveTo状态移动到目标范围,否则执行动作队列

6.当全部动作执行完毕,告诉IGoap目标达成,转换到Idle状态



接着实现 IGoal,Agent,Action,Planer,FSM


Action

[csharp] view plain copy
  1. namespace MyGoap  
  2. {  
  3.     public abstract class Action : MonoBehaviour  
  4.     {  
  5.         #region 字段  
  6.         private HashSet<KeyValuePair<stringobject>> preconditions; // 先行条件  
  7.         private HashSet<KeyValuePair<stringobject>> effects;       // 造成的影响  
  8.   
  9.         private bool inRange = false;                                // 是否在动作的可行范围内  
  10.   
  11.         public float cost = 1f;                                      // 消耗的成本  
  12.   
  13.         public GameObject target;                                    // 执行动作的目标,可以为空  
  14.         #endregion  
  15.  
  16.         #region 属性  
  17.   
  18.         public bool IsInRange { get { return inRange; } set { inRange = value; } }  
  19.   
  20.   
  21.         public HashSet<KeyValuePair<stringobject>> Preconditions  
  22.         {  
  23.             get  
  24.             {  
  25.                 return preconditions;  
  26.             }  
  27.         }  
  28.   
  29.         public HashSet<KeyValuePair<stringobject>> Effects  
  30.         {  
  31.             get  
  32.             {  
  33.                 return effects;  
  34.             }  
  35.         }  
  36.  
  37.         #endregion  
  38.  
  39.         #region 接口  
  40.   
  41.         /// <summary>  
  42.         /// 构造函数:初始化  
  43.         /// </summary>  
  44.         public Action()  
  45.         {  
  46.             preconditions = new HashSet<KeyValuePair<stringobject>>();  
  47.             effects = new HashSet<KeyValuePair<stringobject>>();  
  48.         }  
  49.   
  50.         /// <summary>  
  51.         /// 基类重置  
  52.         /// </summary>  
  53.         public void DoReset()  
  54.         {  
  55.             inRange = false;  
  56.             target = null;  
  57.             Reset();  
  58.         }  
  59.   
  60.         /// <summary>  
  61.         /// 继承类重置  
  62.         /// </summary>  
  63.         public abstract void Reset();  
  64.   
  65.         /// <summary>  
  66.         /// 是否完成动作  
  67.         /// </summary>  
  68.         /// <returns></returns>  
  69.         public abstract bool IsDone();  
  70.   
  71.         /// <summary>  
  72.         /// 由代理检索动作最优目标,并返回目标是否存在,动作是否可以被执行  
  73.         /// </summary>  
  74.         /// <param name="target"></param>  
  75.         /// <returns></returns>  
  76.         public abstract bool CheckProcedualPrecondition(GameObject agent);  
  77.   
  78.         /// <summary>  
  79.         /// 执行动作  
  80.         /// </summary>  
  81.         /// <param name="agent"></param>  
  82.         /// <returns></returns>  
  83.         public abstract bool Perform(GameObject agent);  
  84.   
  85.         /// <summary>  
  86.         /// 是否需要在范围内才能执行动作  
  87.         /// </summary>  
  88.         /// <returns></returns>  
  89.         public abstract bool RequiresInRange();  
  90.   
  91.         /// <summary>  
  92.         /// 增加先行条件  
  93.         /// </summary>  
  94.         /// <param name="key"></param>  
  95.         /// <param name="value"></param>  
  96.         public void AddPrecondition(string key,object value)  
  97.         {  
  98.             preconditions.Add(new KeyValuePair<stringobject>(key, value));  
  99.         }  
  100.   
  101.         /// <summary>  
  102.         /// 移除先行条件  
  103.         /// </summary>  
  104.         /// <param name="key"></param>  
  105.         public void RemovePrecondition(string key)  
  106.         {  
  107.             KeyValuePair<stringobject> remove = default(KeyValuePair<stringobject>);  
  108.             foreach(var kvp in preconditions)  
  109.             {  
  110.                 if (kvp.Key.Equals(key))  
  111.                     remove = kvp;  
  112.             }  
  113.   
  114.             //如果被赋值了  
  115.             if (!default(KeyValuePair<stringobject>).Equals(remove))  
  116.                 preconditions.Remove(remove);  
  117.   
  118.         }  
  119.   
  120.         /// <summary>  
  121.         /// 增加造成的效果  
  122.         /// </summary>  
  123.         /// <param name="key"></param>  
  124.         /// <param name="value"></param>  
  125.         public void AddEffect(string key, object value)  
  126.         {  
  127.             effects.Add(new KeyValuePair<stringobject>(key, value));  
  128.         }  
  129.   
  130.         /// <summary>  
  131.         /// 移除造成的效果  
  132.         /// </summary>  
  133.         /// <param name="key"></param>  
  134.         public void RemoveEffect(string key)  
  135.         {  
  136.             KeyValuePair<stringobject> remove = default(KeyValuePair<stringobject>);  
  137.             foreach (var kvp in effects)  
  138.             {  
  139.                 if (kvp.Key.Equals(key))  
  140.                     remove = kvp;  
  141.             }  
  142.   
  143.             //如果被赋值了  
  144.             if (!default(KeyValuePair<stringobject>).Equals(remove))  
  145.                 effects.Remove(remove);  
  146.   
  147.         }  
  148.  
  149.         #endregion  
  150.     }  
  151. }  


IGoap

[csharp] view plain copy
  1. namespace MyGoap  
  2. {  
  3.     public interface IGoap   
  4.     {  
  5.   
  6.         /// <summary>  
  7.         /// 获取现在所有状态  
  8.         /// </summary>  
  9.         /// <returns></returns>  
  10.         HashSet<KeyValuePair<stringobject>> GetState();  
  11.   
  12.         /// <summary>  
  13.         /// 创建新的目标状态集合  
  14.         /// </summary>  
  15.         /// <returns></returns>  
  16.         HashSet<KeyValuePair<stringobject>> CreateGoalState();  
  17.   
  18.         /// <summary>  
  19.         /// 没有找到可以完成目标的路线  
  20.         /// </summary>  
  21.         /// <param name="failedGoal"></param>  
  22.         void PlanFailed(HashSet<KeyValuePair<stringobject>> failedGoal);  
  23.   
  24.         /// <summary>  
  25.         /// 找到可以完成目标的一系列动作  
  26.         /// </summary>  
  27.         /// <param name="goal"></param>  
  28.         /// <param name="actions"></param>  
  29.         void PlanFound(HashSet<KeyValuePair<stringobject>> goal, Queue<Action> actions);  
  30.   
  31.         /// <summary>  
  32.         /// 动作全部完成,达成目标  
  33.         /// </summary>  
  34.         void ActionsFinished();  
  35.   
  36.         /// <summary>  
  37.         /// 计划被一个动作打断  
  38.         /// </summary>  
  39.         /// <param name="aborterAction"></param>  
  40.         void PlanAborted(Action aborterAction);  
  41.   
  42.         /// <summary>  
  43.         /// 移动到目标动作位置  
  44.         /// </summary>  
  45.         /// <param name="tagetAction"></param>  
  46.         /// <returns></returns>  
  47.         bool MoveAgent(Action tagetAction);  
  48.   
  49.     }  
  50. }  

Planer

[csharp] view plain copy
  1. namespace MyGoap  
  2. {  
  3.     public class Planer   
  4.     {  
  5.   
  6.         /// <summary>  
  7.         /// 计划出最优路线  
  8.         /// </summary>  
  9.         /// <param name="agent">把代理传进来</param>  
  10.         /// <param name="availableActions">当前可行动作</param>  
  11.         /// <param name="currentState">当前状态</param>  
  12.         /// <param name="goal">目标</param>  
  13.         /// <returns></returns>  
  14.         public Queue<Action> Plan(GameObject agent,HashSet<Action> availableActions,HashSet<KeyValuePair<string,object>> currentState,HashSet<KeyValuePair<string,object>> goal)  
  15.         {  
  16.             foreach (var a in availableActions)  
  17.                 a.DoReset();  
  18.   
  19.             //排除不可行动作  
  20.             HashSet<Action> usableActions = new HashSet<Action>();  
  21.             foreach (var a in availableActions)  
  22.                 if (a.CheckProcedualPrecondition(agent))  
  23.                     usableActions.Add(a);  
  24.   
  25.             List<Node> leaves = new List<Node>();  
  26.   
  27.             //由当前状态开始计算,并把结果添加到路线集合里  
  28.             Node start = new Node(null, 0, currentState, null);  
  29.             bool success = BuildGraph(start, leaves, usableActions, goal);  
  30.   
  31.             if (!success)  
  32.                 return null;  
  33.   
  34.             //得到成本最小的路线  
  35.             Node cheapest = null;  
  36.             foreach(Node leaf in leaves)  
  37.             {  
  38.                 if (cheapest == null)  
  39.                     cheapest = leaf;  
  40.                 else  
  41.                 {  
  42.                     if (leaf.CostNum < cheapest.CostNum)  
  43.                         cheapest = leaf;  
  44.                 }  
  45.             }  
  46.   
  47.             //链表遍历法遍历最后一个节点,并把每一个动作往前插入,因为越后面节点的动作是越后面要执行的  
  48.             List<Action> result = new List<Action>();  
  49.             Node n = cheapest;  
  50.             while(n != null)  
  51.             {  
  52.                 if(n.Action != null)  
  53.                 {  
  54.                     result.Insert(0, n.Action);  
  55.                 }  
  56.                 n = n.Parent;  
  57.             }  
  58.   
  59.             //把链表转换为队列返回回去  
  60.             Queue<Action> queue = new Queue<Action>();  
  61.             foreach(Action a in result)  
  62.             {  
  63.                 queue.Enqueue(a);  
  64.             }  
  65.   
  66.             return queue;  
  67.         }  
  68.   
  69.         /// <summary>  
  70.         /// 策划者计划主要算法  
  71.         /// </summary>  
  72.         /// <param name="parent">父节点</param>  
  73.         /// <param name="leaves">路线集合</param>  
  74.         /// <param name="usableActions">可行动作</param>  
  75.         /// <param name="goal">目标状态</param>  
  76.         /// <returns></returns>  
  77.         private bool BuildGraph(Node parent,List<Node> leaves,HashSet<Action> usableActions,HashSet<KeyValuePair<string,object>> goal)  
  78.         {  
  79.             bool foundOne = false;  
  80.   
  81.             // 遍历所有可行动作  
  82.             foreach(var action in usableActions)  
  83.             {  
  84.                 // 如果当前状态匹配当前动作前置条件,动作执行  
  85.                 if(InState(action.Preconditions,parent.State))  
  86.                 {  
  87.                     //造成效果影响当前状态  
  88.                     HashSet<KeyValuePair<stringobject>> currentState = PopulateState(parent.State, action.Effects);  
  89.   
  90.                     //生成动作完成的节点链,注意成本累加  
  91.                     Node node = new Node(parent, parent.CostNum + action.cost, currentState, action);  
  92.   
  93.                     //如果当前状态存在要完成的目标状态  
  94.                     if(InState(goal,currentState))  
  95.                     {  
  96.                         //增加可行方案路线  
  97.                         leaves.Add(node);  
  98.                         foundOne = true;  
  99.                     }  
  100.                     else  
  101.                     {  
  102.                         //否则该可行动作排除,用其他动作由 该节点 继续搜索接下去的路线  
  103.                         HashSet<Action> subset = ActionSubset(usableActions, action);  
  104.                         bool found = BuildGraph(node, leaves, subset, goal);  
  105.                         if (found)  
  106.                             foundOne = true;  
  107.                     }  
  108.                 }  
  109.             }  
  110.   
  111.             return foundOne;  
  112.   
  113.         }  
  114.  
  115.         #region 帮助方法  
  116.   
  117.         /// <summary>  
  118.         /// 移除目标动作并返回移除后的动作集合  
  119.         /// </summary>  
  120.         /// <param name="actions"></param>  
  121.         /// <param name="removeTarget"></param>  
  122.         /// <returns></returns>  
  123.         private HashSet<Action> ActionSubset(HashSet<Action> actions,Action removeTarget)  
  124.         {  
  125.             HashSet<Action> subset = new HashSet<Action>();  
  126.             foreach(var a in actions)  
  127.             {  
  128.                 if (!a.Equals(removeTarget))  
  129.                     subset.Add(a);  
  130.             }  
  131.             return subset;  
  132.         }  
  133.   
  134.         /// <summary>  
  135.         /// 目标状态集合是否全在该目标集合内  
  136.         /// </summary>  
  137.         /// <param name="state"></param>  
  138.         /// <param name="isExistStates"></param>  
  139.         /// <returns></returns>  
  140.         private bool InState(HashSet<KeyValuePair<string,object>> state,HashSet<KeyValuePair<string,object>> isExistStates)  
  141.         {  
  142.             bool allMatch = true;  
  143.   
  144.             foreach (var s in isExistStates)  
  145.             {  
  146.                 bool match = false;  
  147.   
  148.                 foreach(var s2 in state)  
  149.                 {  
  150.                     if(s2.Equals(s))  
  151.                     {  
  152.                         match = true;  
  153.                         break;  
  154.                     }  
  155.                 }  
  156.   
  157.                 //如果出现一个不匹配  
  158.                 if (!match)  
  159.                 {  
  160.                     allMatch = false;  
  161.                     break;  
  162.                 }  
  163.             }  
  164.             return allMatch;  
  165.         }  
  166.   
  167.         /// <summary>  
  168.         /// 将目标状态集合更新到原集合里,没有的增加,存在的更新  
  169.         /// </summary>  
  170.         /// <param name="currentState"></param>  
  171.         /// <param name="stateChange"></param>  
  172.         /// <returns></returns>  
  173.         private HashSet<KeyValuePair<string,object>> PopulateState(HashSet<KeyValuePair<string,object>> currentState,HashSet<KeyValuePair<string,object>> stateChange)  
  174.         {  
  175.             HashSet<KeyValuePair<stringobject>> state = new HashSet<KeyValuePair<stringobject>>();  
  176.   
  177.             foreach (var s in currentState)  
  178.                 state.Add(new KeyValuePair<stringobject>(s.Key, s.Value));  
  179.   
  180.             foreach(var change in stateChange)  
  181.             {  
  182.                 bool exists = false;  
  183.   
  184.                 foreach(var s in state)  
  185.                 {  
  186.                     if(s.Equals(change))  
  187.                     {  
  188.                         exists = true;  
  189.                         break;  
  190.                     }  
  191.                 }  
  192.   
  193.                 if(exists)  
  194.                 {  
  195.                     //删除掉原来的,并把改变后的加进去  
  196.                     state.RemoveWhere((KeyValuePair<stringobject> kvp) => { return kvp.Key.Equals(change.Key); });  
  197.                     KeyValuePair<stringobject> updated = new KeyValuePair<stringobject>(change.Key,change.Value);  
  198.                     state.Add(updated);  
  199.                 }  
  200.                 else  
  201.                 {  
  202.                     state.Add(new KeyValuePair<stringobject>(change.Key, change.Value));  
  203.                 }  
  204.             }  
  205.             return state;  
  206.         }  
  207.  
  208.         #endregion  
  209.   
  210.         /// <summary>  
  211.         /// 策划者用于存储数据的帮助节点  
  212.         /// </summary>  
  213.         private class Node  
  214.         {  
  215.             public Node Parent;                                      // 上一个节点  
  216.             public float CostNum;                                    // 总消耗成本  
  217.             public HashSet<KeyValuePair<stringobject>> State;      // 到这个节点的现有状态  
  218.             public Action Action;                                    // 该节点应该执行的动作  
  219.   
  220.             public Node(Node parent,float costNum,HashSet<KeyValuePair<string,object>> state,Action action)  
  221.             {  
  222.                 Parent = parent;  
  223.                 CostNum = costNum;  
  224.                 State = state;  
  225.                 Action = action;  
  226.             }  
  227.         }  
  228.   
  229.     }  
  230. }  


FSM

[csharp] view plain copy
  1. namespace MyGoap  
  2. {  
  3.     /// <summary>  
  4.     /// 堆栈式有限状态机  
  5.     /// </summary>  
  6.     public class FSM  
  7.     {  
  8.         //状态堆栈  
  9.         private Stack<FSMState> stateStack = new Stack<FSMState>();  
  10.           
  11.         //状态委托  
  12.         public delegate void FSMState(FSM fsm, GameObject go);  
  13.   
  14.         //执行状态  
  15.         public void Update(GameObject go)  
  16.         {  
  17.             if (stateStack.Peek() != null)  
  18.                 stateStack.Peek().Invoke(this, go);  
  19.         }  
  20.           
  21.         //压入状态  
  22.         public void PushState(FSMState state)  
  23.         {  
  24.             stateStack.Push(state);  
  25.         }  
  26.   
  27.         //弹出状态  
  28.         public void PopState()  
  29.         {  
  30.             stateStack.Pop();  
  31.         }  
  32.   
  33.     }  
  34. }  


Agent

[csharp] view plain copy
  1. namespace MyGoap  
  2. {  
  3.     public class Agent : MonoBehaviour  
  4.     {  
  5.  
  6.         #region 字段  
  7.         private FSM stateMachine;                 //状态机  
  8.   
  9.         private FSM.FSMState idleState;  
  10.         private FSM.FSMState moveToState;  
  11.         private FSM.FSMState performActionState;  
  12.   
  13.         private HashSet<Action> availableActions; //可行动作  
  14.         private Queue<Action> currentActions;     //当前需要执行的动作  
  15.           
  16.         private IGoap dataProvider;                 
  17.         private Planer planer;  
  18.         #endregion  
  19.  
  20.         #region 属性  
  21.           
  22.         /// <summary>  
  23.         /// 是否有动作计划  
  24.         /// </summary>  
  25.         private bool HasActionPlan { get { return currentActions.Count > 0; } }  
  26.  
  27.         #endregion  
  28.  
  29.         #region Unity回调  
  30.         void Start()  
  31.         {  
  32.             stateMachine = new FSM();  
  33.             availableActions = new HashSet<Action>();  
  34.             currentActions = new Queue<Action>();  
  35.             planer = new Planer();  
  36.             InitDataProvider();  
  37.             InitIdleState();  
  38.             InitMoveToState();  
  39.             InitPerformActionState();  
  40.             stateMachine.PushState(idleState);  
  41.             LoadActions();  
  42.         }  
  43.   
  44.         void Update()  
  45.         {  
  46.             stateMachine.Update(this.gameObject);  
  47.         }  
  48.         #endregion  
  49.  
  50.         #region 接口  
  51.        
  52.         /// <summary>  
  53.         /// 初始化空闲状态  
  54.         /// </summary>  
  55.         private void InitIdleState()  
  56.         {  
  57.             idleState = (fsm, go) =>  
  58.                 {  
  59.                     HashSet<KeyValuePair<stringobject>> currentState = dataProvider.GetState();  
  60.                     HashSet<KeyValuePair<stringobject>> goal = dataProvider.CreateGoalState();  
  61.   
  62.                     //计算路线  
  63.                     Queue<Action> plan = planer.Plan(gameObject, availableActions, currentState, goal);  
  64.   
  65.                     if (plan != null)  
  66.                     {  
  67.                         currentActions = plan;  
  68.                         //通知计划找到  
  69.                         dataProvider.PlanFound(goal, plan);  
  70.   
  71.                         //转换状态  
  72.                         fsm.PopState();  
  73.                         fsm.PushState(performActionState);  
  74.                     }  
  75.                     else  
  76.                     {  
  77.                         //通知计划没找到  
  78.                         dataProvider.PlanFailed(goal);  
  79.   
  80.                         //转换状态  
  81.                         fsm.PopState();  
  82.                         fsm.PushState(idleState);  
  83.                     }  
  84.                 };  
  85.         }  
  86.   
  87.         /// <summary>  
  88.         /// 初始化移动到目标状态  
  89.         /// </summary>  
  90.         private void InitMoveToState()  
  91.         {  
  92.             moveToState = (fsm, go) =>  
  93.                 {  
  94.                     Action action = currentActions.Peek();  
  95.                       
  96.                     //如果没目标且又需要移动,异常弹出  
  97.                     if(action.RequiresInRange() && action.target != null)  
  98.                     {  
  99.                         //弹出移动和执行动作状态  
  100.                         fsm.PopState();  
  101.                         fsm.PopState();  
  102.                         fsm.PushState(idleState);  
  103.                         return;  
  104.                     }  
  105.   
  106.                     //如果移动到了目标位置,弹出移动状态  
  107.                     if (dataProvider.MoveAgent(action))  
  108.                         fsm.PopState();  
  109.   
  110.                 };  
  111.         }  
  112.   
  113.         /// <summary>  
  114.         /// 初始化执行动作状态  
  115.         /// </summary>  
  116.         private void InitPerformActionState()  
  117.         {  
  118.             performActionState = (fsm, go) =>  
  119.                 {  
  120.                     //如果全部执行完毕,转换到空闲状态,并且通知  
  121.                     if (!HasActionPlan)  
  122.                     {  
  123.                         fsm.PopState();  
  124.                         fsm.PushState(idleState);  
  125.                         dataProvider.ActionsFinished();  
  126.                         return;  
  127.                     }  
  128.   
  129.                     Action action = currentActions.Peek();  
  130.   
  131.                     //如果栈顶动作完成,出栈  
  132.                     if (action.IsDone())  
  133.                         currentActions.Dequeue();  
  134.   
  135.   
  136.                     if(HasActionPlan)  
  137.                     {  
  138.                         action = currentActions.Peek();  
  139.                         //是否在范围内  
  140.                         bool inRange = action.RequiresInRange() ? action.IsInRange : true;  
  141.   
  142.                         if(inRange)  
  143.                         {  
  144.                             bool success = action.Perform(go);  
  145.   
  146.                             //如果动作执行失败,转换到空闲状态,并通知因为该动作导致计划失败  
  147.                             if(!success)  
  148.                             {  
  149.                                 fsm.PopState();  
  150.                                 fsm.PushState(idleState);  
  151.                                 dataProvider.PlanAborted(action);  
  152.                             }  
  153.                         }  
  154.                         else  
  155.                         {  
  156.                             fsm.PushState(moveToState);  
  157.                         }  
  158.                     }  
  159.                     else  
  160.                     {  
  161.                         fsm.PopState();  
  162.                         fsm.PushState(idleState);  
  163.                         dataProvider.ActionsFinished();  
  164.                     }  
  165.   
  166.                 };  
  167.         }  
  168.   
  169.         /// <summary>  
  170.         /// 初始化数据提供者  
  171.         /// </summary>  
  172.         private void InitDataProvider()  
  173.         {  
  174.             //查找当前物体身上继承自IGoap的脚本  
  175.             foreach(Component comp in gameObject.GetComponents(typeof(Component)))  
  176.             {  
  177.                 if(typeof(IGoap).IsAssignableFrom(comp.GetType()))  
  178.                 {  
  179.                     dataProvider = (IGoap)comp;  
  180.                     return;  
  181.                 }  
  182.             }  
  183.         }  
  184.   
  185.         /// <summary>  
  186.         /// 获取身上所有的Action脚本  
  187.         /// </summary>  
  188.         private void LoadActions()  
  189.         {  
  190.             Action[] actions = gameObject.GetComponents<Action>();  
  191.             foreach (var a in actions)  
  192.                 availableActions.Add(a);  
  193.         }  
  194.  
  195.         #endregion  
  196.   
  197.     }  
  198. }  
posted @ 2017-10-25 19:17  00000000O  阅读(949)  评论(0编辑  收藏  举报