使用Goap进行目标导向的行为规划

前些天又重新看了遍ReGoap框架,果然,都忘光了。。。

还是自己写一个简单的小栗子吧。

Base

Goap的核心在于标签化地去抽象描述世界。通过一系列标签来定义世界的当前【状态】,当然,这个所谓的世界其实是Agent认知中的世界,实际应该是通过感知模块将感受到的数据收集到数据组件,然后通过数据组件更新【记忆】中的世界状态,也包括自身状态,比如饥饿值、疲劳度等等,为了描述简单,下面我们不纠结内部世界外部世界,统一用【世界状态】来表示。

一个状态,就是一些列标签的集合,每个标签表示了这个状态的一部分特性,比如 “{年份:2025,月份:7, 日期 : 2,放假:否} ”这样一个标签集合,就共同描述了一个平平无奇的工作日的状态。可见状态中最核心的部分就是记录各个标签的哈希表。

	public enum TestGoapTagType
    {
        Hungry,
        Tired,
        HasFood,
        InKitchen,
    }

    public struct TestGoapTag
    {
        public TestGoapTagType TagType;
        public int IntValue;

        public bool Equals(TestGoapTag _other)
        {
            return _other.IntValue == IntValue;
        }
    }

	public class TestGoapState
    {
    	...
        private Dictionary<TestGoapTagType, TestGoapTag> mTags;
        ...
    }

【以目标为导向的行为规划】自然需要先生成【目标】然后去【规划】一个可以达成这个目标的【行为】序列。

所谓目标(Goal),是期望将世界状态中的某些指定标签变成什么样子。比如当前世界状态的标签集合是 {饥饿 : true 疲惫 : true} ,有一个目标是“吃饱”,那这个目标就是期望将 {饥饿 : true} 变为 {饥饿 : false},至于“疲惫”这个标签怎么样,不在这个目标的考虑范围之内。可见目标的核心是世界状态的一个子集。另外目标还有一个重要的属性是优先级,Agent总是会尝试将更高优先级的目标设置为当前目标。

	public class TestGoapGoal
    {
        /// <summary>
        /// 期望达到的目标状态
        /// </summary>
        protected TestGoapState mTargetState;
        /// <summary>
        /// 规划得到的行为序列
        /// </summary>
        protected Queue<TestGoapAction> mActionQueue;
        /// <summary>
        /// 优先级,可以在子类中根据需求定制规则,既可以是静态也可以是动态
        /// </summary>
        /// <returns></returns>
        public virtual int GetPriority() => mTargetState.Count;
		//其他逻辑
		...
    }

行为(Action)是达成目标的手段,主要包含两个标签集合:一个是Effects,即行为效果,指当行为执行成功后,会将哪些标签进行怎样的改变;另一个是PreConditions,即前置条件,指世界状态必须满足该集合内标签的要求才能够执行这个行为。这样,一些列满足PreCondition的行为按照一定的顺序依次执行,并在执行结束时各自将自身的Effects作用到世界状态上,从而使世界状态最终达到目标所需的样子。我们所作的规划,就是找到这个行为序列。

	public class TestGoapAction
    {
        /// <summary>
        /// 前置条件
        /// </summary>
        protected TestGoapState mPreconditionState;
        /// <summary>
        /// 效果
        /// </summary>
        protected TestGoapState mEffectState;

        public string Name { get; set; }
        public TestGoapState PreConditions => mPreconditionState;
        public TestGoapState Effects => mEffectState;
        /// <summary>
        /// cost值,用于规划路径
        /// 可以在子类中定制,既可以是静态也可以是动态
        /// </summary>
        /// <returns></returns>
        public int GetCost() => mEffectState.Count;
        //其他逻辑
        ...
	}

Agent是Goap中的主体,其主要包括:

  • 一个Goal列表,表示这个Agent可以进行哪些规划
  • 一个Action列表,表示这个Agent可以进行哪些行为
  • 一个Memory,也就是这个Agent感知到的世界状态
  • 一个CurrentGoal,表示这个Agent当前被设置的目标
	public class TestGoapAgent
    {
        private TestGoapState mMemory;
        private List<TestGoapGoal> mAbleGoals;
        private List<TestGoapAction> mAbleActions;
        public TestGoapGoal CurrentGoal { get; private set; }
		//其他逻辑
		...
	}

Planner

