96 lines
3.7 KiB
C#
96 lines
3.7 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
[RequireComponent(typeof(CircleCollider2D))]
|
|
public class AttackHitbox : MonoBehaviour
|
|
{
|
|
public event System.Action<IDamageable> OnHit;
|
|
|
|
private CircleCollider2D _collider;
|
|
private int _damage;
|
|
private Vector2 _hitVelocity;
|
|
private Vector2 _hitSourcePosition;
|
|
private Vector2? _hitTargetPosition;
|
|
private float _hitPositionMinDistance;
|
|
private bool _correctHitTargetY;
|
|
private int _hitPositionSolidMask;
|
|
private float _hitPositionCorrectionDuration;
|
|
private string _hitReactionState;
|
|
private LayerMask _targetLayer;
|
|
private readonly HashSet<IDamageable> _alreadyHit = new();
|
|
|
|
private void Awake()
|
|
{
|
|
_collider = GetComponent<CircleCollider2D>();
|
|
// 플레이어 몸체는 적과 물리 충돌하지 않으므로, 공격 판정은 이 트리거만 사용한다.
|
|
_collider.isTrigger = true;
|
|
_collider.enabled = false;
|
|
}
|
|
|
|
public void Activate(ActionData data, Vector2 localPosition, Vector2 hitVelocity, Vector2 sourcePosition, Vector2? hitTargetPosition, bool correctHitTargetY, int hitPositionSolidMask, LayerMask targetLayer)
|
|
{
|
|
transform.localPosition = localPosition;
|
|
_collider.radius = data.Radius;
|
|
_damage = data.Damage;
|
|
_hitVelocity = hitVelocity;
|
|
_hitSourcePosition = sourcePosition;
|
|
_hitTargetPosition = hitTargetPosition;
|
|
_hitPositionMinDistance = Mathf.Abs(data.HitTargetOffset.x);
|
|
_correctHitTargetY = correctHitTargetY;
|
|
_hitPositionSolidMask = hitPositionSolidMask;
|
|
_hitPositionCorrectionDuration = data.HitPositionCorrectionDuration;
|
|
_hitReactionState = data.HitReactionAnimationState;
|
|
_targetLayer = targetLayer;
|
|
_alreadyHit.Clear();
|
|
_collider.enabled = true;
|
|
|
|
// 판정이 켜진 순간 이미 범위 안에 있던 적도 같은 프레임에 잡아낸다.
|
|
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;
|
|
|
|
// 피격 콜라이더는 자식에 있고, 데미지 처리는 루트에 있을 수 있다.
|
|
if (!other.TryGetComponent<IDamageable>(out var target))
|
|
target = other.GetComponentInParent<IDamageable>();
|
|
if (target == null) return;
|
|
if (_alreadyHit.Contains(target)) return;
|
|
|
|
_alreadyHit.Add(target);
|
|
Vector2? targetPosition = GetCorrectionTargetPosition(other);
|
|
target.TakeDamage(_damage, _hitVelocity, _hitReactionState, targetPosition, _correctHitTargetY, _hitPositionSolidMask, _hitPositionCorrectionDuration);
|
|
OnHit?.Invoke(target);
|
|
}
|
|
|
|
private Vector2? GetCorrectionTargetPosition(Collider2D other)
|
|
{
|
|
if (!_hitTargetPosition.HasValue) return null;
|
|
if (_hitPositionMinDistance <= 0.001f) return null;
|
|
|
|
Vector2 currentPosition = other.attachedRigidbody != null
|
|
? other.attachedRigidbody.position
|
|
: (Vector2)other.transform.root.position;
|
|
float currentDistance = Mathf.Abs(currentPosition.x - _hitSourcePosition.x);
|
|
|
|
return currentDistance < _hitPositionMinDistance ? _hitTargetPosition : null;
|
|
}
|
|
}
|