项目空间

项目笔记N-208

状态间的转移

更新
内容块
145
阅读
约 4 分钟
路径
项目笔记 / CS / 2026.7.7 / AI 模组

行为树在选中节点时的落地(决策和执行的连接点)

不直接intent,而是调用:

代码块C++
setActiveObjective(
    b,
    ObjectiveType::PlantBomb,
    BotIntent::Plant,
    AIState::PlantBomb,
    site,
    site,
    {},
    nearestNode(site),
    1.0f,
    false,
    "CarrierAtSite"
);
  1. 1.判断新目标和旧目标是否相同。
  1. 2.如果旧目标不可打断,并且新目标优先级更低,则拒绝切换。
  1. 3.写入 BotObjective。
  1. 4.同步 b.intent、b.state、b.objectivePos、b.moveTarget。
  1. 5.如果目标节点变了,清空旧路线,准备重新寻路。
  1. 6.设置 lockTimer,防止目标每帧抖动。

团队战术层如何工作

回合开始:`buildRoundPlan()`

回合开始时 resetRound() 会调用 buildRoundPlan()。

buildRoundPlan() 的逻辑:

  1. 1.resolveTactic() 决定战术。
  1. 2.如果用户设置了固定战术,使用固定战术。
  1. 3.如果是 Auto,根据难度、金钱和随机数选择 Default、RushA、RushB、FakeRotate、EcoPick。
  1. 4.决定目标是 A 还是 B。
  1. 5.选择主目标节点、集合点、支援点、狙击点、侧翼点。

这对应战术 FPS 里的“赛前战术安排”。

每帧:`updateTeamTacticalPlan()`

战术不是回合开始后就不变。每帧还会根据局势调整:

  • •掉包且没人带包:T 的团队意图改成 RecoverBomb。
  • •包已下:T 改成 PostPlantHold,CT 改成 DefuseCover。
  • •RushA/RushB:T 改成 ExecuteA / ExecuteB。
  • •FakeRotate:T 改成 FakeAThenB / FakeBThenA。
  • •发现敌人:CT 可能转 A 或转 B。
  • •人数太少:可能 SaveWeapon。

个人战术层如何工作

selectBotIntent() 是最核心的行为树函数。下面按真实逻辑解释。

掉包和捡包优先

开头先调用 updateBombPickup(b)。如果 Bot 是 T、炸弹掉在地上、Bot 走到炸弹附近,就把炸弹转移给这个 Bot。

如果炸弹掉了且没人带包:

  • •droppedBombRecovererIndex() 选择最适合捡包的 T。
  • •这个 Bot 获得 RecoverBomb 目标。
  • •其他 T 不会都挤过去抢包。

这是一个典型团队任务分配。

CT 拆包逻辑优先级很高

如果回合阶段是 BombPlanted 且 Bot 是 CT:

  • •如果看见敌人,先判断是否必须提交拆包。
  • •如果不是必须拆包,且敌人在炸弹附近,可以先 Engage。
  • •如果有指定 defuser,其他 CT 可能 HoldAngle 掩护。
  • •如果时间不够或非目标角色卡住,可能 SaveWeapon。
  • •否则设置 DefuseBomb。

这相当于:

代码块Plain Text
CT BombPlanted Selector
  VisibleEnemyNearBomb and NotMustCommit -> Engage
  IAmCover -> HoldAngle
  TooLateOrStuck -> SaveWeapon
  Else -> Defuse

带包 T 的特殊逻辑

带包者不能像普通 Bot 一样被所有信息牵着跑。代码对 T 持包者做了特殊处理:

  • •如果带包者已经在包点附近,并且敌人离包点远或不构成近距离威胁,则优先 Plant。
  • •如果带包者还没到包点,即使看见远处敌人,也倾向继续走向包点。
  • •没有其他高优先级事件时,带包者默认 TakeSpace 到目标包点。

这修复了一类常见问题:AI 明明带包到点附近,却因为最后敌人记忆或普通交火不下包。

可见敌人

