using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(CircleCollider2D))] public class AttackHitbox : MonoBehaviour { private CircleCollider2D _collider; private int _damage; private Vector2 _hitVelocity; private string _hitReactionState; private LayerMask _targetLayer; private readonly HashSet _alreadyHit = new(); private void Awake() { _collider = GetComponent(); // The player body does not collide with enemies; this trigger is the only attack contact. _collider.isTrigger = true; _collider.enabled = false; } public void Activate(ActionData data, Vector2 localPosition, Vector2 hitVelocity, LayerMask targetLayer) { transform.localPosition = localPosition; _collider.radius = data.Radius; _damage = data.Damage; _hitVelocity = hitVelocity; _hitReactionState = data.HitReactionAnimationState; _targetLayer = targetLayer; _alreadyHit.Clear(); _collider.enabled = true; // Catch enemies already inside the hitbox on the same frame it opens. ScanImmediateOverlap(); } public void Deactivate() { _collider.enabled = false; _alreadyHit.Clear(); } private void ScanImmediateOverlap() { Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, _collider.radius, _targetLayer); foreach (var hit in hits) TryDamage(hit); } private void OnTriggerEnter2D(Collider2D other) => TryDamage(other); private void OnTriggerStay2D(Collider2D other) => TryDamage(other); private void TryDamage(Collider2D other) { if ((_targetLayer.value & (1 << other.gameObject.layer)) == 0) return; // Hurtboxes may live on child objects, while damage handling usually lives on the root. if (!other.TryGetComponent(out var target)) target = other.GetComponentInParent(); if (target == null) return; if (_alreadyHit.Contains(target)) return; _alreadyHit.Add(target); target.TakeDamage(_damage, _hitVelocity, _hitReactionState); } }