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. }

最新文章

  1. Android 6.0 - 动态权限管理的解决方案
  2. date时间函数
  3. loadrunner取出字符串的后面几位
  4. mysql -prompt选项
  5. ViewController之间的切换动画
  6. 解锁Dagger2使用姿势(一)
  7. ACM Sdut 2158 Hello World!(数学题,排序) (山东省ACM第一届省赛C题)
  8. iOS协议
  9. HDU 1226 超级密码 (搜素)
  10. Mvc后台接收 参数
  11. linux 安装mysql5.7版本
  12. 重温TCP
  13. react16实战总结
  14. f.lux 自动调节显示器色温
  15. [转帖]xargs命令详解,xargs与管道的区别
  16. replicate_wild_do_table和replicate-wild-ignore-table的使用【转】
  17. Ubuntu 16.04设置开机启动脚本的方法
  18. 从unity丢图标到unity进不去桌面
  19. mysql 数据表操作 目录
  20. 浅入 dancing links x(舞蹈链算法)

热门文章

  1. (扫盲)jQuery extend()和jQuery.fn.extend()的区别
  2. TensorFlow运行时出现warning如何设置禁止打印方法
  3. 【总结】性能调优:JVM内存调优相关文章
  4. 什么是XXX
  5. LightOJ - 1284 Lights inside 3D Grid —— 期望
  6. JSON.stringify出现 &quot;Converting circular structure to JSON&quot;
  7. python的小知识点
  8. PHP的Calling Scope(::调用非静态方法)
  9. linux进程学习笔记
  10. Ubuntu下安装deb包命令