EnemyBase.ReceiveParry 早就存在且形态正确(强制 Stagger + 打断全部能力 + 定时恢复,小怪 Boss 通用),但一直零调用者:弹反判定点拿不到攻击者引用。 硬直时长的权威也早就存在——ParryConfigSO.StaggerDuration,注释写着 「被弹反敌人的受击硬直时长」,同样零消费者。本次只是把这两端接上,没有新造语义。 接缝选在 DamageInfo 而非"弹反成功时回调 HitBox"(spec §6.2 留的两个选项): 攻击者引用与既有的 SourceProjectile 完全同构——都是"攻击从哪来"的引用, 供弹反分支反向作用于来源。落到代码上就是弹反分支里并列的两行,不引入第二条回调链路。 - Combat 新增 IParryable(攻击被弹反时的反制承受方)。用接口是因为程序集方向: Combat 不能反向依赖 Enemies,而 HurtBox.ReceiveDamage 是唯一同时握有 弹反结果与 DamageInfo 的地方。与 IDamageable 同款做法。 - DamageInfo 加 Attacker 字段([NonSerialized],Builder 与 From 工厂同步支持)。 - HitBox 在 Activate 时按宿主解析并缓存 IParryable——与 _ownerRigidbody 同源、 与 _ownerProjectile 同手法。放 Activate 而非 Awake:attacker 可由调用方传入。 - ParrySystem 暴露 StaggerDuration,HurtBox 在弹反成功时读它,不另立常量。 - EnemyBase 声明实现 IParryable(方法签名本就匹配,无需改实现)。 顺带确认旧的全局误伤路径确实已随 Boss 单轨合并删净:全库不再有 HandleParrySuccess,ParryInfoEventChannelSO 只剩 ParrySystem 自己 Raise, 不存在"弹反小怪导致场上 Boss 一起硬直"的双重路径。 验证:编译 0 错;EditMode 276/276(273 + 新增 3)。 测试走完整 HurtBox 流水线:反射仅用于喂配置与跑真实 Awake,被测逻辑全程生产代码 (与 BossPhaseAbilityGateTests 同一手法)。硬直时长断言取 1.25f 这个非默认值, 才能证明读的是配置而非常量。用例把完美弹反阈值设为负数以避开会改 Time.timeScale 的子弹时间协程(编辑模式下它 yield 后不会恢复),TearDown 另有兜底还原。 变异验证——注释掉 HurtBox 里那一行后,恰好只有反制用例变红;还原后复验 276/276。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
2.2 KiB
C#
64 lines
2.2 KiB
C#
namespace BaseGames.Combat
|
||
{
|
||
/// <summary>
|
||
/// 可受击实体接口。PlayerController 和 EnemyBase 实现此接口。
|
||
/// HurtBox.Awake 通过 GetComponentInParent<IDamageable>() 注入。
|
||
/// </summary>
|
||
public interface IDamageable
|
||
{
|
||
bool IsAlive { get; }
|
||
bool IsInvincible { get; }
|
||
int Defense { get; }
|
||
void TakeDamage(DamageInfo info);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 攻击被弹反时的反制承受方(攻击发起者实现,如 EnemyBase)。
|
||
///
|
||
/// 存在的理由是程序集方向:Combat 不能反向依赖 Enemies,而弹反成功的判定点
|
||
/// (<see cref="HurtBox.ReceiveDamage"/>)是唯一同时握有弹反结果与
|
||
/// <see cref="DamageInfo"/> 的地方,所以攻击者只能以接口形态经 DamageInfo 传进来。
|
||
/// 与 <see cref="IDamageable"/> 同款做法。
|
||
/// </summary>
|
||
public interface IParryable
|
||
{
|
||
/// <summary>本方发起的攻击被弹反:进入硬直 <paramref name="staggerDuration"/> 秒。</summary>
|
||
void ReceiveParry(float staggerDuration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 可持有霸体的实体接口。HurtBox 在 ReceiveDamage 中做等级比较。
|
||
/// </summary>
|
||
public interface IPoiseSource
|
||
{
|
||
PoiseLevel GetCurrentPoiseLevel();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 护盾接口(玩家专属)。由 PlayerController.Awake() 注入 HurtBox。
|
||
/// AbsorbDamage 返回穿透量(0 = 全部吸收,>0 = 穿透量继续走 TakeDamage 流程)。
|
||
/// </summary>
|
||
public interface IShieldable
|
||
{
|
||
bool HasShield { get; }
|
||
int AbsorbDamage(int amount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 可破坏机关/障碍物接口。HitBox 在命中非 HurtBox 对象时尝试调用。
|
||
/// </summary>
|
||
public interface IBreakable
|
||
{
|
||
void TryInteract(DamageInfo info);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 可施加状态效果的实体接口(避免 Combat 直接引用 StatusEffects 程序集)。
|
||
/// StatusEffectManager 实现此接口;HurtBox.ReceiveDamage 步骤 8 通过此接口调用。
|
||
/// </summary>
|
||
public interface IStatusEffectable
|
||
{
|
||
void ApplyStatusEffect(DamageType type);
|
||
}
|
||
}
|