97 lines
3.6 KiB
C#
97 lines
3.6 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace BaseGames.Combat
|
||
{
|
||
/// <summary>
|
||
/// 攻击判定盒。挂载在武器 Prefab 或技能 HitBox Prefab 的子节点上。
|
||
/// Phase 1 简化:直接挂在 Player Prefab 子节点 [HitBoxGround/Up/Down/Air]。
|
||
/// Collider2D 需设 IsTrigger = true,Layer = PlayerHitBox 或 EnemyHitBox。
|
||
/// </summary>
|
||
[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;
|
||
|
||
/// <summary>命中确认委托(PlayerCombat / EnemyCombat 订阅)。</summary>
|
||
public System.Action<DamageInfo> OnHitConfirmed;
|
||
|
||
/// <summary>
|
||
/// 激活 HitBox。source/attacker 均可选,未传则使用 Inspector 默认值。
|
||
/// ⚠️ 不存在 Activate(float duration) 重载。
|
||
/// </summary>
|
||
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<Collider2D>();
|
||
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<HurtBox>();
|
||
if (hurtBox != null)
|
||
{
|
||
hurtBox.ReceiveDamage(info);
|
||
OnHitConfirmed?.Invoke(info);
|
||
return;
|
||
}
|
||
|
||
// ② 命中 IBreakable(机关/障碍物)
|
||
other.GetComponent<IBreakable>()?.TryInteract(info);
|
||
}
|
||
|
||
// ── 同目标多帧命中冷却 ────────────────────────────────────────────────
|
||
private readonly Dictionary<Collider2D, float> _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;
|
||
}
|
||
}
|
||
}
|