• 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. }  
  • 相关阅读:
    springboot—spring aop 实现系统操作日志记录存储到数据库
    排名前16的Java工具类
    SpringBoot集成JWT实现token验证
    使用jQuery实现图片懒加载原理
    Spring主从数据库的配置和动态数据源切换原理
    使用Nginx过滤网络爬虫
    Java io.netty.util.ReferenceCountUtil 代码实例
    Netty系列之Netty百万级推送服务设计要点
    Java给图片和PDF文件添加水印(图片水印和文字水印)
    【TortoiseSVN】windows中连接SVN服务器的工具
  • 原文地址:https://www.cnblogs.com/nafio/p/9137022.html
Copyright © 2020-2023  润新知