250 lines
9.6 KiB
Markdown
250 lines
9.6 KiB
Markdown
# Quick Design Spec: Coordinator Runtime Policy
|
||
|
||
**Type**: Reference / Runtime Policy
|
||
**System**: Combat Coordinator / Minion Encounter AI
|
||
**Related Specs**:
|
||
- `design/quick-specs/minion-formation-showcase-combat-2026-07-03.md`
|
||
- `design/quick-specs/minion-combat-feel-and-weapon-roles-2026-07-03.md`
|
||
**Date**: 2026-07-04
|
||
|
||
## Change Summary
|
||
|
||
本文整理 `CoordinatorSubmodule` 改造期间形成的设计约定,用于指导后续杂兵 AI、Behavior Designer 节点、波次配置和命中反馈系统的开发。
|
||
|
||
核心目标不是让杂兵完全服务玩家,也不是让杂兵无约束地追求最优围攻,而是让 Cielonos 的杂兵战形成一种“有战术倾向,但仍然镜头友好、节奏清晰、打击稳定”的中高速 ARPG 体验。
|
||
|
||
## Current Implementation State
|
||
|
||
当前已经完成:
|
||
|
||
- `FormationProfile`:使用可配置 `Sector` 描述杂兵站位区域。
|
||
- `FormationSectorPurpose`:区分 `Action` 与 `FreeArea`。
|
||
- `FormationTacticalRoleMask`:允许 Sector 声明可接受的战术职责。
|
||
- `CoordinatorSubmodule` profile-driven sector assignment:旧固定 6 扇区逻辑已替换为 profile-driven runtime slots。
|
||
- Stable random target:敌人取得 Sector 时,会在 Sector 内抽取一个稳定随机点,避免整齐拼阵。
|
||
- `FormationFrame`:`PlayerForward` / `CameraForward` 只作为阵型初始化方向,不再逐帧拖动已分配 Sector。
|
||
- Path crossing penalty:候选 Sector 如果导致敌人从玩家附近横穿,会被显著降权。
|
||
- `RefreshFormationFrame()`:已提供手动刷新阵型方向的方法,但尚未接入自动 policy。
|
||
|
||
当前仍未完成:
|
||
|
||
- 敌人 role assignment 仍主要停留在接口和数据层,Behavior Designer 尚未真正按 `FormationTacticalRole` 分流行为。
|
||
- `RefreshFormationFrame()` 尚未由波次、房间或 Coordinator policy 自动调用。
|
||
- 玩家命中敌人后的 `Movement Suppression` 尚未实现。
|
||
- `FreeArea` 敌人还缺少低威胁行为策略,例如展示位、待机位、挑衅、缓慢重定位。
|
||
- `AttackToken` 与 `FormationTacticalRole` 还没有完全合并为统一威胁预算。
|
||
|
||
## Design Principles
|
||
|
||
### 1. Formation is Tactical Staging, Not Real-Time Rotation
|
||
|
||
`FormationProfile.referenceMode` 的职责是决定阵型初始化方向,而不是让阵型每帧跟随玩家或摄像机旋转。
|
||
|
||
正确语义:
|
||
|
||
```text
|
||
referenceMode = FormationFrame 初始化方向来源
|
||
FormationFrame.rotation = 当前战斗阶段的稳定阵型方向
|
||
Player position = 当前阵型中心,可持续跟随玩家
|
||
```
|
||
|
||
这样可以保留 `CameraForward` / `PlayerForward` 的战术意义,同时避免玩家转身或摄像机旋转导致敌人频繁横向移动。
|
||
|
||
### 2. RefreshFormationFrame is a Combat Director Event
|
||
|
||
`RefreshFormationFrame()` 不应在普通 AI Tick 中调用。它表示“当前战斗阶段需要重新定义舞台方向”,因此应由房间、波次、阶段切换或 Coordinator policy 触发。
|
||
|
||
推荐调用时机:
|
||
|
||
| Trigger | Rule |
|
||
|---------|------|
|
||
| 新一波敌人生成 | 刷新一次,使新波次以当前镜头或玩家朝向组织阵型 |
|
||
| CombatRoom / Zone 开始 | 默认由第一次 `TryAcquireSector` 创建 `FormationFrame`,通常不需要额外调用 |
|
||
| 所有 `Action` Sector 空闲且敌人未处于攻击/受击/连段中 | 可作为重整期刷新 |
|
||
| 玩家发生大位移 | 玩家离开原战场约 8-12 米后,可刷新一次 |
|
||
| 战斗阶段切换 | 例如从玩家输出窗口切换到敌人反击窗口 |
|
||
| 特殊演出或脚本事件 | 可显式刷新,但应避免打断当前受击或攻击行为 |
|
||
|
||
不推荐调用时机:
|
||
|
||
- 玩家只是转身。
|
||
- 摄像机只是旋转。
|
||
- 敌人正在 `MoveToSector`。
|
||
- 玩家正在近战连段命中敌人。
|
||
- 敌人正在攻击前摇、攻击中、受击、击飞、倒地或硬直。
|
||
- Boss 战中杂兵只是辅助压力时。
|
||
|
||
推荐约束:
|
||
|
||
```text
|
||
minRefreshInterval = 3.0s - 5.0s
|
||
largePlayerDisplacement = 8m - 12m
|
||
onlyRefreshWhenNoHighPriorityState = true
|
||
```
|
||
|
||
### 3. Hit Ownership Overrides Formation
|
||
|
||
当玩家近战命中普通杂兵时,敌人应短时间进入玩家拥有的表现窗口。这个窗口内,敌人的主动移动和阵型重定位应被抑制。
|
||
|
||
建议机制名:
|
||
|
||
```text
|
||
Movement Suppression
|
||
Player-Owned Window
|
||
```
|
||
|
||
允许保留:
|
||
|
||
- 受击动画。
|
||
- 击退、浮空、牵引、倒地。
|
||
- `ReactionSubcontroller` 被动位移。
|
||
- `FuncAnimPayload` 或 `AttackArea` 施加的位移。
|
||
- Hit stop、camera shake、damage number、Wwise hit event。
|
||
|
||
应暂停:
|
||
|
||
- `MoveToSector`。
|
||
- 主动重新申请 Sector。
|
||
- 主动刷新 Sector 目标点。
|
||
- 主动攻击请求。
|
||
- 绕背、归位、重新排队。
|
||
|
||
建议初始数值:
|
||
|
||
| Enemy Type | Movement Suppression |
|
||
|------------|----------------------|
|
||
| 普通近战杂兵 | 0.6s - 1.0s |
|
||
| `ShowcaseVictim` | 0.9s - 1.4s |
|
||
| 远程/轻型敌人 | 0.4s - 0.7s |
|
||
| 精英敌人 | 0.2s - 0.5s |
|
||
| Boss / `C1_Axe` | 不走此普通规则,使用 Boss poise 机制 |
|
||
|
||
### 4. Threat Budget Must Be Explicit
|
||
|
||
杂兵战应有战术倾向,但不能让所有敌人同时成为高威胁单位。
|
||
|
||
Demo 阶段推荐:
|
||
|
||
```text
|
||
maxActiveMeleeThreats = 1
|
||
maxActiveRangedThreats = 0-1
|
||
maxTotalActiveThreats = 2
|
||
defaultNonThreatRole = ShowcaseVictim / Reposition / Taunt
|
||
```
|
||
|
||
`AttackToken` 目前已经承担部分威胁预算功能,但还需要与 `FormationTacticalRole` 结合。未来应避免“没有攻击 token 的敌人仍继续强行攻击玩家”。
|
||
|
||
### 5. FreeArea is Still Formation
|
||
|
||
`FreeArea` 不是无规则闲逛区域,而是低威胁敌人的舞台调度区域。
|
||
|
||
推荐用途:
|
||
|
||
- `ShowcaseVictim`:靠近玩家前场,等待被连段或压制。
|
||
- `Reposition`:填补镜头友好站位。
|
||
- `Taunt`:做低威胁展示动作或短距离摆位。
|
||
- `Staging`:等待下一次 role 切换。
|
||
|
||
`FreeArea` 可以比 `Action` 更活跃,但刷新频率仍应较低。例如每 3-6 秒、玩家大位移后、或敌人脱离镜头太久时重取目标点。
|
||
|
||
## Runtime Priority Stack
|
||
|
||
杂兵 AI 的优先级建议如下:
|
||
|
||
```text
|
||
Death / Disabled
|
||
> Hit Reaction / Player-Owned Window
|
||
> Current Attack / Telegraph / Recovery
|
||
> AttackToken / Threat Budget
|
||
> Formation Sector Assignment
|
||
> FreeArea Showcase / Reposition
|
||
> Idle / Taunt
|
||
```
|
||
|
||
这意味着 Formation 不是最高优先级。它负责组织战场,但不能打断受击表现、攻击前摇、击飞、倒地或玩家连段窗口。
|
||
|
||
## Behavior Designer Integration Notes
|
||
|
||
现有节点:
|
||
|
||
- `TryAcquireSector`
|
||
- `MoveToSector`
|
||
- `ReleaseSector`
|
||
- `TryAcquireAttackToken`
|
||
- `HasAttackToken`
|
||
- `ReleaseAttackToken`
|
||
|
||
建议新增或扩展:
|
||
|
||
| Node | Purpose |
|
||
|------|---------|
|
||
| `TryAcquireSectorByRole` | 按 `FormationTacticalRole` / `FormationSectorPurpose` 申请 Sector |
|
||
| `HasAssignedRole` | 行为树根据 role 分流 |
|
||
| `IsMovementSuppressed` | 被玩家命中后阻止主动移动 |
|
||
| `CanRequestAttackToken` | 集中检查 threat budget、受击状态、展示窗口 |
|
||
| `RequestFormationRefresh` | 仅由波次或特殊行为触发,不放入普通移动循环 |
|
||
| `MoveToFormationSector` | 未来替代或扩展 `MoveToSector`,支持到达后停留、低频重取点 |
|
||
|
||
现阶段可继续兼容旧节点,但下一阶段应优先让 L1/L2 行为树显式读取 role。
|
||
|
||
## Suggested Next Implementation Order
|
||
|
||
### Step 1. Movement Suppression
|
||
|
||
实现玩家近战命中后的主动移动抑制。
|
||
|
||
最低要求:
|
||
|
||
- 在敌人被玩家近战命中时记录 `movementSuppressedUntil`。
|
||
- `MoveToSector` 在抑制期间返回 `Running` 或进入等待,不刷新 destination。
|
||
- 攻击 token 请求在抑制期间失败。
|
||
- 受击、击退、浮空等被动位移仍然保留。
|
||
|
||
### Step 2. Role-Aware Behavior Tree
|
||
|
||
让杂兵行为树不再只问“有没有 Sector”,而是问“我现在是什么职责”。
|
||
|
||
优先对象:
|
||
|
||
- `L1_Assault_Pawn`
|
||
- `L2_Assault_Stinger`
|
||
|
||
### Step 3. RefreshFormationFrame Policy
|
||
|
||
接入自动刷新策略,但先保守实现。
|
||
|
||
最低要求:
|
||
|
||
- 房间/波次开始时允许刷新。
|
||
- 玩家大位移后允许刷新。
|
||
- 有 cooldown。
|
||
- 有敌人处于受击、攻击、硬直时不刷新。
|
||
|
||
### Step 4. FreeArea Low-Threat Behaviors
|
||
|
||
为 `ShowcaseVictim` / `Reposition` / `Taunt` 提供简单行为。
|
||
|
||
不要求新动画和 VFX,先通过移动、停留、朝向玩家、等待、轻微重定位完成。
|
||
|
||
### Step 5. Threat Budget Unification
|
||
|
||
把 `AttackToken`、`FormationTacticalRole`、enemy rank、当前战斗阶段统一为一套 Coordinator policy。
|
||
|
||
## Acceptance Criteria
|
||
|
||
- [ ] 玩家转身或旋转摄像机时,已分配 Sector 的敌人不会频繁横向重定位。
|
||
- [ ] 玩家近战命中普通杂兵后,敌人不会立刻为了 Formation 逃出玩家攻击范围。
|
||
- [ ] 敌人从一个 Sector 移动到另一个 Sector 时,优先避免从玩家身体附近横穿。
|
||
- [ ] `RefreshFormationFrame()` 只在战斗阶段断点、波次变化或大位移重整时调用。
|
||
- [ ] `Action` 敌人与 `FreeArea` 敌人在行为意图上可区分。
|
||
- [ ] Demo 阶段同一时间的高威胁杂兵数量可控。
|
||
- [ ] `C1_Axe` Boss 战不被普通杂兵 Coordinator 规则误削弱。
|
||
|
||
## Open Questions
|
||
|
||
- `Movement Suppression` 应放在 `StatusSubmodule`、`ReactionSubcontroller`、还是专门的 AI state 中?
|
||
- 玩家攻击类型如何区分 melee hit、projectile hit、support equipment hit?
|
||
- `RefreshFormationFrame()` 应由 `CombatRoomSubmodule`、`RunManager`、还是 Coordinator 自身 policy 触发?
|
||
- `FreeArea` 敌人是否需要单独的 low-threat token,避免所有自由人同时轻微移动造成视觉噪声?
|
||
- 高难度 profile 中,`Flanker` 是否可以绕背攻击,还是只允许绕背施压但不攻击?
|