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;
}
}
}