项目笔记N-205
AI模组
- 更新
- 内容块
- 116
- 阅读
- 约 6 分钟
- 路径
- 项目笔记 / CS / 2026.7.7 / AI 模组
本项目没有引入 BehaviorTree.CPP、Unreal Behavior Tree、GOAP 等第三方 AI 框架,也没有用 XML/JSON 配一棵可视化行为树。它采用的是“行为树思想 + 有限状态机 + 优先级选择器 + 导航图”的 C++ 直接实现。
目前采用在每一帧都进行行为逻辑判断
- 1.我现在知道什么?
- 2.我现在最应该做什么?
- 3.我该怎么把这个决定变成动作?
- •情报层:
updateTeamIntel()
- •战术层:
buildRoundPlan()、updateTeamTacticalPlan()
- •决策层:
selectBotIntent()
- •执行层:
executeBotIntent()、followObjective()、engageEnemy()
- •运动层:
findPath()、steerToTarget()、moveToTarget()
- •武器层:
weapon::WeaponSimulator::tryFire()
- •Selector:选择器,从上到下尝试子节点,谁能处理就执行谁。适合表达“优先级”。
- •Sequence:顺序节点,多个条件和动作依次通过才算成功。适合表达“先判断、再执行”。
- •Condition:条件节点,例如“我能看到敌人吗?”
- •Action:动作节点,例如“开火”“下包”“移动到掩体”。
- •Blackboard:黑板,所有节点共享的状态数据,例如敌人位置、目标点、当前意图。
调用链
感知 -> 团队计划 -> 个人决策 -> 执行动作 -> 修正世界状态
Game::update(...)
repairBombInvariant("pre_update")
updateBots(dt)
updateTeamIntel()
updateTeamTacticalPlan()
for each bot:
死亡/冻结/回合结束快速处理
updateBotMemoryAndStuck()
findVisibleEnemy()
selectBotIntent()
executeBotIntent()
repairBombInvariant("post_bots")数据结构设计
5.1 AIState:粗粒度状态机
AIState 是 Bot 当前所处的粗状态:
enum class AIState {
Freeze,
PlanRoute,
MoveToObjective,
EngageEnemy,
TakeCover,
PlantBomb,
DefuseBomb,
PostPlantHold,
Retake,
Rotate,
Dead
};5.2 BotRole:队内职责
BotRole 描述 Bot 在队伍里的长期角色:
enum class BotRole {
Entry,
Support,
Lurker,
Sniper,
Objective
};- •
Entry:突破手,先拉出去,占空间,吃第一波交火。(一突)
- •
Support:补枪位,跟 Entry,必要时丢闪。(二突)
- •
Lurker:绕后/侧翼,制造牵制。(先锋)
- •
Sniper:长枪线架点。(哨位)
- •
Objective:目标位,优先带包、下包、拆包。(哨位,烟位)
5.3 BotIntent:当前意图
BotIntent 是当前帧或当前短时间内 Bot 具体想做的事:
enum class BotIntent {
None,
TakeSpace,抢占空间 / 占位
TradeEntry,补枪突入 / 协同进入
Lurk,潜伏 / 阴人
HoldAngle,架点 / 卡住角度
RotateByIntel,根据情报转点
Engage,交战 / 接敌
TakeCover,寻找掩体 / 躲避
Plant,安放炸弹
Defuse, 拆弹
PostPlantHold, 安放后防守 / 守包
SearchLastKnown,搜索最后已知位置
Repath,重新寻路
IdleScan 空闲扫描 / 闲置观察
};5.4 ObjectiveType:目标优先级分类
ObjectiveType 比 BotIntent 更偏“任务重要性”:
enum class ObjectiveType {
None,
Combat,
PlantBomb,
RecoverBomb,
DefuseBomb,
DefendBomb,
TakeSpace,
Rotate,
HoldAngle,
Trade,
Lurk,
TakeCover,
SearchLastKnown,
ExitSpawn
};objectivePriority() 给不同任务打优先级:
DefuseBomb = 120
PlantBomb = 110
RecoverBomb = 105
DefendBomb = 95
Trade = 88
Combat = 82
TakeCover = 80
ExitSpawn = 50
TakeSpace = 45
...5.5 BotObjective:当前选中的黑板目标
BotObjective 是本项目 AI 最接近“行为树黑板任务”的结构:
struct BotObjective {
ObjectiveType type;
BotIntent intent;
AIState state;
Vec3 moveTarget;
Vec3 aimTarget;
Vec3 fireTarget;
int targetNode;
float score;
float lockTimer;
bool interruptible;
const char* source;
};字段解释:
- •
type:任务类型,例如PlantBomb。
- •
intent:执行意图,例如Plant。
- •
state:粗状态,例如AIState::PlantBomb。
- •
moveTarget:移动目标位置。
- •
aimTarget:预瞄目标位置。
- •
fireTarget:开火目标位置。
- •
targetNode:导航图目标节点。
- •
score:该目标得分或权重。
- •
lockTimer:短时间锁定,防止每帧来回切换。
- •
interruptible:是否允许低优先级行为打断。
- •
source:谁设置了这个目标,用于调试,例如"VisibleEnemy"、"CarrierAtSite"。
5.6 Bot:每个 Bot 的完整状态
Bot 是 AI 的主体,里面混合了位置、生命、武器、路线、记忆、战斗状态和当前目标:
struct Bot {
Team team; // 队伍(所属阵营)
BotRole role; // Bot角色(如突破、支援、狙击等)
BotIntent intent; // Bot意图(当前选中的行为意图)
BotObjective objective; // Bot目标(具体的任务目标,如安包点、拆弹点等)
Vec3 pos; // 位置(三维坐标)
float yaw; // 偏航角(朝向)
int health; // 生命值
bool alive; // 是否存活
bool hasBomb; // 是否携带炸弹
WeaponState gun; // 武器状态(当前武器、弹夹、备弹等)
AIState state; // AI状态(行为子状态或阶段)
std::vector<int> route; // 路径点列表(导航节点索引序列)
int routeIndex; // 当前路径点索引(正在前往的目标节点位置)
int targetEnemy; // 目标敌人实体ID
DuelState duel; // 对决状态(与敌人交火时的子状态)
float reaction; // 反应时间(用于模拟人类反应延迟)
float plantProgress; // 安放炸弹进度(0~1)
float defuseProgress; // 拆弹进度(0~1)
float memoryAge; // 记忆年龄(上次看到/听到敌人的时间衰减)
float heardAge; // 听觉信息年龄(上次听到声音的时间)
float stuckTimer; // 卡住计时器(用于检测是否被障碍物卡住)
Vec3 lastKnownEnemy; // 最后已知敌人位置
Vec3 objectivePos; // 目标位置(与objective对应的具体坐标)
Vec3 moveTarget; // 移动目标点(当前导航目标)
Vec3 aimTarget; // 瞄准目标点(准星指向)
Vec3 fireTarget; // 开火目标点(射击瞄准点)
...
};从行为树角度看,Bot 就是单个 AI 的 Blackboard。
5.7 SharedIntel:团队共享情报
SharedIntel 存团队级信息:
struct SharedIntel {
bool enemySpotted; // 是否发现敌人(至少一名可见敌人)
Vec3 lastEnemyPos; // 最后已知敌人位置(最后被看到或听到的位置)
float age; // 信息年龄(该条情报的时效,单位秒)
bool bombKnown; // 是否已知炸弹位置(已探明或队友报告)
Vec3 bombPos; // 炸弹位置(已安放或掉落时的坐标)
bool bombSiteA; // 是否为A包点(标识炸弹所在包点是A还是B)
int aliveCT; // CT方存活人数(警家剩余人数)
int aliveT; // T方存活人数(匪家剩余人数)
int pressureA; // A区压力值(敌人活动强度或进攻权重,数值越高威胁越大)
int pressureB; // B区压力值(同上,B区)
Vec3 ctCentroid; // CT方重心位置(所有存活CT的平均坐标)
Vec3 tCentroid; // T方重心位置(所有存活T的平均坐标)
};5.8 RoundPlan:T 方回合战术
RoundPlan 是 T 方本回合计划:
struct RoundPlan {
TeamTactic tactic; // 当前团队战术(如推进、防守、转点等)
bool targetSiteA; // 是否以A包点为目标(true为A,false为B)
int rallyNode; // 集结节点(队员集合汇合的导航节点)
int mainGoalNode; // 主要目标节点(主攻/主防的路径节点)
int supportGoalNode; // 支援目标节点(辅助位置的路径节点)
int sniperNode; // 狙击手位置节点(负责架狙的站位)
int flankNode; // 侧翼节点(绕后或侧翼包抄的位置)
float tacticAge; // 战术存活时间(当前战术方案已持续的时间,秒)
float reevaluateTimer; // 重新评估计时器(距离下次重新评估战术的剩余时间)
bool flashIssued; // 是否已发出闪光弹指令(或已部署闪光弹支援)
};它由 buildRoundPlan() 创建。tactic 可能是:
- •
Default
- •
RushA
- •
RushB
- •
FakeRotate
- •
EcoPick
- •
Auto解析后的具体战术
5.9 TeamTacticalPlan:每帧动态团队计划
TeamTacticalPlan 是比 RoundPlan 更实时的计划:
struct TeamTacticalPlan {
TeamPlanIntent tIntent; // T方战术意图
TeamPlanIntent ctIntent; // CT方战术意图
int tPrimaryNode; // T方主要目标节点
int tSecondaryNode; // T方次要目标节点
int ctPrimaryNode; // CT方主要目标节点
int ctSecondaryNode; // CT方次要目标节点
int defuserIndex; // 拆弹者索引(负责拆弹的Bot编号)
float tPressureA; // T方在A点的压力值
float tPressureB; // T方在B点的压力值
float ctPressureA; // CT方在A点的压力值
float ctPressureB; // CT方在B点的压力值
};5.10 Map / NavNode:导航图
AI 不是自由寻路,而是在手写导航图上走:
struct NavNode {
Vec3 pos;
std::vector<int> links;
bool siteA;
bool siteB;
bool cover;
bool valid;
std::string label;
};
struct Map {
std::vector<Box> boxes;
std::vector<NavNode> nodes;
std::vector<NavEdgeInfo> edgeInfo;
Vec3 ctSpawn;
Vec3 tSpawn;
Vec3 siteA;
Vec3 siteB;
bool authoredNavLinks;
};行为树在选中节点时的落地(决策和执行的连接点)
不直接intent,而是调用:
setActiveObjective(
b,
ObjectiveType::PlantBomb,
BotIntent::Plant,
AIState::PlantBomb,
site,
site,
{},
nearestNode(site),
1.0f,
false,
"CarrierAtSite"
);- 1.判断新目标和旧目标是否相同。
- 2.如果旧目标不可打断,并且新目标优先级更低,则拒绝切换。
- 3.写入
BotObjective。
- 4.同步
b.intent、b.state、b.objectivePos、b.moveTarget。
- 5.如果目标节点变了,清空旧路线,准备重新寻路。
- 6.设置
lockTimer,防止目标每帧抖动。