Planner负责为Agent执行规划,核心就是一个Plan方法,根据优先级从高到底依次对目标列表中的目标尝试进行规划,直到找到一个可以规划出行为序列的目标,或者都失败。
大体过程可以分为三步:

  1. 先用Agent的Action列表对Goal进行粗筛,不考虑Action是否真的满足前置条件,只考虑Action的Effects是不是可以覆盖掉Goal所有的标签要求
  2. 通过粗筛的Goal视为有可能达成的目标,使用A*逆向进行路径搜索,尝试找到一个可行的行为序列
  3. A*搜索成功,代表这个Goal真实可达,生成行为序列,并将该目标设置为Agent的当前目标。

当然这中间可以根据需要自己增加诸如迭代次数、规划间隔、紧急目标之类的逻辑。

		public void Plan(TestGoapAgent _agent, TestGoapAStar _aStar)
        {
            List<TestGoapGoal> _possibleGoals = _agent.AbleGoals;
            for (int i = _possibleGoals.Count - 1; i >= 0; i--)
            {
                TestGoapGoal _tmpGoal = _possibleGoals[i];
                
                //第一步,粗筛,目标是否能被Action列表覆盖
                ...
                //第二步,尝试规划Action路径
                ...
                //第三步,用搜索到的路径填充Action列表,并为Agent设置当前目标
                ...
                return;
            }
        }

路径搜索

路径搜索就是A*搜索的过程,从GoalState出发,按cost依次找对达成当前剩余目标有帮助的Action,消除掉目标中那些可以被Action的Effects满足的标签,如果Action有前置条件,这些前置条件也会变成新的目标标签添加到GoalState中,然后继续搜索下一Action,直至剩余目标被清空(或Action用完、或达到指定的迭代次数)。

具体的内容基本都写在注释里,这里不啰嗦了。

测试

新建了两个Goal的子类:

  • 吃饭: 目标是将饥饿度清空,但是优先级比较低
  • 睡觉:目标是将疲惫值清空,优先级比较高

新建了三个Action的子类:

  • 进食:效果会清空饥饿度,但有两个前置条件,一个是在厨房里,另一个是有食物
  • 前往厨房:效果是将标签“在厨房里”置为true,没有前置
  • 烹饪:效果是将标签"有食物"置为true,前置是需要在厨房里

这里没有加感知的部分,手动为Agent设置了初始的Memory,饥饿度和疲惫值都为1。

因此,根据优先级,Planner会先为Agent规划【睡觉】,但由于Action不足以覆盖目标,无法通过粗筛,该目标被丢弃,然后规划【吃饭】,并通过A*查找到可行路径为:【前往厨房】→【烹饪】→【进食】

在这里插入图片描述

完整测试代码:

Base:

using System.Collections.Generic;
using UnityEngine;

namespace MapRandom.Goap
{
    public enum TestGoapTagType
    {
        Hungry,
        Tired,
        HasFood,
        InKitchen,
    }

    public struct TestGoapTag
    {
        public TestGoapTagType TagType;
        public int IntValue;

        public bool Equals(TestGoapTag _other)
        {
            return _other.IntValue == IntValue;
        }
    }

    public class TestGoapState
    {
        #region Pool
        
        private static Stack<TestGoapState> mPool = new();
        public static TestGoapState Pop()
        {
            TestGoapState _ret = 0 == mPool.Count ? new TestGoapState() : mPool.Pop();
            _ret.Init();
            return _ret;
        }
        public static void Push(TestGoapState _state)
        {
            _state.Clear();
            mPool.Push(_state);
        }

        #endregion

        private Dictionary<TestGoapTagType, TestGoapTag> mBuff_1 = new();
        private Dictionary<TestGoapTagType, TestGoapTag> mBuff_2 = new();
        private Dictionary<TestGoapTagType, TestGoapTag> mTags;

        public Dictionary<TestGoapTagType, TestGoapTag> Tags => mTags;
        public int Count => mTags.Count;

        public void Init()
        {
            mTags = mBuff_1;
        }

        public void AddGoapTag(TestGoapTag _tagData)
        {
            Tags.Add(_tagData.TagType, _tagData);
        }

        public void Clear()
        {
            mBuff_1.Clear();
            mBuff_2.Clear();
        }

        public void Clone(TestGoapState _other)
        {
            mTags.Clear();
            foreach (var _kv in _other.Tags)
            {
                mTags.Add(_kv.Key, _kv.Value);
            }
        }

        public void CoverByOther(TestGoapState _other)
        {
            foreach (var _kv in _other.Tags)
            {
                var _key = _kv.Key;
                var _value = _kv.Value;
                if (mTags.ContainsKey(_key))
                {
                    mTags[_key] = _value;
                }
                else
                {
                    mTags.Add(_key, _value);
                }
            }
        }

