目标导向的AI系统核心是让智能体根据当前环境状态,自主选择一系列动作达成预设目标,Goap(Goal-Oriented Action Planning)算法正是实现这一能力的经典方案,它通过定义世界状态、可用动作和目标条件,自动生成最优行动序列。

Goap算法核心概念
要实现Goap AI系统,首先需要明确三个核心组成部分:
- 世界状态:描述当前环境的所有关键属性,比如角色是否有武器、目标是否在视野内、当前生命值等,通常用键值对结构存储。
- 动作:智能体可以执行的操作,每个动作包含前置条件、执行效果和消耗成本,只有满足前置条件时动作才能被执行。
- 目标:智能体需要达成的状态条件,规划器会根据当前状态和目标状态,筛选出可用的动作序列。
C++基础结构定义
首先定义世界状态的结构,使用枚举加映射的方式存储状态键值对:
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <queue>
#include <algorithm>
// 状态键枚举,可根据需求扩展
enum class StateKey {
HasWeapon,
TargetInSight,
HealthFull,
AtTargetPosition
};
// 世界状态结构,存储键值对状态
struct WorldState {
std::unordered_map<StateKey, bool> states;
// 检查是否满足目标条件
bool Match(const std::unordered_map<StateKey, bool>& target) const {
for (const auto& [key, value] : target) {
auto it = states.find(key);
if (it == states.end() || it->second != value) {
return false;
}
}
return true;
}
// 应用动作效果,更新状态
void Apply(const std::unordered_map<StateKey, bool>& effects) {
for (const auto& [key, value] : effects) {
states[key] = value;
}
}
};
动作类实现
动作需要包含前置条件、执行效果和成本,定义动作基类如下:
struct GoapAction {
std::string name;
// 前置条件:执行该动作需要满足的状态
std::unordered_map<StateKey, bool> preconditions;
// 执行效果:动作执行后改变的状态
std::unordered_map<StateKey, bool> effects;
// 动作执行成本,用于规划时选择最优序列
float cost;
GoapAction(std::string actionName, float actionCost)
: name(std::move(actionName)), cost(actionCost) {}
// 检查当前状态是否满足前置条件
bool CheckPreconditions(const WorldState& currentState) const {
for (const auto& [key, value] : preconditions) {
auto it = currentState.states.find(key);
if (it == currentState.states.end() || it->second != value) {
return false;
}
}
return true;
}
};
规划器实现
规划器采用A*算法思路,从当前状态出发,不断尝试可用动作,直到找到满足目标状态的动作序列:
struct PlanNode {
WorldState state;
std::vector<const GoapAction*> actions;
float totalCost;
// 优先队列需要比较总代价
bool operator>(const PlanNode& other) const {
return totalCost > other.totalCost;
}
};
class GoapPlanner {
public:
// 规划动作序列,返回从当前状态到目标状态的动作列表
std::vector<const GoapAction*> Plan(const WorldState& startState,
const std::unordered_map<StateKey, bool>& goal,
const std::vector<GoapAction>& availableActions) {
std::priority_queue<PlanNode, std::vector<PlanNode>, std::greater<PlanNode>> openList;
std::vector<PlanNode> closedList;
// 初始节点
openList.push({startState, {}, 0.0f});
while (!openList.empty()) {
PlanNode currentNode = openList.top();
openList.pop();
// 检查是否达成目标
if (currentNode.state.Match(goal)) {
return currentNode.actions;
}
// 遍历所有可用动作
for (const auto& action : availableActions) {
// 检查动作前置条件是否满足
if (!action.CheckPreconditions(currentNode.state)) {
continue;
}
// 生成新状态
WorldState newState = currentNode.state;
newState.Apply(action.effects);
// 检查新状态是否已经在关闭列表中
bool inClosed = false;
for (const auto& closedNode : closedList) {
if (closedNode.state.states == newState.states) {
inClosed = true;
break;
}
}
if (inClosed) {
continue;
}
// 生成新节点
std::vector<const GoapAction*> newActions = currentNode.actions;
newActions.push_back(&action);
PlanNode newNode{newState, newActions, currentNode.totalCost + action.cost};
openList.push(newNode);
}
closedList.push_back(currentNode);
}
// 没有找到可行路径,返回空序列
return {};
}
};
完整使用示例
下面定义一个简单的战斗场景,智能体需要先获取武器,再移动到目标位置,最后攻击目标:
int main() {
// 初始化可用动作
std::vector<GoapAction> actions;
// 动作1:拾取武器,前置条件不需要武器,效果获得武器,成本1
GoapAction pickWeapon("PickWeapon", 1.0f);
pickWeapon.preconditions = {{StateKey::HasWeapon, false}};
pickWeapon.effects = {{StateKey::HasWeapon, true}};
actions.push_back(pickWeapon);
// 动作2:移动到目标位置,前置条件目标不在视野内,效果到达目标位置,成本2
GoapAction moveToTarget("MoveToTarget", 2.0f);
moveToTarget.preconditions = {{StateKey::AtTargetPosition, false}};
moveToTarget.effects = {{StateKey::AtTargetPosition, true}};
actions.push_back(moveToTarget);
// 动作3:攻击目标,前置条件有武器且在目标位置,效果目标被击败(这里简化为目标不在视野内),成本3
GoapAction attackTarget("AttackTarget", 3.0f);
attackTarget.preconditions = {{StateKey::HasWeapon, true}, {StateKey::AtTargetPosition, true}};
attackTarget.effects = {{StateKey::TargetInSight, false}};
actions.push_back(attackTarget);
// 初始化当前世界状态
WorldState currentState;
currentState.states = {
{StateKey::HasWeapon, false},
{StateKey::TargetInSight, true},
{StateKey::AtTargetPosition, false}
};
// 定义目标:目标不在视野内(即击败目标)
std::unordered_map<StateKey, bool> goal = {{StateKey::TargetInSight, false}};
// 创建规划器并生成计划
GoapPlanner planner;
std::vector<const GoapAction*> plan = planner.Plan(currentState, goal, actions);
// 输出计划结果
if (plan.empty()) {
std::cout << "没有找到可行的行动序列" << std::endl;
} else {
std::cout << "生成的行动序列:" << std::endl;
for (const auto* action : plan) {
std::cout << "- " << action->name << " (成本: " << action->cost << ")" << std::endl;
}
}
return 0;
}
优化与扩展方向
上述实现是基础版本,实际使用中可以根据需求扩展:
- 增加状态值的数值类型支持,比如生命值、弹药数量等,不只是布尔类型。
- 给动作添加执行时长、冷却时间等属性,让规划更贴近实际场景。
- 加入动态状态更新机制,当环境状态变化时重新触发规划,实现实时决策。
- 优化规划器的性能,比如加入状态哈希去重,减少重复计算。
通过这套实现,就可以在C++中搭建起基础的Goap目标导向AI系统,让智能体具备自主规划行动的能力,适配游戏NPC、机器人控制等多种场景需求。