- 将开发过程/归档文档迁至 docs_dev/(development_plan、certification_checklist、 已废弃的 Cocos 架构草案 archived_cocos_architecture_draft),并修正全部跨引用 - 新增根 README.md(项目介绍,暂定名 Spellforge)与 docs_dev/README.md 索引 - 新增 docs_dev/doc_code_audit_2026-07-20.md:文档 vs 代码交叉审计报告(经 6 路对抗性复核,零证伪),含「代码更优 / 文档更优 / 中性」判定汇总 - 在 docs/ 各设计·技术·机制文档就地加「实现现状 (2026-07-20)」callout: 追认代码更优实现(纯 JSON 数据驱动、SpatialGrid-only 碰撞、MultiMesh 单档、 存档选最新槽等),订正陈旧/矛盾内容(.tres→JSON、Boss HP/阈值/波次、EventID、 StatusManager.apply 签名等),标记未实现功能(C# 热路径、Mana、元进展、 Boss 阶段/抗性、Tutorial、轨迹/连锁/催化等)与 latent bug(CoreFeatureTag 位运算、 pierce 空操作、MAX_OPS 不读 cpu_limit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
95 lines
6.4 KiB
Markdown
95 lines
6.4 KiB
Markdown
# 连续打击与伤害叠加设计 (Consecutive Hits & Damage Stacking)
|
||
|
||
> **⚠️ 实现现状 (2026-07-20 审计)**:`VULNERABILITY`(易伤,+5%/层)状态**未实现**——无 JSON 条目、`apply_damage` 不读取。现状代码改用 **`COMBO_MARK` 每层 +2%**(`enemy_manager.gd`)。文中标注为 P2 长期方案的 `is_combo_tracker` **已提前实现**并正确守卫 DoT tick(COMBO_MARK 不误触发 DoT)。详见 [审计报告](../../docs_dev/doc_code_audit_2026-07-20.md)。
|
||
|
||
## 需求分析
|
||
用户希望实现“对同一个目标,同一种伤害在一定间隔内认定为持续多次伤害,从而可以制作伤害叠加”。
|
||
|
||
这通常被称为 **“连击系统 (Combo System)”** 或 **“弱点叠加 (Vulnerability Stacking)”**。
|
||
与 DoT(中毒/燃烧)不同,这种机制侧重于 **“攻击频率”** 和 **“爆发奖励”**。
|
||
|
||
## 核心概念
|
||
|
||
### 1. 连击窗口 (Combo Window)
|
||
* 每个敌人对每种伤害来源(或类型)维护一个 `LastHitTime` 和 `StackCount`。
|
||
* 如果在 `WindowDuration` (例如 2秒) 内再次受到同类伤害:`StackCount++`, 刷新 `LastHitTime`。
|
||
* 如果超时:`StackCount` 重置为 0。
|
||
|
||
### 2. 叠加收益 (Stacking Benefits)
|
||
当 `StackCount` 增加时,可以产生以下效果:
|
||
* **Damage Scaling**: 第 N 次伤害提高 `N * 10%`。
|
||
* **Proc Trigger**: 每第 3 次攻击触发一次爆炸 (3-Hit Passive)。
|
||
* **State Transition**: 叠满 5 层后,施加“破甲”或“眩晕”。
|
||
|
||
## 数据结构设计
|
||
|
||
在现有的 `EnemyManager` 混合 ECS 中,我们需要拓展 `StatusContext` 或新增 `ComboContext`。
|
||
|
||
由于连击通常与特定的“来源”或“伤害标签”绑定,建议存放在 `StatusContext` 中的专用字段,或者复用 Status 机制。
|
||
|
||
### 方案 A: 专用 Combo Tracker
|
||
在 `EnemyEntityManager` 的 Context 中增加:
|
||
```gdscript
|
||
# combo_tracker.gd — 通过 ObjectPool 复用,避免 GC 开销
|
||
class_name ComboTracker
|
||
extends RefCounted
|
||
|
||
var source_id: int = -1 # 来源实体 ID(支持多武器/多玩家,-1 表示无效)
|
||
var damage_tag: int = 0 # 伤害类型位掩码(PHYSICAL=1, FIRE=2, LASER=4 ...)
|
||
var count: int = 0 # 当前连击层数
|
||
var timer: float = 0.0 # 剩余窗口时间(秒)
|
||
|
||
func reset() -> void:
|
||
source_id = -1
|
||
damage_tag = 0
|
||
count = 0
|
||
timer = 0.0
|
||
```
|
||
|
||
### 方案 B: 利用现有 Status 系统
|
||
定义一种特殊的 StatusType `COMBO_MARK`。
|
||
* 每次攻击施加 `COMBO_MARK`。
|
||
* `addEffect` 逻辑中,如果已存在,则 `intensity++` (层数) 并刷新 `duration`。
|
||
* 伤害计算时,检查 `COMBO_MARK` 的 `intensity` 来加成伤害。
|
||
|
||
**✅ 追加选择:推荐方案 B**,因为它复用了现有的 Tick 和合并逻辑,不需要写额外的 Timer。方案 A 适合需要多来源(`source_id`)区分的场景(如多玩家共同攻击同一目标)。
|
||
|
||
> **R4-C3 补充(COMBO_MARK 的 StatusTypeDef 参数)**:`COMBO_MARK` 对应以下 `StatusTypeDef` 配置:
|
||
> - `stack_mode = 1`(**Intensity 强度叠加**):每次施加 +1 层,`intensity` 字段记录当前层数。
|
||
> - `max_stacks = 0`(**0 = 无限叠加**,不设层数上限;由高频攻击自然封顶)。
|
||
> - `tick_interval`:⚠️ **语义说明(P6-N14 修正)**:`StatusTypeDef.tick_interval` 在 `StatusManager` 的通用实现中是"DoT 伤害跳字间隔"。但 `COMBO_MARK` **不造成 DoT 伤害**,其 `_apply_dot_tick()` 回调被设计为空操作(`pass`)。
|
||
> 此处 `tick_interval` 被 COMBO_MARK **重载为连击窗口刷新机制**:每次施加 COMBO_MARK 时,调用方手动将 `remaining_duration` 重置为连击窗口时长(如 2.0s),利用 `StatusManager` 的 duration 计时器实现超时重置。
|
||
> **推荐实践**:为避免混淆,`COMBO_MARK` 的 `tick_interval` 设置为一个大于连击窗口的值(如 `999.0`),确保在窗口内 `tick_accumulator` 永不达到触发阈值,彻底避免意外的 DoT 跳字。
|
||
> **长期方案(P2)**:为 `StatusTypeDef` 增加 `is_combo_tracker: bool` 字段,从根本上区分"持续伤害状态"和"计数追踪状态",使 `StatusManager._apply_dot_tick` 在 `is_combo_tracker = true` 时直接跳过。
|
||
> - `can_catalyze = []`(空数组):连击标记本身不触发元素反应,只作为伤害加成计算输入。
|
||
> `StatusTypeDef.can_catalyze` 字段类型为 `Array[int]`,必须赋空数组 `[]`,
|
||
> 禁止赋值 `false`(GDScript 4 严格类型模式下触发 bool→Array 类型错误)。
|
||
> 详见 `combat_mechanics_depth.md §3.0`(StatusTypeDef 完整字段)。
|
||
|
||
## 实现逻辑 (基于 Status 系统)
|
||
|
||
1. **定义新状态**: `StatusID.VULNERABILITY` (易伤标记)。
|
||
> 所有状态引用使用 `StatusID` Autoload 常量(架构为数据驱动整数 ID,不存在 StatusType 枚举):
|
||
> - `StatusID.VULNERABILITY = 8`(在 status_id.gd 中登记)
|
||
> - `StatusID.COMBO_MARK = 7`(已定义)
|
||
> 禁止写法:`StatusType.VULNERABILITY`、`StatusType.COMBO_MARK`。
|
||
2. **施加规则**:
|
||
* 每次 `ActionLaser` (高频攻击) 命中时,施加 1 层 `StatusID.VULNERABILITY`,持续 0.5秒。
|
||
3. **叠加规则**:
|
||
* 在 `StatusContext.addEffect` 中,如果发现已有 `StatusID.VULNERABILITY`,则 `stack++`。
|
||
4. **伤害修正**:
|
||
* 在 `onApplyDamage` 中,计算最终伤害前,读取目标的 `StatusID.VULNERABILITY` 层数。
|
||
* `FinalDamage = BaseDamage * (1 + Stack * 0.05)` (每层增伤 5%)。
|
||
* 由于激光每秒 10 次,0.5秒内能叠 5 层,伤害会越来越高。
|
||
|
||
## 扩展玩法示例
|
||
* **聚焦激光 (Focus Laser)**: 初始伤害低,但对同一目标持续照射越久伤害越高(利用 0.1s 间隔的刷新机制)。
|
||
* **三环被动 (Three-Hit Passive)**: 类似于 LOL 的薇恩。给敌人挂一个隐藏的 `StackStatus`,当 Stack 到达 3 时,消耗所有 Stack 并造成由最大生命值决定的真实伤害。
|
||
|
||
## 代码落地计划
|
||
1. 在 `status_id.gd`(StatusID Autoload)中追加 `VULNERABILITY: int = 8` 常量。
|
||
`COMBO_MARK: int = 7` 已在 `advanced_mechanics_summons_and_environment.md §3.2` 定义。
|
||
禁止在此文件外使用裸整数或 StatusType 枚举(见 ADR-R5-N2 规范)。
|
||
2. 在 `EnemyManager.gd` 的 `apply_damage` 函数中,计算扎血前先检查 `StatusID.VULNERABILITY` 进行倍率修正。
|
||
3. 在 `StatusManager.gd` 的 `apply_status` 函数中,确保此类型状态可正确无限叠加层数(或设置上限)。
|