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
- namespace MyGoap
- {
- public abstract class Action : MonoBehaviour
- {
- #region 字段
- private HashSet<KeyValuePair<string, object>> preconditions; // 先行条件
- private HashSet<KeyValuePair<string, object>> effects; // 造成的影响
- private bool inRange = false; // 是否在动作的可行范围内
- public float cost = 1f; // 消耗的成本
- public GameObject target; // 执行动作的目标,可以为空
- #endregion
- #region 属性
- public bool IsInRange { get { return inRange; } set { inRange = value; } }
- public HashSet<KeyValuePair<string, object>> Preconditions
- {
- get
- {
- return preconditions;
- }
- }
- public HashSet<KeyValuePair<string, object>> Effects
- {
- get
- {
- return effects;
- }
- }
- #endregion
- #region 接口
- /// <summary>
- /// 构造函数:初始化
- /// </summary>
- public Action()
- {
- preconditions = new HashSet<KeyValuePair<string, object>>();
- effects = new HashSet<KeyValuePair<string, object>>();
- }
- /// <summary>
- /// 基类重置
- /// </summary>
- public void DoReset()
- {
- inRange = false;
- target = null;
- Reset();
- }
- /// <summary>
- /// 继承类重置
- /// </summary>
- public abstract void Reset();
- /// <summary>
- /// 是否完成动作
- /// </summary>
- /// <returns></returns>
- public abstract bool IsDone();
- /// <summary>
- /// 由代理检索动作最优目标,并返回目标是否存在,动作是否可以被执行
- /// </summary>
- /// <param name="target"></param>
- /// <returns></returns>
- public abstract bool CheckProcedualPrecondition(GameObject agent);
- /// <summary>
- /// 执行动作
- /// </summary>
- /// <param name="agent"></param>
- /// <returns></returns>
- public abstract bool Perform(GameObject agent);
- /// <summary>
- /// 是否需要在范围内才能执行动作
- /// </summary>
- /// <returns></returns>
- public abstract bool RequiresInRange();
- /// <summary>
- /// 增加先行条件
- /// </summary>
- /// <param name="key"></param>
- /// <param name="value"></param>
- public void AddPrecondition(string key,object value)
- {
- preconditions.Add(new KeyValuePair<string, object>(key, value));
- }
- /// <summary>
- /// 移除先行条件
- /// </summary>
- /// <param name="key"></param>
- public void RemovePrecondition(string key)
- {
- KeyValuePair<string, object> remove = default(KeyValuePair<string, object>);
- foreach(var kvp in preconditions)
- {
- if (kvp.Key.Equals(key))
- remove = kvp;
- }
- //如果被赋值了
- if (!default(KeyValuePair<string, object>).Equals(remove))
- preconditions.Remove(remove);
- }
- /// <summary>
- /// 增加造成的效果
- /// </summary>
- /// <param name="key"></param>
- /// <param name="value"></param>
- public void AddEffect(string key, object value)
- {
- effects.Add(new KeyValuePair<string, object>(key, value));
- }
- /// <summary>
- /// 移除造成的效果
- /// </summary>
- /// <param name="key"></param>
- public void RemoveEffect(string key)
- {
- KeyValuePair<string, object> remove = default(KeyValuePair<string, object>);
- foreach (var kvp in effects)
- {
- if (kvp.Key.Equals(key))
- remove = kvp;
- }
- //如果被赋值了
- if (!default(KeyValuePair<string, object>).Equals(remove))
- effects.Remove(remove);
- }
- #endregion
- }
- }
IGoap
- namespace MyGoap
- {
- public interface IGoap
- {
- /// <summary>
- /// 获取现在所有状态
- /// </summary>
- /// <returns></returns>
- HashSet<KeyValuePair<string, object>> GetState();
- /// <summary>
- /// 创建新的目标状态集合
- /// </summary>
- /// <returns></returns>
- HashSet<KeyValuePair<string, object>> CreateGoalState();
- /// <summary>
- /// 没有找到可以完成目标的路线
- /// </summary>
- /// <param name="failedGoal"></param>
- void PlanFailed(HashSet<KeyValuePair<string, object>> failedGoal);
- /// <summary>
- /// 找到可以完成目标的一系列动作
- /// </summary>
- /// <param name="goal"></param>
- /// <param name="actions"></param>
- void PlanFound(HashSet<KeyValuePair<string, object>> goal, Queue<Action> actions);
- /// <summary>
- /// 动作全部完成,达成目标
- /// </summary>
- void ActionsFinished();
- /// <summary>
- /// 计划被一个动作打断
- /// </summary>
- /// <param name="aborterAction"></param>
- void PlanAborted(Action aborterAction);
- /// <summary>
- /// 移动到目标动作位置
- /// </summary>
- /// <param name="tagetAction"></param>
- /// <returns></returns>
- bool MoveAgent(Action tagetAction);
- }
- }
Planer
- namespace MyGoap
- {
- public class Planer
- {
- /// <summary>
- /// 计划出最优路线
- /// </summary>
- /// <param name="agent">把代理传进来</param>
- /// <param name="availableActions">当前可行动作</param>
- /// <param name="currentState">当前状态</param>
- /// <param name="goal">目标</param>
- /// <returns></returns>
- public Queue<Action> Plan(GameObject agent,HashSet<Action> availableActions,HashSet<KeyValuePair<string,object>> currentState,HashSet<KeyValuePair<string,object>> goal)
- {
- foreach (var a in availableActions)
- a.DoReset();
- //排除不可行动作
- HashSet<Action> usableActions = new HashSet<Action>();
- foreach (var a in availableActions)
- if (a.CheckProcedualPrecondition(agent))
- usableActions.Add(a);
- List<Node> leaves = new List<Node>();
- //由当前状态开始计算,并把结果添加到路线集合里
- Node start = new Node(null, 0, currentState, null);
- bool success = BuildGraph(start, leaves, usableActions, goal);
- if (!success)
- return null;
- //得到成本最小的路线
- Node cheapest = null;
- foreach(Node leaf in leaves)
- {
- if (cheapest == null)
- cheapest = leaf;
- else
- {
- if (leaf.CostNum < cheapest.CostNum)
- cheapest = leaf;
- }
- }
- //链表遍历法遍历最后一个节点,并把每一个动作往前插入,因为越后面节点的动作是越后面要执行的
- List<Action> result = new List<Action>();
- Node n = cheapest;
- while(n != null)
- {
- if(n.Action != null)
- {
- result.Insert(0, n.Action);
- }
- n = n.Parent;
- }
- //把链表转换为队列返回回去
- Queue<Action> queue = new Queue<Action>();
- foreach(Action a in result)
- {
- queue.Enqueue(a);
- }
- return queue;
- }
- /// <summary>
- /// 策划者计划主要算法
- /// </summary>
- /// <param name="parent">父节点</param>
- /// <param name="leaves">路线集合</param>
- /// <param name="usableActions">可行动作</param>
- /// <param name="goal">目标状态</param>
- /// <returns></returns>
- private bool BuildGraph(Node parent,List<Node> leaves,HashSet<Action> usableActions,HashSet<KeyValuePair<string,object>> goal)
- {
- bool foundOne = false;
- // 遍历所有可行动作
- foreach(var action in usableActions)
- {
- // 如果当前状态匹配当前动作前置条件,动作执行
- if(InState(action.Preconditions,parent.State))
- {
- //造成效果影响当前状态
- HashSet<KeyValuePair<string, object>> currentState = PopulateState(parent.State, action.Effects);
- //生成动作完成的节点链,注意成本累加
- Node node = new Node(parent, parent.CostNum + action.cost, currentState, action);
- //如果当前状态存在要完成的目标状态
- if(InState(goal,currentState))
- {
- //增加可行方案路线
- leaves.Add(node);
- foundOne = true;
- }
- else
- {
- //否则该可行动作排除,用其他动作由 该节点 继续搜索接下去的路线
- HashSet<Action> subset = ActionSubset(usableActions, action);
- bool found = BuildGraph(node, leaves, subset, goal);
- if (found)
- foundOne = true;
- }
- }
- }
- return foundOne;
- }
- #region 帮助方法
- /// <summary>
- /// 移除目标动作并返回移除后的动作集合
- /// </summary>
- /// <param name="actions"></param>
- /// <param name="removeTarget"></param>
- /// <returns></returns>
- private HashSet<Action> ActionSubset(HashSet<Action> actions,Action removeTarget)
- {
- HashSet<Action> subset = new HashSet<Action>();
- foreach(var a in actions)
- {
- if (!a.Equals(removeTarget))
- subset.Add(a);
- }
- return subset;
- }
- /// <summary>
- /// 目标状态集合是否全在该目标集合内
- /// </summary>
- /// <param name="state"></param>
- /// <param name="isExistStates"></param>
- /// <returns></returns>
- private bool InState(HashSet<KeyValuePair<string,object>> state,HashSet<KeyValuePair<string,object>> isExistStates)
- {
- bool allMatch = true;
- foreach (var s in isExistStates)
- {
- bool match = false;
- foreach(var s2 in state)
- {
- if(s2.Equals(s))
- {
- match = true;
- break;
- }
- }
- //如果出现一个不匹配
- if (!match)
- {
- allMatch = false;
- break;
- }
- }
- return allMatch;
- }
- /// <summary>
- /// 将目标状态集合更新到原集合里,没有的增加,存在的更新
- /// </summary>
- /// <param name="currentState"></param>
- /// <param name="stateChange"></param>
- /// <returns></returns>
- private HashSet<KeyValuePair<string,object>> PopulateState(HashSet<KeyValuePair<string,object>> currentState,HashSet<KeyValuePair<string,object>> stateChange)
- {
- HashSet<KeyValuePair<string, object>> state = new HashSet<KeyValuePair<string, object>>();
- foreach (var s in currentState)
- state.Add(new KeyValuePair<string, object>(s.Key, s.Value));
- foreach(var change in stateChange)
- {
- bool exists = false;
- foreach(var s in state)
- {
- if(s.Equals(change))
- {
- exists = true;
- break;
- }
- }
- if(exists)
- {
- //删除掉原来的,并把改变后的加进去
- state.RemoveWhere((KeyValuePair<string, object> kvp) => { return kvp.Key.Equals(change.Key); });
- KeyValuePair<string, object> updated = new KeyValuePair<string, object>(change.Key,change.Value);
- state.Add(updated);
- }
- else
- {
- state.Add(new KeyValuePair<string, object>(change.Key, change.Value));
- }
- }
- return state;
- }
- #endregion
- /// <summary>
- /// 策划者用于存储数据的帮助节点
- /// </summary>
- private class Node
- {
- public Node Parent; // 上一个节点
- public float CostNum; // 总消耗成本
- public HashSet<KeyValuePair<string, object>> State; // 到这个节点的现有状态
- public Action Action; // 该节点应该执行的动作
- public Node(Node parent,float costNum,HashSet<KeyValuePair<string,object>> state,Action action)
- {
- Parent = parent;
- CostNum = costNum;
- State = state;
- Action = action;
- }
- }
- }
- }
FSM
- namespace MyGoap
- {
- /// <summary>
- /// 堆栈式有限状态机
- /// </summary>
- public class FSM
- {
- //状态堆栈
- private Stack<FSMState> stateStack = new Stack<FSMState>();
- //状态委托
- public delegate void FSMState(FSM fsm, GameObject go);
- //执行状态
- public void Update(GameObject go)
- {
- if (stateStack.Peek() != null)
- stateStack.Peek().Invoke(this, go);
- }
- //压入状态
- public void PushState(FSMState state)
- {
- stateStack.Push(state);
- }
- //弹出状态
- public void PopState()
- {
- stateStack.Pop();
- }
- }
- }
Agent
- namespace MyGoap
- {
- public class Agent : MonoBehaviour
- {
- #region 字段
- private FSM stateMachine; //状态机
- private FSM.FSMState idleState;
- private FSM.FSMState moveToState;
- private FSM.FSMState performActionState;
- private HashSet<Action> availableActions; //可行动作
- private Queue<Action> currentActions; //当前需要执行的动作
- private IGoap dataProvider;
- private Planer planer;
- #endregion
- #region 属性
- /// <summary>
- /// 是否有动作计划
- /// </summary>
- private bool HasActionPlan { get { return currentActions.Count > 0; } }
- #endregion
- #region Unity回调
- void Start()
- {
- stateMachine = new FSM();
- availableActions = new HashSet<Action>();
- currentActions = new Queue<Action>();
- planer = new Planer();
- InitDataProvider();
- InitIdleState();
- InitMoveToState();
- InitPerformActionState();
- stateMachine.PushState(idleState);
- LoadActions();
- }
- void Update()
- {
- stateMachine.Update(this.gameObject);
- }
- #endregion
- #region 接口
- /// <summary>
- /// 初始化空闲状态
- /// </summary>
- private void InitIdleState()
- {
- idleState = (fsm, go) =>
- {
- HashSet<KeyValuePair<string, object>> currentState = dataProvider.GetState();
- HashSet<KeyValuePair<string, object>> goal = dataProvider.CreateGoalState();
- //计算路线
- Queue<Action> plan = planer.Plan(gameObject, availableActions, currentState, goal);
- if (plan != null)
- {
- currentActions = plan;
- //通知计划找到
- dataProvider.PlanFound(goal, plan);
- //转换状态
- fsm.PopState();
- fsm.PushState(performActionState);
- }
- else
- {
- //通知计划没找到
- dataProvider.PlanFailed(goal);
- //转换状态
- fsm.PopState();
- fsm.PushState(idleState);
- }
- };
- }
- /// <summary>
- /// 初始化移动到目标状态
- /// </summary>
- private void InitMoveToState()
- {
- moveToState = (fsm, go) =>
- {
- Action action = currentActions.Peek();
- //如果没目标且又需要移动,异常弹出
- if(action.RequiresInRange() && action.target != null)
- {
- //弹出移动和执行动作状态
- fsm.PopState();
- fsm.PopState();
- fsm.PushState(idleState);
- return;
- }
- //如果移动到了目标位置,弹出移动状态
- if (dataProvider.MoveAgent(action))
- fsm.PopState();
- };
- }
- /// <summary>
- /// 初始化执行动作状态
- /// </summary>
- private void InitPerformActionState()
- {
- performActionState = (fsm, go) =>
- {
- //如果全部执行完毕,转换到空闲状态,并且通知
- if (!HasActionPlan)
- {
- fsm.PopState();
- fsm.PushState(idleState);
- dataProvider.ActionsFinished();
- return;
- }
- Action action = currentActions.Peek();
- //如果栈顶动作完成,出栈
- if (action.IsDone())
- currentActions.Dequeue();
- if(HasActionPlan)
- {
- action = currentActions.Peek();
- //是否在范围内
- bool inRange = action.RequiresInRange() ? action.IsInRange : true;
- if(inRange)
- {
- bool success = action.Perform(go);
- //如果动作执行失败,转换到空闲状态,并通知因为该动作导致计划失败
- if(!success)
- {
- fsm.PopState();
- fsm.PushState(idleState);
- dataProvider.PlanAborted(action);
- }
- }
- else
- {
- fsm.PushState(moveToState);
- }
- }
- else
- {
- fsm.PopState();
- fsm.PushState(idleState);
- dataProvider.ActionsFinished();
- }
- };
- }
- /// <summary>
- /// 初始化数据提供者
- /// </summary>
- private void InitDataProvider()
- {
- //查找当前物体身上继承自IGoap的脚本
- foreach(Component comp in gameObject.GetComponents(typeof(Component)))
- {
- if(typeof(IGoap).IsAssignableFrom(comp.GetType()))
- {
- dataProvider = (IGoap)comp;
- return;
- }
- }
- }
- /// <summary>
- /// 获取身上所有的Action脚本
- /// </summary>
- private void LoadActions()
- {
- Action[] actions = gameObject.GetComponents<Action>();
- foreach (var a in actions)
- availableActions.Add(a);
- }
- #endregion
- }
- }
AI决策算法 之 GOAP (二)的更多相关文章
- AI决策算法 之 GOAP (一)
http://blog.csdn.net/lovethrain/article/details/67632033 本系列文章内容部分参考自:http://gamerboom.com/archives/ ...
- AI决策算法 之 GOAP (三)
源码地址:http://pan.baidu.com/s/1dFwzmfB 这篇我们使用上篇文章写的GOAP框架来完成一个实例: 实例内容: AI有10HP, 需要去站岗,站岗完成扣5HP 当HP< ...
- 贝叶斯公式由浅入深大讲解—AI基础算法入门
1 贝叶斯方法 长久以来,人们对一件事情发生或不发生的概率,只有固定的0和1,即要么发生,要么不发生,从来不会去考虑某件事情发生的概率有多大,不发生的概率又是多大.而且概率虽然未知,但最起码是一个确定 ...
- 贝叶斯公式由浅入深大讲解—AI基础算法入门【转】
本文转载自:https://www.cnblogs.com/zhoulujun/p/8893393.html 1 贝叶斯方法 长久以来,人们对一件事情发生或不发生的概率,只有固定的0和1,即要么发生, ...
- Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解
Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全 Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...
- js算法集合(二) javascript实现斐波那契数列 (兔子数列)
js算法集合(二) 斐波那契数列 ★ 上一次我跟大家分享一下做水仙花数的算法的思路,并对其扩展到自幂数的算法,这次,我们来对斐波那契数列进行研究,来加深对循环的理解. Javascript实 ...
- 2018科大讯飞AI营销算法大赛全面来袭,等你来战!
AI技术已成为推动营销迭代的重要驱动力.AI营销高速发展的同时,积累了海量的广告数据和用户数据.如何有效应用这些数据,是大数据技术落地营销领域的关键,也是检测智能营销平台竞争力的标准. 讯飞AI营销云 ...
- 算法题 19 二叉平衡树检查 牛客网 CC150
算法题 19 二叉平衡树检查 牛客网 CC150 实现一个函数,检查二叉树是否平衡,平衡的定义如下,对于树中的任意一个结点,其两颗子树的高度差不超过1. 给定指向树根结点的指针TreeNode* ro ...
- 实践案例丨基于ModelArts AI市场算法MobileNet_v2实现花卉分类
概述 MobileNetsV2是基于一个流线型的架构,它使用深度可分离的卷积来构建轻量级的深层神经网,此模型基于 MobileNetV2: Inverted Residuals and Linear ...
随机推荐
- java中的clone方法
Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...
- Apache NiFi 开发 处理器使用说明
NIFI的使用: 注意:FlowFile由[属性]和[内容]组成,在解析的过程中这个概念非常重要,因为有些组件操作的是属性,有些组件操作的是内容,在配置组件时Destination配置项的选择很重要, ...
- P1856 [USACO5.5]矩形周长Picture
P1856 [USACO5.5]矩形周长Picture $len$ $sum$ $num$ $flag\_l$ $flage\_ ...
- centos下安装wordpress
https://www.jianshu.com/p/2439dc2187b2 https://blog.csdn.net/liuhelong/article/details/79924014
- CLion提示can't find stdio.h等错误
先上解决办法,启动参数如下: $ LANG=en_US.UTF-8 /path/to/clion.sh 查了好知久,竟然就由于编码的原因.可是Ubuntu已经设置为英文UTF-8,还是可以通过上面的方 ...
- 20145239杜文超 《Java程序设计》第1周学习总结
20145239<Java程序设计>第1周学习总结 教材学习内容总结 第一周. 通过教材简单的了解了java的历史.因为之前看过视频,所以有一个大致明了的认识. 识记了Java三大平台:J ...
- BZOJ 1192 [HNOI2006]鬼谷子的钱袋:二进制 砝码称重问题
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1192 题意: 鬼谷子带了a元钱,他要把a元钱分装在小袋子中,使得任意不大于a的数目的钱,都 ...
- kvm初体验之六:克隆
目标:克隆vm1到vm1-clone 1. virsh suspend vm1 2. virt-clone --connect qemu:///system --original vm1 --name ...
- 分享知识-快乐自己:Java 中 的String,StringBuilder,StringBuffer三者的区别
这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面. 1):首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer &g ...
- python- 双层装饰器 字符串格式化 python模块 递归 生成器 迭代器 序列化
1.双层装饰器 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # author:zml LOGIN_INFO = False IS_ADMIN = Fa ...