using UnityEngine; using BaseGames.Parry; namespace BaseGames.Combat { /// /// 受击盒组件。实现完整 8 步伤害流水线(架构 06_CombatModule §5)。 /// 挂载在角色根节点或指定子节点上,Collider2D 需设 IsTrigger = true, /// Layer = PlayerHurtBox 或 EnemyHurtBox。 /// [RequireComponent(typeof(Collider2D))] public class HurtBox : MonoBehaviour { // ── 伤害接受方(Awake 注入)────────────────────────────────────────── private IDamageable _owner; private IShieldable _shieldable; // 由 PlayerController.Awake() 注入 private ParrySystem _parrySystem; // 由 PlayerController.Awake() 注入 private IPoiseSource _poiseSource; // 由 EnemyBase.Awake() 注入 private IStatusEffectable _statusEffectable; // Awake 缓存,避免每次受击调用 GetComponent private bool _isHurtBoxInvincible; private bool _isActive = true; // ── 事件频道 ────────────────────────────────────────────────────────── [SerializeField] private DamageInfoEventChannelSO _onDamageDealt; [SerializeField] private HitConfirmedEventChannelSO _onHitConfirmed; // ── 注入接口 ────────────────────────────────────────────────────────── public void SetShieldable(IShieldable shieldable) => _shieldable = shieldable; public void SetParrySystem(ParrySystem ps) => _parrySystem = ps; public void SetPoiseSource(IPoiseSource src) => _poiseSource = src; public void SetInvincible(bool value) => _isHurtBoxInvincible = value; public void SetActive(bool value) => _isActive = value; #if UNITY_EDITOR // 付给编辑器的只读属性——避免反射并限制编辑器与运行时字段名耐合性。 public object EditorOwner => _owner; public object EditorShieldable => _shieldable; public object EditorParrySystem => _parrySystem; public object EditorPoiseSource => _poiseSource; public object EditorStatusEffectable => _statusEffectable; #endif private void Awake() { _owner = GetComponentInParent(); _statusEffectable = GetComponentInParent(); if (_owner == null) Debug.LogWarning($"[HurtBox] {name}: 父节点中未找到 IDamageable 实现。", this); } /// /// 接受伤害(由 HitBox.OnTriggerEnter2D 直接调用)。 /// ⚠️ 方法名必须为 ReceiveDamage。 /// public void ReceiveDamage(DamageInfo info) { if (!_isActive || _owner == null) return; // 1. 无敌帧检查 if ((_owner.IsInvincible || _isHurtBoxInvincible) && !info.Flags.HasFlag(DamageFlags.IgnoreIFrame)) return; // 2. 弹反检查(_parrySystem == null 时跳过) // ParrySystem 只暴露窗口状态,伤害数据留在 Combat 层,无跨程序集数据依赖。 if (_parrySystem != null && info.Flags.HasFlag(DamageFlags.CanBeParried)) if (_parrySystem.ConsumeParry()) return; // 3. 霸体检查(_poiseSource == null 时跳过) if (!info.Flags.HasFlag(DamageFlags.ForceBreak) && _poiseSource != null) { PoiseLevel curPoise = _poiseSource.GetCurrentPoiseLevel(); if (curPoise == PoiseLevel.Unbreakable) return; if ((int)info.Break < (int)curPoise) { _onHitConfirmed?.Raise(new HitInfo { DamageInfo = info, HitPoint = transform.position, }); return; } } // 4. 护盾层拦截(玩家专属,在防御减免前) if (_shieldable != null && _shieldable.HasShield) { int passThrough = _shieldable.AbsorbDamage(info.Amount); if (passThrough <= 0) return; info.Amount = passThrough; } // 5. 计算 FinalDamage(防御减免,最低 1) int finalDamage = UnityEngine.Mathf.Max(1, info.Amount - _owner.Defense); info.Amount = finalDamage; info.FinalDamage = finalDamage; // 6. 调用 _owner.TakeDamage _owner.TakeDamage(info); // 7. 全局广播 _onDamageDealt?.Raise(info); _onHitConfirmed?.Raise(new HitInfo { DamageInfo = info, HitPoint = transform.position, }); // 8. 状态效果触发(DoT — Fire / Poison) // _statusEffectable 已在 Awake 中缓存,无需每次受击调用 GetComponent _statusEffectable?.ApplyStatusEffect(info.Type); } #if UNITY_EDITOR private void OnDrawGizmos() { var col = GetComponent(); if (col == null) return; // 激活时红色不透明,无敌/非激活时半透明 Gizmos.color = (_isActive && !_isHurtBoxInvincible) ? new UnityEngine.Color(1f, 0f, 0f, 0.45f) : new UnityEngine.Color(1f, 0f, 0f, 0.1f); Gizmos.DrawCube(col.bounds.center, col.bounds.size); Gizmos.color = new UnityEngine.Color(1f, 0f, 0f, 0.9f); Gizmos.DrawWireCube(col.bounds.center, col.bounds.size); } #endif } }