using System.Collections.Generic; using UnityEngine; namespace BaseGames.Combat { /// /// 攻击判定盒。挂载在武器 Prefab 或技能 HitBox Prefab 的子节点上。 /// Phase 1 简化:直接挂在 Player Prefab 子节点 [HitBoxGround/Up/Down/Air]。 /// Collider2D 需设 IsTrigger = true,Layer = PlayerHitBox 或 EnemyHitBox。 /// [RequireComponent(typeof(Collider2D))] public class HitBox : MonoBehaviour { [SerializeField] private DamageSourceSO _defaultSource; [SerializeField] private float _hitCooldown = 0.1f; private DamageSourceSO _currentSource; private Transform _attackerTransform; private bool _isActive; /// 命中确认委托(PlayerCombat / EnemyCombat 订阅)。 public System.Action OnHitConfirmed; /// /// 激活 HitBox。source/attacker 均可选,未传则使用 Inspector 默认值。 /// ⚠️ 不存在 Activate(float duration) 重载。 /// public void Activate(DamageSourceSO source = null, Transform attacker = null) { _currentSource = source ?? _defaultSource; _attackerTransform = attacker ?? transform; _isActive = true; } public void Deactivate() => _isActive = false; private void Awake() { // 确保 Collider2D 是 Trigger var col = GetComponent(); if (!col.isTrigger) Debug.LogWarning($"[HitBox] {name}: Collider2D.isTrigger 应为 true。", this); } private void OnDisable() { _isActive = false; _hitCooldownTimers.Clear(); } private void OnTriggerEnter2D(Collider2D other) { if (!_isActive) return; if (_currentSource == null) { Debug.LogWarning($"[HitBox] {name}: 无 DamageSourceSO,跳过命中。", this); return; } if (!CheckCooldown(other)) return; Vector2 knockDir = ((Vector2)other.bounds.center - (Vector2)_attackerTransform.position).normalized; // ⚡ 零 GC:struct 工厂,就地赋值运行时字段 var info = DamageInfo.From(_currentSource); info.KnockbackDirection = knockDir; info.KnockbackForce = _currentSource.KnockbackForce; info.SourcePosition = _attackerTransform.position; info.SourceLayer = _attackerTransform.gameObject.layer; // ① 命中 HurtBox var hurtBox = other.GetComponent(); if (hurtBox != null) { hurtBox.ReceiveDamage(info); OnHitConfirmed?.Invoke(info); return; } // ② 命中 IBreakable(机关/障碍物) other.GetComponent()?.TryInteract(info); } // ── 同目标多帧命中冷却 ──────────────────────────────────────────────── private readonly Dictionary _hitCooldownTimers = new(); private bool CheckCooldown(Collider2D other) { float now = Time.time; if (_hitCooldownTimers.TryGetValue(other, out float last) && now - last < _hitCooldown) return false; _hitCooldownTimers[other] = now; return true; } } }