如果看见敌人:

  • •调用 rememberEnemy() 更新个人记忆和团队情报。
  • •如果低弹量,选 TakeCover。
  • •否则选 Engage。

这对应 FPS 最基础的“看到敌人先处理”的行为。

卡住恢复

如果 Bot 正在执行移动目标,但位置长时间没进展:

  • •进入 Repath。
  • •清理或重建路线。
  • •避开 blockedNode。

这不是传统行为树里常见的战术节点,但对手写导航图非常重要。

包已下后的双方行为

包已下:

  • •T:PostPlantHold,选择守包角度。
  • •CT:Defuse,往炸弹走。

低弹量找掩体

如果弹量低且还有备弹:

  • •设置 TakeCover。
  • •执行层会找掩体并换弹。

T 出生点脱困

如果 T 在出生点口袋区域:

  • •设置 ExitSpawn / TakeSpace。
  • •强制先离开出生点。

这个规则专门解决“Bot 卡 T 家”的实局问题。

记忆和声音

如果没有直接看到敌人,但有最近敌人位置:

  • •SearchLastKnown

如果听到声音:

  • •也转 SearchLastKnown,目标是声音位置。

默认战术

如果以上都没命中,就按团队战术和角色分配默认目标:

  • •Rush:Entry/Objective 向主点,Support 补枪,Sniper 架点,Lurker 侧翼。
  • •EcoPick:更分散,Lurker 或手枪位偏绕。
  • •FakeRotate:部分人制造假打,部分人转点。
  • •Default:按角色默认控图。
  • •CT:默认守角或根据情报转点。

战斗行为怎么搭

对枪入口是 engageEnemy()。

它的大致流程:

代码块Plain Text
目标死了 -> 回到 PlanRoute
根据距离和武器选 WeaponStyle
如果被闪严重 -> 延迟反应/后撤
转向目标
如果狙击太近或敌人太近 -> 后撤换位
如果无射线 -> Peek/找掩体
如果没子弹 -> 换弹找掩体
如果反应时间没到 -> Acquire
如果需要跳拉 -> jump peek + strafe
如果角度或距离不允许开火 -> 追击/后撤
如果需要急停 -> CounterStrafe
横移/跑打
fireBotWeapon()
达到扫射换位阈值 -> Reposition

DuelState

DuelState 表示战斗微状态:

  • •Acquire:捕捉目标/反应中。
  • •CounterStrafe:急停准备开枪。
  • •BurstFire:点射/连发。
  • •Peek:探身或处理视线。
  • •Reposition:换位。

WeaponStyle

WeaponStyle 根据武器和距离决定打法:

  • •PistolLurk
  • •SmgRush
  • •RifleTap
  • •RifleBurst
  • •RifleSpray
  • •SniperHold

这些风格影响:

  • •是否允许开火。
  • •瞄准高度。
  • •散布缩放。
  • •开枪间隔。
  • •是否跑打。
  • •是否后撤。
  • •是否换位。

武器模拟

真正开枪时会调用 weapon::WeaponSimulator::tryFire(),它会考虑:

  • •当前弹匣和冷却。
  • •后坐力预测。
  • •AI 瞄准误差。
  • •站立/空中状态。
  • •是否开镜。
  • •武器散布。

所以 AI 不是简单 if visible then damage,而是走一套接近真实武器规则的模拟。

炸弹逻辑如何融入行为树

炸弹是爆破模式 AI 的核心目标。项目里相关函数包括:

  • •updateBombPickup()
  • •repairBombInvariant()
  • •bombDebugState()
  • •executePlant()
  • •executeDefuse()
  • •siteSafeForPlant()

行为树里炸弹任务优先级很高:

  • •DefuseBomb = 120
  • •PlantBomb = 110
  • •RecoverBomb = 105
  • •DefendBomb = 95

这样做的原因是,爆破模式不是死亡竞赛。T 的最终目标是下包或消灭 CT;CT 的最终目标是阻止下包、拆包或消灭 T。AI 如果只按“看见敌人就打”写,很容易变成中路对枪游戏。