        public bool HasAnySameOf(TestGoapState _other)
        {
            foreach (var _kv in _other.Tags)
            {
                if (mTags.TryGetValue(_kv.Key, out TestGoapTag _value))
                {
                    if (_value.Equals(_kv.Value))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        public bool HasAnyConflictOf(TestGoapState _other)
        {
            foreach (var _kv in _other.Tags)
            {
                if (mTags.TryGetValue(_kv.Key, out TestGoapTag _value))
                {
                    if (!_value.Equals(_kv.Value))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        public bool HasAnyConflictOfBoth(TestGoapState _other, TestGoapState _fixState)
        {
            foreach (var _kv in _other.Tags)
            {
                if (mTags.TryGetValue(_kv.Key, out TestGoapTag _selfValue))
                {
                    if (!_selfValue.Equals(_kv.Value)) 
                    {
                        //与条件冲突,检查冲突是否可以被结果修复
                        if (!_fixState.Tags.TryGetValue(_kv.Key, out TestGoapTag _fixValue))
                        {
                            return true;
                        }
                        return !_selfValue.Equals(_fixValue);
                    }
                }
            }
            return false;
        }

        public void RemoveSameToCollector(TestGoapState _other, TestGoapState _diffCollector)
        {
            foreach (var _kv in mTags)
            {
                if (_other.Tags.TryGetValue(_kv.Key, out TestGoapTag _otherValue) && _kv.Value.Equals(_otherValue))
                {
                    continue;
                }
                _diffCollector.Tags.Add(_kv.Key, _kv.Value);
            }
        }

        public void RemoveSameToSelf(TestGoapState _other)
        {
            Dictionary<TestGoapTagType, TestGoapTag> _next = mTags == mBuff_1 ? mBuff_2 : mBuff_1;
            _next.Clear();
            foreach (var _kv in mTags)
            {
                if (_other.Tags.TryGetValue(_kv.Key, out TestGoapTag _otherValue) && _kv.Value.Equals(_otherValue))
                {
                    continue;
                }
                _next.Add(_kv.Key, _kv.Value);
            }
            mTags = _next;
        }

    }

    public class TestGoapGoal
    {
        /// <summary>
        /// 期望达到的目标状态
        /// </summary>
        protected TestGoapState mTargetState;
        /// <summary>
        /// 规划得到的行为序列
        /// </summary>
        protected Queue<TestGoapAction> mActionQueue;

        public string Name { get; set; }
        public TestGoapState TargetState => mTargetState;
        
        /// <summary>
        /// 优先级,可以在子类中根据需求定制规则,既可以是静态也可以是动态
        /// </summary>
        /// <returns></returns>
        public virtual int GetPriority() => mTargetState.Count;

        public TestGoapGoal()
        {
            mTargetState = TestGoapState.Pop();
            mActionQueue = new();
            OnCreate();
        }
        protected virtual void OnCreate() {}

        public void ResetActionQueue(TestGoapAStarNode _node)
        {
            mActionQueue.Clear();
            while (null != _node)
            {
                TestGoapAction _action = _node.GetAction();
                if (null != _action)
                {
                    mActionQueue.Enqueue(_action);
                }
                _node = _node.GetParentNode();
            }
        }

        public void Run()
        {
            Debug.Log($"-----------------------------------------");
            Debug.Log($" =====> 执行目标 : 【{Name}】");
            while (mActionQueue.Count > 0)
            {
                TestGoapAction _curAction = mActionQueue.Dequeue();
                _curAction.Run();
            }
        }
    }

    public class TestGoapAction
    {
        /// <summary>
        /// 前置条件
        /// </summary>
        protected TestGoapState mPreconditionState;
        /// <summary>
        /// 效果
        /// </summary>
        protected TestGoapState mEffectState;

        public string Name { get; set; }
        public TestGoapState PreConditions => mPreconditionState;
        public TestGoapState Effects => mEffectState;
        /// <summary>
        /// cost值,用于规划路径
        /// 可以在子类中定制,既可以是静态也可以是动态
        /// </summary>
        /// <returns></returns>
        public int GetCost() => mEffectState.Count;

        public TestGoapAction()
        {
            mPreconditionState = TestGoapState.Pop();
            mEffectState = TestGoapState.Pop();
            OnCreate();
        }
        protected virtual void OnCreate() {}

        public void Run()
        {
            Debug.Log($" ===========> 执行行为 : 【{Name}】");
        }
    }

    public class TestGoapAgent
    {
        private TestGoapState mMemory;
        private List<TestGoapGoal> mAbleGoals;
        private List<TestGoapAction> mAbleActions;

        public TestGoapGoal CurrentGoal { get; private set; }
        public TestGoapState Memory => mMemory;
        public List<TestGoapGoal> AbleGoals => mAbleGoals;
        public List<TestGoapAction> AbleActions => mAbleActions;

        public TestGoapAgent()
        {
            mMemory = TestGoapState.Pop();
            mAbleGoals = new();
            mAbleActions = new();
        }

        public void SetCurrentGoal(TestGoapGoal _goal)
        {
            Debug.LogError($"Agent设置新的目标 : {_goal.Name} !!!");
            CurrentGoal = _goal;
        }

        public void RunCurrentGoal()
        {
            if (null == CurrentGoal)
            {
                return;
            }
            CurrentGoal.Run();
        }

        /// <summary>
        /// 优先级越大越优先
        /// </summary>
        /// <param name="_goal"></param>
        public void AddAbleGoal(TestGoapGoal _goal)
        {
            int _priority = _goal.GetPriority();
            for (int i = 0; i < mAbleGoals.Count; i++)
            {
                if (_priority > mAbleGoals[i].GetPriority())
                {
                    continue;
                }
                mAbleGoals.Insert(i, _goal);
                return;
            }
            mAbleGoals.Add(_goal);
        }

        /// <summary>
        /// Cost越小越优先
        /// </summary>
        /// <param name="_action"></param>
        public void AddAbleAction(TestGoapAction _action)
        {
            int _cost = _action.GetCost();
            for (int i = 0; i < mAbleActions.Count; i++)
            {
                if (_cost < mAbleActions[i].GetCost())
                {
                    continue;
                }
                mAbleActions.Insert(i, _action);
                return;
            }
            mAbleActions.Add(_action);
        }
    }
}

Planner:

using System.Collections.Generic;
using UnityEngine;

namespace MapRandom.Goap
{
    public class TestGoapPlanner
    {

        public void Plan(TestGoapAgent _agent, TestGoapAStar _aStar)
        {

            List<TestGoapGoal> _possibleGoals = _agent.AbleGoals;
            for (int i = _possibleGoals.Count - 1; i >= 0; i--)
            {
                TestGoapGoal _tmpGoal = _possibleGoals[i];
                
                //第一步,粗筛,目标是否能被Action列表覆盖
                if (!BroadCheckAction(_agent, _tmpGoal)) continue;
                
                //第二步,尝试规划Action路径
                TestGoapAStarNode _startNode = TestGoapAStarNode.Pop();
                _startNode.Init(_agent, _tmpGoal.TargetState, null, null);
                TestGoapAStarNode _retNode = _aStar.Search(_startNode);
                if (null == _retNode)
                {
                    //没有找到可行路径
                    Debug.Log($"A* 搜索失败!!! 没有可行的Action路径!!! 目标: {_tmpGoal.Name}");
                    continue;
                }
                
                //第三步,用搜索到的路径填充Action列表,并为Agent设置当前目标
                _tmpGoal.ResetActionQueue(_retNode);
                _agent.SetCurrentGoal(_tmpGoal);
                return;
            }
            
        }

        private bool BroadCheckAction(TestGoapAgent _agent, TestGoapGoal _goal)
        {
            TestGoapState _goalState = TestGoapState.Pop();
            _goalState.Clone(_goal.TargetState);
            
            List<TestGoapAction> _ableActions = _agent.AbleActions;
            foreach (TestGoapAction _action in _ableActions)
            {
                _goalState.RemoveSameToSelf(_action.Effects);
                if (0 == _goalState.Count)
                {
                    Debug.LogError($"BroadCheckAction 检查成功!!! Action可以覆盖目标: {_goal.Name}");
                    return true;
                }
            }
            
            Debug.Log($"BroadCheckAction 检查失败!!! Action不能覆盖目标: {_goal.Name}");
            return false;
        }

    }
}

A*:

using System;
using System.Collections.Generic;
using MapRandom.MapRandomCommon;

namespace MapRandom.Goap
{
    public class TestGoapAStar
    {
        private MinPriorityQueue<TestGoapAStarNode, int> mOpenList = new();
        private Dictionary<TestGoapState, TestGoapAStarNode> mCloseList = new();

        public TestGoapAStarNode Search(TestGoapAStarNode _startNode)
        {
            mOpenList.Enqueue(_startNode, _startNode.GetCost());
            while (mOpenList.TryDequeue(out (TestGoapAStarNode node, int priority) _tuple))
            {
                var _node = _tuple.node;
                if (_node.CheckGoalFinish())
                {
                    //目标被满足,找到终点
                    return _node;
                }

                mCloseList.Add(_node.GetSubMemory(), _node);
                List<TestGoapAStarNode> _expandList = _node.Expand();
                foreach (var _childNode in _expandList)
                {
                    if (mCloseList.ContainsKey(_childNode.GetSubMemory()))
                    {
                        continue;
                    }
                    mOpenList.Enqueue(_childNode, _childNode.GetCost());
                }
            }

            return null;
        }
    }

    public class TestGoapAStarNode : IComparable<TestGoapAStarNode>
    {
        #region Pool

        private static Stack<TestGoapAStarNode> mPool = new();

        public static TestGoapAStarNode Pop()
        {
            TestGoapAStarNode _ret = 0 == mPool.Count ? new TestGoapAStarNode() : mPool.Pop();
            return _ret;
        }

        public static void Push(TestGoapAStarNode _node)
        {
            _node.Clear();
            mPool.Push(_node);
        }

        #endregion

        private List<TestGoapAStarNode> mExpandList = new();

        private TestGoapAgent mAgent;
        private TestGoapAction mAction;
        private TestGoapAStarNode mParentNode;
        private TestGoapState mCompareToTarget;
        private TestGoapState mSubMemory;
        private TestGoapState mRemainGoalState;

        private int G;
        private int H;
        
        public TestGoapAction GetAction() => mAction;
        public TestGoapAStarNode GetParentNode() => mParentNode;
        public TestGoapState GetSubMemory() => mSubMemory;
        public int GetCost() => G + H;
        public bool CheckGoalFinish() => 0 == mCompareToTarget.Count;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_agent"></param>
        /// <param name="_remainGoalState"></param>
        /// <param name="_action"></param>
        /// <param name="_parent"></param>
        public void Init(TestGoapAgent _agent, TestGoapState _remainGoalState, TestGoapAction _action, TestGoapAStarNode _parent)
        {
            mAgent = _agent;
            mAction = _action;
            mParentNode = _parent;

            mSubMemory = TestGoapState.Pop();
            if (null == _parent)
            {
                //是根节点
                G = 0;
                mSubMemory.Clone(_agent.Memory);
            }
            else
            {
                G = _parent.GetCost();
                mSubMemory.Clone(_parent.GetSubMemory());
            }
            
            mRemainGoalState = TestGoapState.Pop();
            mRemainGoalState.Clone(_remainGoalState);
            if (null != _action)
            {
                TestGoapState _preConditions = _action.PreConditions;
                TestGoapState _effects = _action.Effects;
                
                mSubMemory.CoverByOther(_effects);
                
                //当前节点能够实现的效果,从剩余目标当中消掉
                mRemainGoalState.RemoveSameToSelf(_effects);
                //当前节点要能执行需要的前置条件,添加到剩余目标当中
                mRemainGoalState.CoverByOther(_preConditions);
                G += _action.GetCost();
            }
            //先简单粗暴的将H设置为剩余目标数量
            H = mRemainGoalState.Count;
            
            //至此,mRemainGoalState里留下的就是搜索至当前节点时,还剩余的目标
            //这些目标中有的可能是在记忆中已经满足的,收集实际的差异,用于判定是否A*结束
            //但是在A*的Search中,当Node弹出查找邻接点时,使用的是更宽泛的mRemainGoalState,可以优化组合
            mCompareToTarget = TestGoapState.Pop();
            mRemainGoalState.RemoveSameToCollector(_agent.Memory, mCompareToTarget);
        }

        /// <summary>
        /// 找相邻节点
        /// </summary>
        /// <returns></returns>
        public List<TestGoapAStarNode> Expand()
        {
            mExpandList.Clear();
            List<TestGoapAction> _ableActions = mAgent.AbleActions;
            for (int i = _ableActions.Count - 1; i >= 0; i--)
            {
                TestGoapAction _action = _ableActions[i];
                if (_action == mAction) continue;

                TestGoapState _preConditions = _action.PreConditions;
                TestGoapState _effects = _action.Effects;
                 
                if (_effects.HasAnySameOf(mRemainGoalState) &&  //必须对剩余目标的达成有帮助 
                    !mRemainGoalState.HasAnyConflictOfBoth(_preConditions, _effects) &&  //前置条件不能跟目标有冲突,或者即使有冲突也会被行为的效果修复
                    !mRemainGoalState.HasAnyConflictOf(_effects) //一个行为可能有多个效果,因此,当其中一个效果有助于达成目标,但另一个效果会破坏目标时也不可以
                    )
                {
                    TestGoapAStarNode _childNode = TestGoapAStarNode.Pop();
                    _childNode.Init(mAgent, mRemainGoalState, _action, this);
                    mExpandList.Add(_childNode);
                }
            }
            return mExpandList;
        }

        public void Clear()
        {
            mExpandList.Clear();
            //...
        }

        public int CompareTo(TestGoapAStarNode other)
        {
            return GetCost().CompareTo(other.GetCost());
        }
    }
}

Test :

using UnityEngine;

namespace MapRandom.Goap
{
    /// <summary>
    /// 目标 -- 吃饭
    /// </summary>
    public sealed class TestGoapGoal_Eat : TestGoapGoal
    {
        protected override void OnCreate()
        {
            base.OnCreate();
            Name = "目标 -- 吃饭";
            
            mTargetState.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.Hungry,
                IntValue = 0,
            });
            
        }
    }

    /// <summary>
    /// 目标 -- 睡觉
    /// </summary>
    public sealed class TestGoapGoal_Sleep : TestGoapGoal
    {
        protected override void OnCreate()
        {
            base.OnCreate();
            Name = "目标 -- 睡觉";
            
            mTargetState.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.Tired,
                IntValue = 0,
            });
        }

        public override int GetPriority()
        {
            return 100;
        }
    }

    /// <summary>
    /// 行动 -- 进食
    /// </summary>
    public sealed class TestGoapAction_EatFood : TestGoapAction
    {
        protected override void OnCreate()
        {
            base.OnCreate();
            Name = "行动 -- 进食";
            
            mPreconditionState.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.HasFood,
                IntValue = 1,
            });
            mPreconditionState.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.InKitchen,
                IntValue = 1,
            });
            
