AI决策算法 之 GOAP (二)


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

    }
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值