feat(combat): HurtBox 自带受击伤害倍率

倍率归 HurtBox 自己(弱点部位就是一个倍率 > 1 的 HurtBox),
取代 WeakPointSystem 那份写进私有字段却零调用者的倍率——
其注释称由 BossStats 调用,而项目里根本没有 BossStats 类型。

倍率 1 时数值原样返回而非"乘完再钳最低 1":本改动先于任何消费者落地,
若默认路径引入钳制会静默改变游戏里每一次伤害计算。
测试用 ApplyDamageMultiplier(0)==0 守住这条——10 在两种实现下都得 10,
只有 0 能把二者区分开。变异验证:去掉该捷径后恰好该断言变红。
This commit is contained in:
2026-07-30 14:15:12 +08:00
parent 5046125acb
commit 5cc93483d3
4 changed files with 126 additions and 5 deletions
+30 -5
View File
@@ -4,7 +4,7 @@ using BaseGames.Parry;
namespace BaseGames.Combat
{
/// <summary>
/// 受击盒组件。实现完整 8 步伤害流水线(架构 06_CombatModule §5)。
/// 受击盒组件。实现完整 9 步伤害流水线(架构 06_CombatModule §5)。
/// 挂载在角色根节点或指定子节点上,Collider2D 需设 IsTrigger = true
/// Layer = PlayerHurtBox 或 EnemyHurtBox。
/// </summary>
@@ -33,6 +33,28 @@ namespace BaseGames.Combat
public void SetPoiseSource(IPoiseSource src) => _poiseSource = src;
public void SetInvincible(bool value) => _isHurtBoxInvincible = value;
public void SetActive(bool value) => _isActive = value;
// 本 HurtBox 专属的受击倍率(弱点部位 > 1;默认 1 = 无影响)。
// 由能力在弱点窗口期间开关,语义与"这个部位更脆弱"一致。
private float _damageMultiplier = 1f;
/// <summary>本 HurtBox 当前的受击伤害倍率。</summary>
public float DamageMultiplier => _damageMultiplier;
/// <summary>设置受击倍率(弱点窗口开合时调用)。负值钳到 0。</summary>
public void SetDamageMultiplier(float value)
=> _damageMultiplier = UnityEngine.Mathf.Max(0f, value);
/// <summary>
/// 按本 HurtBox 的倍率缩放伤害。最低 1 点,与防御减免同规则
/// (倍率为 0 也不该让攻击完全无效——那属于无敌帧的语义,不是弱点倍率的)。
/// 倍率为 1 时原样返回,保证默认状态下伤害数值与未引入倍率前完全一致。
/// </summary>
public int ApplyDamageMultiplier(int raw)
=> _damageMultiplier == 1f
? raw
: UnityEngine.Mathf.Max(1, UnityEngine.Mathf.RoundToInt(raw * _damageMultiplier));
#if UNITY_EDITOR
// 付给编辑器的只读属性——避免反射并限制编辑器与运行时字段名耐合性。
public object EditorOwner => _owner;
@@ -109,15 +131,18 @@ namespace BaseGames.Combat
info.Amount = passThrough;
}
// 5. 计算 FinalDamage(防御减免,最低 1
// 5. 本 HurtBox 受击倍率(弱点部位;默认 1 时无影响
info.Amount = ApplyDamageMultiplier(info.Amount);
// 6. 计算 FinalDamage(防御减免,最低 1
int finalDamage = UnityEngine.Mathf.Max(1, info.Amount - _owner.Defense);
info.Amount = finalDamage;
info.FinalDamage = finalDamage;
// 6. 调用 _owner.TakeDamage
// 7. 调用 _owner.TakeDamage
_owner.TakeDamage(info);
// 7. 全局广播
// 8. 全局广播
_onDamageDealt?.Raise(info);
_onHitConfirmed?.Raise(new HitInfo
{
@@ -125,7 +150,7 @@ namespace BaseGames.Combat
HitPoint = resolvedHitPoint,
});
// 8. 状态效果触发(DoT — Fire / Poison
// 9. 状态效果触发(DoT — Fire / Poison
// _statusEffectable 已在 Awake 中缓存,无需每次受击调用 GetComponent
_statusEffectable?.ApplyStatusEffect(info.Type);
}