            mEffectState.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.Hungry,
                IntValue = 0,
            });
        }
    }

    /// <summary>
    /// 行动 -- 前往厨房
    /// </summary>
    public sealed class TestGoapAction_GoToKitchen : TestGoapAction
    {
        protected override void OnCreate()
        {
            base.OnCreate();
            Name = "行动 -- 前往厨房";
            
            mEffectState.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.InKitchen,
                IntValue = 1,
            });
        }
    }

    /// <summary>
    /// 行动 -- 烹饪
    /// </summary>
    public sealed class TestGoapAction_Cook : TestGoapAction
    {
        protected override void OnCreate()
        {
            base.OnCreate();
            Name = "行动 -- 烹饪";
            
            mPreconditionState.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.InKitchen,
                IntValue = 1,
            });
            
            mEffectState.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.HasFood,
                IntValue = 1,
            });
        }
    }

    public class TestGoapMain : MonoBehaviour
    {
        private TestGoapState mWorldState;
        private TestGoapAgent mAgent;
        private TestGoapAStar mAStar;
        private TestGoapPlanner mPlanner;
        
        private void Update()
        {
            if (Input.GetKeyUp(KeyCode.F2))
            {
                Initialize();
                mPlanner.Plan(mAgent, mAStar);
                mAgent.RunCurrentGoal();
            }
        }

        private void Initialize()
        {
            InitTools();
            InitWorldState();
            InitAgent();
        }

        private void InitTools()
        {
            mAStar = new TestGoapAStar();
            mPlanner = new TestGoapPlanner();
        }

        private void InitWorldState()
        {
            mWorldState = TestGoapState.Pop();
            // mWorldState.AddGoapTag();
        }

        private void InitAgent()
        {
            mAgent = new TestGoapAgent();
            
            mAgent.AddAbleGoal(new TestGoapGoal_Eat());
            mAgent.AddAbleGoal(new TestGoapGoal_Sleep());
            
            mAgent.AddAbleAction(new TestGoapAction_Cook());
            mAgent.AddAbleAction(new TestGoapAction_EatFood());
            mAgent.AddAbleAction(new TestGoapAction_GoToKitchen());
            
            mAgent.Memory.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.Hungry,
                IntValue = 1,
            });
            mAgent.Memory.AddGoapTag(new TestGoapTag()
            {
                TagType = TestGoapTagType.Tired,
                IntValue = 1,
            });
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值