6.0 KiB
6.0 KiB
连续打击与伤害叠加设计 (Consecutive Hits & Damage Stacking)
需求分析
用户希望实现“对同一个目标,同一种伤害在一定间隔内认定为持续多次伤害,从而可以制作伤害叠加”。
这通常被称为 “连击系统 (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 中增加:
# 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 系统)
- 定义新状态:
StatusID.VULNERABILITY(易伤标记)。所有状态引用使用
StatusIDAutoload 常量(架构为数据驱动整数 ID,不存在 StatusType 枚举):StatusID.VULNERABILITY = 8(在 status_id.gd 中登记)StatusID.COMBO_MARK = 7(已定义) 禁止写法:StatusType.VULNERABILITY、StatusType.COMBO_MARK。
- 施加规则:
- 每次
ActionLaser(高频攻击) 命中时,施加 1 层StatusID.VULNERABILITY,持续 0.5秒。
- 每次
- 叠加规则:
- 在
StatusContext.addEffect中,如果发现已有StatusID.VULNERABILITY,则stack++。
- 在
- 伤害修正:
- 在
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 并造成由最大生命值决定的真实伤害。
代码落地计划
- 在
status_id.gd(StatusID Autoload)中追加VULNERABILITY: int = 8常量。COMBO_MARK: int = 7已在advanced_mechanics_summons_and_environment.md §3.2定义。 禁止在此文件外使用裸整数或 StatusType 枚举(见 ADR-R5-N2 规范)。 - 在
EnemyManager.gd的apply_damage函数中,计算扎血前先检查StatusID.VULNERABILITY进行倍率修正。 - 在
StatusManager.gd的apply_status函数中,确保此类型状态可正确无限叠加层数(或设置上限)。