配置 Tactical Assignment Plan
本篇建立一个最小闭环:StateTree Task 从固定数组 Source 汇集候选 Unit 和目标,Unit 把 GAIS.CommandIntent.Engage 解析为具体 Command,系统选择并提交至少一条 assignment。完成后,Task 的 AssignmentCount 大于零,候选 Unit 的 CommandSystem 中能看到对应的具体命令。
第一轮使用固定数组,暂时不加入 EQS、小队适配器和自定义 Allocator。这样可以把“输入是否正确”“命令是否可执行”“容量是否允许”分开验证。
前置条件
- 一个由
AAIController驱动的 StateTree。 - 至少一个候选 Pawn/Character,在 Pawn 上带有
UGAIS_CommandSystemComponent,并能接收目标 Command。 - 候选 Actor 上的
UGAIS_UnitComponent,或其上实现的IGAIS_CommandIntentResolver。 - 一个已经能被手动下发、且能接受测试目标的 Command Definition。
- 一个测试目标 Actor,以及项目中已存在的
GAIS.CommandIntent.*标签。
Tactical Assignment 不会绕过 CanObeyCommand 或 IsValidTarget。先确认手动命令能运行;否则自动分配失败时无法判断问题在命令还是规划。
Task 在 Authority 上启动,并从 AIController 当前 Possession 派生 ContextPawn。Controller 无效、缺少 Authority、Pawn 已换人、Plan 无法唯一解析或 Plan 没有 Batch,都会让 Task 直接返回 Failed。
1. 配置 Unit 的意图映射
在 UGAIS_UnitComponent.CommandIntentMappings 添加一项:
字段 | 示例 | 要点 |
|---|---|---|
| | 必须与 Batch 完全相同。 |
| 项目的攻击或支援命令 | 必须是此 Unit 可执行的有效 Definition。 |
| 默认值或有效变体索引 | MultiVariant Command 才需要选择特定变体。 |
默认 UnitComponent 会从该表解析意图。若玩法需要根据装备、状态或目标来源动态选命令,再实现自定义 Resolver;最小验证阶段不应把这层复杂度混入 Source 或 Allocator。
2. 先决定使用 Data Asset 还是 Inline Plan
两种入口使用同一个 UGAIS_TacticalAssignmentPlan 类型,运行结果一致。配置是否需要复用,决定采用哪一种形式。
如果你的情况是 | 选择 | 原因 |
|---|---|---|
多个小队 StateTree、多个状态或多个关卡共用同一批次规则 | | 规则集中维护;每个 Task 仍可绑定不同 Source。 |
Combat Plan 要由设计人员独立审阅、校验和调参 | | 资产边界清晰,引用者共享修改。 |
只为一个状态快速验证单 Batch | | 无需额外资产,配置紧贴调用点。 |
规则仅在该状态成立,复制到别处反而容易误用 | | 让局部规则与 StateTree 分支一起维护。 |
不要同时填写 Plan 和 Inline Plan,也不要两者都留空;Task 会因无法解析唯一 Plan 而进入 Failed。还要注意:两种 Plan 都不保存 Source。Source 始终配置或绑定在 Task 的 AssigneeSources / TargetSources 上,所以一个共享 Data Asset 可以针对不同小队运行。
以下示例使用 Data Asset;若选择 Inline Plan,就在 Task 的 Inline Plan 展开相同的 Batch 字段。
3. 创建 Plan
创建 UGAIS_TacticalAssignmentPlan Data Asset,并先只填一个 Batch:
字段 | 最小设置 |
|---|---|
| 一个 Batch。 |
| 例如 |
| 与 Unit 映射相同。 |
| 留空。 |
| 保持默认乘数 |
| 留空,使用默认容量感知贪心分配器。 |
| 保持正数;首次接入可使用默认值。 |
Plan 至少需要一个 Batch;每个 Batch 需要有效 IntentTag;重评估间隔必须是正的有限数值。它们是 Data Asset 可直接校验的配置项。
4. 在 StateTree Task 上配置顶层 Source
在目标 StateTree 状态中添加 Task: Run Tactical Assignment Plan(FGAIS_STT_RunTacticalAssignmentPlan)。在该 Task 上:
- 把
AIController绑定到当前 AIController 上下文。 - 在
Plan和Inline Plan中只设置一个;本例将 Data Asset 填入Plan。 - 在
AssigneeSources添加 Actor Array,填入测试 Unit Actor。 - 在
TargetSources添加 Actor Targets,填入测试目标 Actor;需要区分来源时设置SourceTag。
Source 在 Task 上,不在 Batch 中。每个 Source 独立填充临时数组,Task 将有效输出合并为候选流,再交由 Plan 的各 Batch 顺序消费。
1StateTree Task2 ├─ Plan 或 Inline Plan(二选一)3 ├─ AssigneeSources:Actor Array4 └─ TargetSources:Actor Targets5 ↓6Plan Batch:IntentTag、Filter、ScoreAdjustment、Allocator
Task 输入与输出怎样用于 StateTree 分支
字段 | 配置方式 | StateTree 中的用途 |
|---|---|---|
| 绑定当前 AIController 上下文。 | 决定 Authority、Context Pawn 和 Session 唯一所有者。 |
| 二选一。 | 提供 Batch 与重评估间隔。 |
| 内联配置或通过任务数据准备运行时 Source。 | 汇集所有可能接收 Command 的 Actor。 |
| 内联配置或通过任务数据准备运行时 Source。 | 汇集 Actor、Location 或 Actor+Location 目标。 |
| 只读输出。 | 判断 Session 已完成多少次评估。 |
| 只读输出。 | 区分 |
| 只读输出。 | 判断当前完整评估保留了多少 assignment。 |
| 边沿事件。 | 首次得到 assignment,或从零恢复到非零时驱动转换。 |
| 边沿事件。 | 首次完整评估为零,或从非零降到零时驱动兜底状态。 |
Task 进入状态后保持 Running,定时重评估由 Task 自己的 StateTree Scheduled Tick 驱动。退出状态时 Task 撤销调度并停止 Session,匹配的 Command 与 reservation 随之清理。
5. 进入状态并验证
Task 进入状态时会立即评估。请按顺序观察:
LastUpdateResult显示Completed;短暂的世界更新冲突可能显示Deferred并等待重试。ReplanCount在完整评估后增加,AssignmentCount大于零。- 候选 Unit 的 CommandSystem 收到由 IntentTag 解析得到的具体 Command。
- GameplayDebugger 中 Actor pool、Target pool、Candidates、Selected 和最终 Outcome 都符合预期。
OnAssignmentsAvailable 在首次得到分配,以及之后从无分配恢复为有分配时触发。连续有分配期间不会重复广播。若本轮因世界更新 guard 冲突得到 Deferred,Task 会保持运行并在之后重试。
6. 用 SourceTag 标记目标来自哪里
当一个 Task 同时收集多类目标时,为 Target Source 使用 GAIS.TacticalAssignment.TargetSource.* 子标签。例如:
Target Source | | 产出 |
|---|---|---|
EQS Actor Targets | | 感知或空间查询发现的敌人 Actor。 |
Target Data Array / 项目 Source | | 任务系统给出的守点位置。 |
项目 Source | | 玩家临时标记的目标。 |
SourceTag 是 Target Source 的默认来源标签:只有目标数据本身没有 Tag 时才补写。若 Target Data Array 中某一项已经带 Tag,该项自己的 Tag 会保留。
接着在 Batch 的 TargetFilters 添加 Target Source Tag:
- 攻击 Batch:
AllowedSourceTags允许...Perception.Enemy与...Player.Ping; - 守点 Batch:只允许
...Objective.DefensePoint; - 某类危险来源永远不可用时放入
BlockedSourceTags。Blocked 先于 Allowed 判定。
这不会创建独立的目标占用。同一个敌人 Actor 即使以“感知”和“玩家标记”两个 Tag 进入,世界容量仍按同一个 Actor 计算。
7. 用 Filter 让同一份 Source 服务多个 Batch
Source 应尽量表达稳定的数据来源,Batch Filter 表达当前战术规则。以战斗小队为例:
AssigneeSources只放一个 All Squad Units,显式绑定当前 Squad Actor。TargetSources放敌人 EQS Source 与守点位置 Source,并为两者设置不同SourceTag。- Plan 中按优先级添加 Batch:
EngagePriority:Assignee Filter 选择战斗角色;Target Filter 要求 Hostile、Has Actor、敌人来源。HoldPoints:从前一批未占用的 Unit 中选择守卫;Target Filter 要求 No Actor、守点来源。SupportRemainder:选择治疗/支援角色;Target Filter 限定友军 Actor。
所有配置在同一数组内的 Filter 都必须通过。Assignee Filter 在命令意图解析前把不合适的 Unit 剪掉,Target Filter 在命令 IsValidTarget 前剪掉不合适的目标;CommandDefinition 仍负责最终的执行者和目标合法性。
验证时看 GameplayDebugger 中每个 Batch 的 Actors before -> after 与 Targets before -> after:
- before 就是 0:回到 Source;
- before 有值、after 为 0:检查当前 Batch 的 Filter;
- after 有值但
NoResolvedCommand:检查 Intent 映射; - Candidates 有值、Selected 为 0:检查容量和 Allocator。
8. 正确处理没有 Source 输出的情况
当前 Source API 使用输出数组,没有 runtime 成功状态返回值。EQS 本轮没有结果、某个项目查询暂时产出空集或一个 Source 输出被判无效时,该 Source 本轮贡献零候选;其他 Source 继续参与,整轮评估仍会正常完成。
Source 空输出仍会完成本轮正常评估。若候选因此从合并流中消失,系统会协调先前 assignment,并让不再可用的记录进入 Stale 清理。Deferred 专门表示世界更新冲突或同步重入,此时保留旧的 tracked assignment/reservation。
配置错误则应修复,而非等待下轮:空 Source 对象、EQS 缺 QueryTemplate、必填 RoleTag 或 SquadActor 缺失都会使验证失败。
9. 按层增加真实玩法规则
最小闭环通过后,逐项加入:
- 用小队、EQS 或项目容器 Source 替换固定数组。
- 加入 Assignee Filter,再验证 Actor pool 的过滤前后数量。
- 加入 Target Filter,检查
SourceTag、队伍态度和目标形态。 - 调整
ScoreAdjustment,观察候选排序是否符合设计。 - 默认 Allocator 不足时再自定义 Allocator;它接收已校验候选与世界容量快照,不应重新发现目标或重复命令校验。
10. 组织一个 Idle / Combat 小队分支
在小队的 StateTree 中,把 Task 放在真正拥有该战术语义的状态。通常由 Idle、Combat 等分支分别持有对应 Task:
状态 | 顶层 Source | Plan Batch | 结果 |
|---|---|---|---|
Idle | All Squad Units;目标可省略或提供编队/岗位位置。 | Inline Plan: | 规则只属于 Idle 分支时就地维护,成员恢复跟随、站位或警戒。 |
Combat | All Squad Units;敌人 EQS、守点位置和友军 Target Source。 | Data Asset:先 | 可复用战斗规则;同一小队不同成员分别攻击、驻守或支援。 |
进入 Combat 时 Task 创建 Session 并立即评估;状态保持期间按 ReevaluationInterval 重评估;退出 Combat 时 Task 停止 Session。为每种 Unit 配置同一 Intent 的不同映射即可产生分工:前排的 Engage 解析为近战 Command,远程解析为射击 Command,治疗者只参与 Support Batch。Intent Resolver 才是“该 Unit 把战术语义落成什么命令”的接缝。
一个六人小队的完整配置示例
假设小队包含两名近战、一名远程、一名治疗者和两名守卫。Combat Task 使用:
Assignee Sources
- All Squad Units:
SquadOverride指向当前 Squad Actor,得到六名成员。这个 Source 要求显式 Squad Actor。 - 如果 Context Pawn 本身带有 SquadComponent,也可以按角色使用 Squad Units By Role;其
SquadOverride为空时会尝试 Context Pawn,RoleTag必须有效并采用精确匹配。
Target Sources
- EQS Actor Targets:查询当前敌人,
SourceTag = GAIS.TacticalAssignment.TargetSource.Perception.Enemy。 - EQS Location Targets:查询可守卫位置,
SourceTag = GAIS.TacticalAssignment.TargetSource.Defense.Position。 - All Squad Units:把指定 Squad 的成员作为友军目标,供治疗 Batch 使用。
Plan Batches
AttackPriorityIntentTag = Engage- Assignee Filter:Role Query 接受近战与远程
- Target Filter:Team Attitude = Hostile、Has Actor、SourceTag = Enemy
- 结果:三名战斗成员优先取得敌人目标
HealAlliesIntentTag = Support- Assignee Filter:Role Query 只接受治疗者
- Target Filter:Has Actor、友军标签或 Friendly 队伍态度
- 结果:治疗者从前序 Batch 未使用的成员中取得友军目标
HoldDefensePointsIntentTag = HoldPosition- Assignee Filter:Role Query 接受守卫
- Target Filter:No Actor、SourceTag = Defense.Position
- 结果:两名守卫竞争位置目标,每个位置的容量通常设置为一
Batch 顺序会先占用 Unit。若治疗者也满足 Attack 的 Assignee Filter,它会在第一批被 claim,后续 HealAllies 无法再使用。因此角色 Filter 和 Batch 顺序共同决定“谁先干什么”。目标容量则决定“同一个目标能分给多少人”。
Idle 分支可使用一个短 Inline Plan,只有 FollowSquad Batch。Follow Command 若采用 None 或 Passive 目标类型,Task 可以保留空 TargetSources。进入 Combat 分支后,Idle Session 停止,Combat Task 创建新的 Session 并下发战斗 Command。
11. 用面板与日志验证真实分配
运行 PIE 后打开 GameplayDebugger,启用 GenericAISystem category,选中由 AIController 控制的 Pawn,再按 Shift+7 切换 Tactical Assignment section。先看 Source 数与 Actor/Target pool,再看每个 Batch 的 Filter 前后数量、Candidate/Selected、外部目标占用和 Assignment 记录;这比只看 Unit 是否移动更容易判断问题在搜集、意图解析、命令校验、容量还是提交。
需要诊断 Session 开始/停止、重规划和提交结果时,可在控制台临时使用 Log LogGAIS_Tactical Verbose。GameplayDebugger 用于查看当前 Session/Batch 快照;每个 Source 产出的 Actor/Target 明细当前通过 Visual Logger 的 UE_VLOG 输出,不在普通文本日志里逐项展开。诊断结束后执行 Log LogGAIS_Tactical Log。需要跨启动保留生命周期文本日志时在项目 [Core.Log] 配置 LogGAIS_Tactical=Verbose。
常见失败
现象 | 优先检查 |
|---|---|
进入 Task 立即 | AIController 是否有效;Plan/Inline Plan 是否恰好一个;Plan/Source 的确定性配置是否完整。 |
| Batch IntentTag 与 Unit 映射是否一致;Resolver 和 Command Definition 是否有效。 |
| 具体 Command 的执行者需求、资源和项目状态。 |
| Resolver 返回的 Command 需要采用 |
| Source 输出是否匹配 Command 的 TargetType;Target Filter 是否过严。 |
Candidates 有值但 Selected 为零 | 世界容量快照、已有 claim、评分与 Allocator 选择。 |
一个 Source 无输出 | 检查该 Source 自身输入和其他 Source 是否仍有候选;本轮会按实际合并结果继续评估。 |
None / Passive Command 不需要 Target Source,会使用局部空目标数据。不要向共享 TargetSources 填充虚假目标;同一 Task 的其他 Batch 可能仍需要真实目标流。
