GOAP 目标导向型行为计划 AI 算法
全称: Goal-Oriented Action Planning
游戏开发AI 实现,一般就是 FSM(有限状态机)、HFSM(分层/层次有限状态机)、BehaviorTree(行为树)
他们需要提前设计好 行为、状态之间的切换
当需要添加或者减少 行为、状态 的时候,需要重新配置,甚至需要挺大的改动
FSM(有限状态机)、HFSM(分层/层次有限状态机)、BehaviorTree(行为树) 这种根据配置,读取环境信息,然后根据配置找到分支,然后找到执行的行为,属于 Reactive AI(反应型 AI,先接受刺激输入,然后执行对应行为)
目标导向型行动计划(简称GOAP) 是让AI 自己去找到解决问题的方法
是一种能够轻松呈现给你的代理选择的AI系统,也是帮助你可以无需维持一个庞大且复杂的有限状态机、行为树 而做出明智决策的机器,GOAP 属于 慎思型AI 将环境和背景条件纳入决策考量,可以胜任复杂决策
目标导向型行为计划,参考 STRIPS-like:专门设计用于游戏中自主角色行为实时控制的规划体系结构。
STRIPS规划的简单介绍
STRIPS是由斯坦福大学(StanfordUniversity)于1970年开发的,名称是斯坦福研究所问题解决者(Stanford Research Problem Solver)的首字母缩写。 一个Action只有在其所有前提条件都得到满足的情况下才能执行,并且每个动作都会以某种方式改变世界的状态
四个主要类:
1.Agent代理: 主体,负责连接各个类,主要逻辑在这里运行,游戏中可以是 Player、NPC 等具体的实现类,AI 就是控制 Agent 执行各种行为的。
2.Action:动作: 可以被执行的动作,比如:拿东西、丢东西、吃饭、伐木等,
每个 Action 有需要有:执行的先决条件 preCondition,执行后的效果 effect
每个 Action 的执行需要设定一个代价:Cost
如 吃饭行为
执行的先决条件:饿了、 有食物
执行后的效果: 解决饥饿
如 伐木行为
执行的先决条件:有树木、有斧头
执行后的效果:获得木头
3.IGoap: 数据提供者,提供给 Agent 目标和反馈是否成功失败等等,主要用于描述世界状态简称环境变量、Agent 目标
环境变量:有树木、有斧头、有食物、体力值、拥有金币数、有矿山、矿山位置
Agent 目标:拥有木头、解决饥饿、获得金币、获取矿石
4.Planer: 决策者, 通过各个类来决策出最优的路线供Agent使用,
执行逻辑:大体思路是依据 AStar 寻路算法的思想,从目标开始往前查找 Action,如果Action 的效果能够完成至少一个目标,则将 Action 的效果写入到环境变量,然后将 Action 的前置条件写入到目标,继续往前查找
下面名词解释:action 行为,worldStatus 环境变量,agentGoal 目标, cost 消耗值,Action preCondition 先决条件,Action effect 执行效果
逻辑图如下
下面伪代码展示下 Planer 执行逻辑
worldStatus -- 环境变量
agentGoal -- Agent 目标
获取所有可用的 Action,并依据Action的 Cost 从小到大排序得到 ActionList
local resultNode = nil -- 最终结果
local heap = Heap.new() -- 创建小根堆
local parentNode = nil
local action = nil
local cost = 0
local node = Node.new(parentNode, action, worldStatus, agentGoal, cost)
heap.insert(node)
while heap.count > 0 do
local node = heap.DelNode() -- 获取堆顶节点并且删除
-- node.action 不为空 并且 node 的先决条件 preCondition 都包含在 node.worldStatus
if (null ~= node.action) and action.preCondition.IsContainIn(node.worldStatus) then
-- 最终可执行的结果
resultNode = node
break
end
foreach action in ActionList do -- 遍历所有 Action
-- 如果 action 不可用,跳过
if not action.EnableUse() then
continue
end
local newWorldStatus = node.worldStatus.Clone() -- 节点的 环境变量 克隆一份
local newAgentGoal = node.agentGoal.Clone() -- 节点的 Agent目标 克隆一份
-- 要求 Action 的执行效果 action.effect,至少有一个包含在agentGoal
if not action.effect.IsAnyContain(node.agentGoal) then
continue
end
-- 要求 action 的先决条件与不能与目标冲突
if (action.preCondition.HasAnyConflict(goalStatus)) then
continue
end
-- 筛选出来一个可以执行的 Action 了
-- 将 action 的执行效果(action.effect),写入到 新的环境变量(newWorldStatus)中
newWorldStatus.AddFrom(action.effect)
-- 将 action 的执行效果(action.effect),从 新Agent目标(newAgentGoal)中移除
newAgentGoal.RemoveFromStatus(action.effect)
-- 将 action 的先决条件(action.preCondition) 中的值写入 newAgentGoal
newAgentGoal.AddFromStatus(action.preCondition);
local cost = node.cost + action.cost
local newNode = Node.new(node, action, newWorldStatus, newAgentGoal, cost)
heap.Insert(newNode)
do
end
if nil == resultNode then
return
end
local stack = stack .new() -- 栈,LIFO 后进先出
while nil ~= resultNode and resultNode .parentNode ~= nil do
local parentNode = resultNode .parentNode
queue.Push(parentNode)
resultNode = resultNode .parentNode
end
例子:
AI 饿了,要找 食物 填饱肚子,
环境状态包含: 饿了
AI 的目标就是 解决饥饿
每个行为都是有先决条件和执行效果。行为是否能够执行必须要行为的效果是否能达成目标,然后是先决条件是否满足
Action | 先决条件 | 执行效果 | Cost |
---|---|---|---|
吃东西 | 有食物 | 解决饥饿 | 5 |
做饭 | 有菜 | 有食物 | 10 |
买菜 | 有超市 | 有菜 | 15 |
去饭店 | 有饭店 | 解决饥饿 | 30 |
翻冰箱找面包 | 有食物 | 解决饥饿 | 30 |
以上五中行为可以组合成 3 组 达到 解决饥饿 的目的。
GOAP 缺点
1.GOAP学习成本偏高
2.GOAP默认不保证Action顺序,如果我们想要强制指定顺序,需要额外加State(就像上面那个例子一样),但是一般而言我们的AI也不会去在意这种无关紧要的顺序,因为重要的顺序我们已经规范好了。
3.GOAP毕竟使用了运行时规划,会对游戏性能产生一定的影响,但是这种目标导向的A*规划在AI复杂的情况下,性能并不一定比FSM/行为树弱
GOAP优点
1.解耦目标和行为,设计时可以更加专注的设计AI的行为不用过多考虑目标,新增,减少Action非常方便,不会像FSM/行为树那样具有那么强的入侵性和破坏性,提高了开发效率
2.动态规划的能力,这会使我们的AI看起来更加“智能”
3.配置方便,易于理解,完全可以让策划使用EXCEL接管AI开发工作