chore: initial commit
This commit is contained in:
96
Assets/Scripts/Combat/HitBox.cs
Normal file
96
Assets/Scripts/Combat/HitBox.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user