# 20 · 护盾模块(Shield Module) > **命名空间** `BaseGames.Combat` > **程序集** `BaseGames.Combat`(`Assets/Scripts/Combat/`) > **依赖** `BaseGames.Core.Events` · `BaseGames.Combat`(DamageInfo · HurtBox)· `BaseGames.UI`(HUDController) > **Design 来源** [30_ShieldMechanicsSystem](../Design/30_ShieldMechanicsSystem.md) --- ## 目录 1. [模块职责](#1-模块职责) 2. [伤害管道修正](#2-伤害管道修正) 3. [ShieldConfigSO](#3-shieldconfigso) 4. [ShieldComponent](#4-shieldcomponent) 5. [IShieldable 接口](#5-ishieldable-接口) 6. [护盾恢复机制](#6-护盾恢复机制) 7. [护盾 UI 集成](#7-护盾-ui-集成) 8. [弹反集成(P1)](#8-弹反集成p1) 9. [SaveData 集成](#9-savedata-集成) 10. [事件频道](#10-事件频道) --- ## 1. 模块职责 护盾是独立于玩家 HP 之外的**第二道防御层**: ``` 伤害输入(来自 HurtBox) │ ▼ ShieldComponent.AbsorbDamage(incomingDamage) ├─ 护盾耐久 > 0 且未破碎 │ ├─ 吸收 = damage × DamageAbsorptionRatio │ ├─ 扣除护盾耐久 │ ├─ 耐久归零 → 触发护盾破碎(EVT_ShieldBroken) │ └─ 返回穿透量 → HurtBox 直接调用 _damageable.TakeDamage(passInfo) │ └─ 护盾已破碎或耐久 = 0 └─ HurtBox 走原始伤害流程(直接 TakeDamage) ``` **零耦合**:`ShieldComponent.AbsorbDamage()` 只返回穿透量,不持有 `PlayerStats` 引用。穿透伤害由 `HurtBox.ReceiveDamage()` 负责转交给 `IDamageable.TakeDamage()`(见 §2)。 --- ## 2. 伤害管道修正 `HurtBox.ReceiveDamage()` 需要在调用 `IDamageable.TakeDamage` 之前检查护盾: ```csharp // HurtBox.cs(修改 06_CombatModule §2 的实现) public void ReceiveDamage(DamageInfo info) { if (!_isActive) return; if (_shieldable != null && _shieldable.HasShield) { // 护盾优先拦截:AbsorbDamage 返回穿透伤害剧量 int passThrough = _shieldable.AbsorbDamage(info.Amount); if (passThrough > 0) { var passInfo = info; passInfo.Amount = passThrough; _damageable?.TakeDamage(passInfo); } // ShieldComponent 内部已通过事件频道更新 ShieldBarUI return; } // 无护盾或已穿透,走原始流程 _damageable?.TakeDamage(info); _onDamageDealt.Raise(info); // EVT_DamageDealt(AnalyticsManager) _onHitConfirmed.Raise(new HitInfo { DamageInfo = info, HitPoint = transform.position }); } ``` `PlayerController` 在 Awake 时将 `ShieldComponent` 注入 `HurtBox._shieldable`: ```csharp // PlayerController.Awake() _hurtBox.SetShieldable(_shieldComponent); ``` --- ## 3. ShieldConfigSO ```csharp namespace BaseGames.Combat { [CreateAssetMenu(menuName = "Player/ShieldConfig")] public class ShieldConfigSO : ScriptableObject { [Header("耐久")] [Min(1)] public int MaxShieldHP = 60; [Range(0f, 1f)] public float DamageAbsorptionRatio = 1.0f; // 1.0 = 完全吸收 [Header("恢复")] [Min(0f)] public float RechargeDelay = 2.5f; // 最后一次受击后延迟恢复的秒数 [Min(0f)] public float RechargeRate = 20f; // 每秒恢复耐久点数 public bool FullRechargeOnSavePoint = true; // 激活存档点时护盾立即满值 [Header("破碎惩罚")] [Min(0f)] public float BrokenPenaltyDuration = 3.0f; // 护盾破碎后无法恢复的时间 [Header("弹反加成(P1)")] [Range(0f, 1f)] public float ParryRestoreRatio = 0.3f; // 成功格挡时恢复护盾耐久比例 } } ``` **资产路径**:`Assets/ScriptableObjects/Player/Shield_Config.asset` --- ## 4. ShieldComponent ```csharp namespace BaseGames.Combat { /// /// 挂在 PlayerController 子节点 [Shield] 上。 /// 在 HurtBox 和 IDamageable(PlayerStats)之间担当拦截层。 /// public class ShieldComponent : MonoBehaviour, IShieldable { // ── Inspector ─────────────────────────────────────── [SerializeField] ShieldConfigSO _config; [SerializeField] IntEventChannelSO _onShieldHPChanged; // 广播当前耐久整数 [SerializeField] VoidEventChannelSO _onShieldBrokenChannel; [SerializeField] VoidEventChannelSO _onShieldRestoredChannel; [SerializeField] DifficultyChangedEventChannel _onDifficultyChanged; // ── Runtime State ──────────────────────────────────── int _currentShieldHP; bool _isBroken; float _timeSinceLastHit; float _brokenTimer; // ── IShieldable ───────────────────────────────────── public bool HasShield => _currentShieldHP > 0 && !_isBroken; public int CurrentShieldHP => _currentShieldHP; public int MaxShieldHP => _config.MaxShieldHP; void Awake() => _currentShieldHP = _config.MaxShieldHP; void OnEnable() { _onDifficultyChanged.OnEventRaised += OnDifficultyChanged; } void OnDisable() { _onDifficultyChanged.OnEventRaised -= OnDifficultyChanged; } void Update() { if (_isBroken) { _brokenTimer += Time.deltaTime; if (_brokenTimer >= _config.BrokenPenaltyDuration) { _isBroken = false; _brokenTimer = 0f; } return; } if (_currentShieldHP < _config.MaxShieldHP) { _timeSinceLastHit += Time.deltaTime; if (_timeSinceLastHit >= _config.RechargeDelay) { _currentShieldHP = Mathf.Min( _config.MaxShieldHP, _currentShieldHP + Mathf.RoundToInt(_config.RechargeRate * Time.deltaTime) ); } } } /// /// 护盾拦截伤害。由 HurtBox.ReceiveDamage 调用。 /// 返回剩余穿透伤害値(0 = 完全吸收)。 /// 通过 EVT_ShieldHPChanged 更新 ShieldBarUI,不直接修改 DamageInfo。 /// public int AbsorbDamage(int incomingDamage) { _timeSinceLastHit = 0f; int absorbed = Mathf.RoundToInt(incomingDamage * _config.DamageAbsorptionRatio); int passThrough = incomingDamage - absorbed; _currentShieldHP -= absorbed; if (_currentShieldHP <= 0) { // 护盾破碎:多余伤害穿透 passThrough += Mathf.Abs(_currentShieldHP); _currentShieldHP = 0; _isBroken = true; _brokenTimer = 0f; _onShieldBrokenChannel.Raise(); } _onShieldHPChanged.Raise(_currentShieldHP); // 更新 ShieldBarUI return passThrough; } /// 存档点激活时调用(若配置允许)。 public void FullRecharge() { if (!_config.FullRechargeOnSavePoint) return; _currentShieldHP = _config.MaxShieldHP; _isBroken = false; _brokenTimer = 0f; _onShieldRestoredChannel.Raise(); } /// 存档加载时恢复护盾状态。由 PlayerController.LoadFromSaveData() 调用。 public void SetShieldHP(int hp, bool isBroken) { _currentShieldHP = Mathf.Clamp(hp, 0, _config.MaxShieldHP); _isBroken = isBroken; _brokenTimer = 0f; _timeSinceLastHit = 0f; } /// 弹反成功时恢复护盾(P1)。 public void OnParrySuccess() { int restore = Mathf.RoundToInt(_config.MaxShieldHP * _config.ParryRestoreRatio); _currentShieldHP = Mathf.Min(_config.MaxShieldHP, _currentShieldHP + restore); } void OnDifficultyChanged(DifficultyScalerSO scaler) { // 难度变更时按比例调整护盾(可选,若 ShieldConfigSO 有难度钩子字段则使用) } } } ``` --- ## 5. IShieldable 接口 ```csharp namespace BaseGames.Combat { /// /// 可拥有护盾的实体接口。HurtBox 持有此接口引用,在受击时优先检查护盾。 /// public interface IShieldable { bool HasShield { get; } int CurrentShieldHP { get; } int MaxShieldHP { get; } /// 拦截 incomingDamage,返回剩余穿透伤害(0 = 完全吸收)。 int AbsorbDamage(int incomingDamage); void FullRecharge(); void OnParrySuccess(); } } ``` --- ## 6. 护盾恢复机制 | 时机 | 行为 | |------|------| | 最后一次受击后 `RechargeDelay` 秒 | 开始按 `RechargeRate/s` 线性恢复 | | 护盾破碎后 `BrokenPenaltyDuration` 秒 | 破碎状态结束,恢复计时重新开始 | | 激活存档点 | `SavePoint.Activate()` → `ShieldComponent.FullRecharge()`(若配置为 true) | | 弹反成功 | `ParrySystem.OnParrySuccess` → `ShieldComponent.OnParrySuccess()` | --- ## 7. 护盾 UI 集成 护盾 UI 显示在 HUD 的 HP 条上方(或并列): ```csharp // HUDController 中订阅护盾变化 // ShieldBarUI 组件,与 HP Bar 类似设计 public class ShieldBarUI : MonoBehaviour { [SerializeField] Image _fill; [SerializeField] ShieldComponent _shield; [SerializeField] GameObject _brokenIndicator; // 护盾破碎时显示红色图标 [Header("Event Channels")] [SerializeField] IntEventChannelSO _onShieldHPChanged; // 订阅耐久变化 [SerializeField] VoidEventChannelSO _onShieldBrokenChannel; [SerializeField] VoidEventChannelSO _onShieldRestoredChannel; void OnEnable() { _onShieldHPChanged.OnEventRaised += RefreshFill; _onShieldBrokenChannel.OnEventRaised += ShowBroken; _onShieldRestoredChannel.OnEventRaised += HideBroken; } void OnDisable() { _onShieldHPChanged.OnEventRaised -= RefreshFill; _onShieldBrokenChannel.OnEventRaised -= ShowBroken; _onShieldRestoredChannel.OnEventRaised -= HideBroken; } private void RefreshFill(int currentHP) { _fill.fillAmount = _shield.MaxShieldHP > 0 ? (float)currentHP / _shield.MaxShieldHP : 0f; } private void ShowBroken() => _brokenIndicator.SetActive(true); private void HideBroken() => _brokenIndicator.SetActive(false); } ``` --- ## 8. 弹反集成(P1) 弹反成功时,`ParrySystem.HandleSuccessfulParry()` 末尾调用(见 06_CombatModule §8): ```csharp if (_controller.TryGetComponent(out var shield)) shield.OnParrySuccess(); ``` 此处 `HandleSuccessfulParry()` 是 `ParrySystem` 中处理弹反成功后统一逻辑的方法,同时负责广播 `_onParrySuccess`(`ParryInfoEventChannelSO`)事件、奖励灵力、触发子弹时间。`ShieldComponent.OnParrySuccess()` 通过直接调用(而非事件订阅)接收通知,以保证执行顺序。 --- ## 9. SaveData 集成 `PlayerSaveData` 中新增字段: ```csharp public int ShieldHP; // 当前护盾耐久(-1 = 使用最大值,即满护盾) public bool ShieldIsBroken; // 是否处于破碎状态 ``` **加载时**(`PlayerController.LoadFromSaveData`): ```csharp if (saveData.ShieldHP >= 0) _shield.SetShieldHP(saveData.ShieldHP, saveData.ShieldIsBroken); else _shield.FullRecharge(); ``` --- ## 10. 事件频道 | 频道 SO | Payload | 发布者 | 订阅者 | |--------|---------|--------|--------| | `EVT_ShieldHPChanged` | `int`(当前耐久值) | `ShieldComponent` | `ShieldBarUI`(更新护盾条填充) | | `EVT_ShieldBroken` | void | `ShieldComponent` | `PlayerFeedback`(破碎音效/特效)、`HUDController`(护盾破碎 UI) | | `EVT_ShieldRestored` | void | `ShieldComponent` | `HUDController`(护盾恢复 UI) | ## Player Prefab 层级更新 ``` [Player] ├── PlayerController.cs │ └── [SerializeField] ShieldComponent _shield ← 新增 │ ├── [Combat] │ ├── HurtBox.cs │ │ └── [SerializeField] IShieldable _shieldable ← 由 PlayerController.Awake() 注入 │ └── HitBox.cs │ └── [Shield] ← 新增子节点 └── ShieldComponent.cs ```