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



IGoap

[csharp] view
plain
 copy

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

Planer

[csharp] view
plain
 copy

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



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. public delegate void FSMState(FSM fsm, GameObject go);
  12. //执行状态
  13. public void Update(GameObject go)
  14. {
  15. if (stateStack.Peek() != null)
  16. stateStack.Peek().Invoke(this, go);
  17. }
  18. //压入状态
  19. public void PushState(FSMState state)
  20. {
  21. stateStack.Push(state);
  22. }
  23. //弹出状态
  24. public void PopState()
  25. {
  26. stateStack.Pop();
  27. }
  28. }
  29. }

Agent

[csharp] view
plain
 copy

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

AI决策算法 之 GOAP (二)的更多相关文章

  1. AI决策算法 之 GOAP (一)

    http://blog.csdn.net/lovethrain/article/details/67632033 本系列文章内容部分参考自:http://gamerboom.com/archives/ ...

  2. AI决策算法 之 GOAP (三)

    源码地址:http://pan.baidu.com/s/1dFwzmfB 这篇我们使用上篇文章写的GOAP框架来完成一个实例: 实例内容: AI有10HP, 需要去站岗,站岗完成扣5HP 当HP< ...

  3. 贝叶斯公式由浅入深大讲解—AI基础算法入门

    1 贝叶斯方法 长久以来,人们对一件事情发生或不发生的概率,只有固定的0和1,即要么发生,要么不发生,从来不会去考虑某件事情发生的概率有多大,不发生的概率又是多大.而且概率虽然未知,但最起码是一个确定 ...

  4. 贝叶斯公式由浅入深大讲解—AI基础算法入门【转】

    本文转载自:https://www.cnblogs.com/zhoulujun/p/8893393.html 1 贝叶斯方法 长久以来,人们对一件事情发生或不发生的概率,只有固定的0和1,即要么发生, ...

  5. Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解

    Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全   Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...

  6. js算法集合(二) javascript实现斐波那契数列 (兔子数列)

    js算法集合(二)  斐波那契数列 ★ 上一次我跟大家分享一下做水仙花数的算法的思路,并对其扩展到自幂数的算法,这次,我们来对斐波那契数列进行研究,来加深对循环的理解.     Javascript实 ...

  7. 2018科大讯飞AI营销算法大赛全面来袭,等你来战!

    AI技术已成为推动营销迭代的重要驱动力.AI营销高速发展的同时,积累了海量的广告数据和用户数据.如何有效应用这些数据,是大数据技术落地营销领域的关键,也是检测智能营销平台竞争力的标准. 讯飞AI营销云 ...

  8. 算法题 19 二叉平衡树检查 牛客网 CC150

    算法题 19 二叉平衡树检查 牛客网 CC150 实现一个函数,检查二叉树是否平衡,平衡的定义如下,对于树中的任意一个结点,其两颗子树的高度差不超过1. 给定指向树根结点的指针TreeNode* ro ...

  9. 实践案例丨基于ModelArts AI市场算法MobileNet_v2实现花卉分类

    概述 MobileNetsV2是基于一个流线型的架构,它使用深度可分离的卷积来构建轻量级的深层神经网,此模型基于 MobileNetV2: Inverted Residuals and Linear ...

随机推荐

  1. java中的clone方法

    Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...

  2. Apache NiFi 开发 处理器使用说明

    NIFI的使用: 注意:FlowFile由[属性]和[内容]组成,在解析的过程中这个概念非常重要,因为有些组件操作的是属性,有些组件操作的是内容,在配置组件时Destination配置项的选择很重要, ...

  3. P1856 [USACO5.5]矩形周长Picture

    P1856 [USACO5.5]矩形周长Picture $len$            $sum$              $num$             $flag\_l$ $flage\_ ...

  4. centos下安装wordpress

    https://www.jianshu.com/p/2439dc2187b2 https://blog.csdn.net/liuhelong/article/details/79924014

  5. CLion提示can't find stdio.h等错误

    先上解决办法,启动参数如下: $ LANG=en_US.UTF-8 /path/to/clion.sh 查了好知久,竟然就由于编码的原因.可是Ubuntu已经设置为英文UTF-8,还是可以通过上面的方 ...

  6. 20145239杜文超 《Java程序设计》第1周学习总结

    20145239<Java程序设计>第1周学习总结 教材学习内容总结 第一周. 通过教材简单的了解了java的历史.因为之前看过视频,所以有一个大致明了的认识. 识记了Java三大平台:J ...

  7. BZOJ 1192 [HNOI2006]鬼谷子的钱袋:二进制 砝码称重问题

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1192 题意: 鬼谷子带了a元钱,他要把a元钱分装在小袋子中,使得任意不大于a的数目的钱,都 ...

  8. kvm初体验之六:克隆

    目标:克隆vm1到vm1-clone 1. virsh suspend vm1 2. virt-clone --connect qemu:///system --original vm1 --name ...

  9. 分享知识-快乐自己:Java 中 的String,StringBuilder,StringBuffer三者的区别

    这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面. 1):首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer &g ...

  10. python- 双层装饰器 字符串格式化 python模块 递归 生成器 迭代器 序列化

    1.双层装饰器 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # author:zml LOGIN_INFO = False IS_ADMIN = Fa ...