using UnityEngine; using System; using BaseGames.Core.Events; namespace BaseGames.Combat { /// /// 护盾组件。实现 IShieldable 接口供 HurtBox 注入。 /// 护盾参数通过 ShieldConfigSO 集中配置。 /// public class ShieldComponent : MonoBehaviour, IShieldable { [Header("配置资产")] [SerializeField] private ShieldConfigSO _config; [Header("VFX 事件频道")] [SerializeField] private VoidEventChannelSO _onShieldBrokenChannel; // 护盾破碎 → 播放破碎 VFX [SerializeField] private VoidEventChannelSO _onShieldRestoredChannel; // 护盾恢复 → 播放恢复 VFX // ── 运行时属性 ──────────────────────────────────────────────────────── public int MaxShieldHP => _config.MaxShieldHP; public int CurrentShieldHP { get; private set; } /// 当前是否能吸收伤害(护盾 HP > 0 且不在破碎惩罚期)。 public bool HasShield => CurrentShieldHP > 0 && _brokenPenaltyTimer <= 0f; private float AbsorptionRatio => _config.DamageAbsorptionRatio; private float RechargeDelay => _config.RechargeDelay; private float RechargeRate => _config.RechargeRate; private float BrokenPenaltyDur => _config.BrokenPenaltyDuration; private float ParryRestoreRatio => _config.ParryRestoreRatio; public event Action OnShieldChanged; // (current, max) public event Action OnShieldBroken; private float _regenDelayTimer; private float _brokenPenaltyTimer; private void Awake() { Debug.Assert(_config != null, "[ShieldComponent] _config 未赋值,请在 Inspector 中指定 ShieldConfigSO。", this); CurrentShieldHP = MaxShieldHP; } private void Update() { int maxHP = MaxShieldHP; if (maxHP <= 0) return; // 破碎惩罚计时 if (_brokenPenaltyTimer > 0f) { _brokenPenaltyTimer -= Time.deltaTime; // 破碎惩罚结束 → 广播护盾恢复事件(VFX 钩子) if (_brokenPenaltyTimer <= 0f) _onShieldRestoredChannel?.Raise(); return; } if (CurrentShieldHP >= maxHP) return; if (RechargeRate <= 0f) return; if (_regenDelayTimer > 0f) { _regenDelayTimer -= Time.deltaTime; return; } int prev = CurrentShieldHP; CurrentShieldHP = Mathf.Min(CurrentShieldHP + Mathf.CeilToInt(RechargeRate * Time.deltaTime), maxHP); if (CurrentShieldHP != prev) OnShieldChanged?.Invoke(CurrentShieldHP, maxHP); } /// /// 尝试以护盾吸收伤害。返回穿透量(0=全部吸收,>0=穿透量继续走 TakeDamage 流程)。 /// public int AbsorbDamage(int amount) { if (!HasShield) return amount; _regenDelayTimer = RechargeDelay; int maxHP = MaxShieldHP; // 按吸收比例计算实际由护盾承担的伤害 int toAbsorb = Mathf.FloorToInt(amount * AbsorptionRatio); toAbsorb = Mathf.Min(toAbsorb, CurrentShieldHP); int passthrough = amount - toAbsorb; CurrentShieldHP -= toAbsorb; OnShieldChanged?.Invoke(CurrentShieldHP, maxHP); if (CurrentShieldHP <= 0) { CurrentShieldHP = 0; _brokenPenaltyTimer = BrokenPenaltyDur; OnShieldBroken?.Invoke(); _onShieldBrokenChannel?.Raise(); // VFX 钩子:播放护盾破碎特效 } return passthrough; } /// 存档点 / 复活时调用:完全恢复护盾并清除惩罚状态。 public void FullRecharge() { bool wasBroken = _brokenPenaltyTimer > 0f || CurrentShieldHP <= 0; _brokenPenaltyTimer = 0f; _regenDelayTimer = 0f; CurrentShieldHP = MaxShieldHP; OnShieldChanged?.Invoke(CurrentShieldHP, MaxShieldHP); if (wasBroken) _onShieldRestoredChannel?.Raise(); // VFX 钩子:护盾已满血恢复 } /// 弹反成功时调用:按 ParryRestoreRatio 恢复护盾(会清除惩罚状态)。 public void OnParrySuccess() { int maxHP = MaxShieldHP; if (maxHP <= 0) return; _brokenPenaltyTimer = 0f; _regenDelayTimer = 0f; int restore = Mathf.CeilToInt(maxHP * ParryRestoreRatio); CurrentShieldHP = Mathf.Min(CurrentShieldHP + restore, maxHP); OnShieldChanged?.Invoke(CurrentShieldHP, maxHP); } /// Inspector / 道具系统调用:设置最大护盾并重置当前值。 public void SetMaxShieldHP(int max) { _maxShieldHP = Mathf.Max(0, max); _brokenPenaltyTimer = 0f; CurrentShieldHP = MaxShieldHP; OnShieldChanged?.Invoke(CurrentShieldHP, MaxShieldHP); } } }