Files
vampire-like/Assets/GameMain/Scripts/Runtime/EntityLogic/Weapon/WeaponLance/WeaponLance.cs
T
2026-09-15 10:13:58 +08:00

225 lines
7.8 KiB
C#

using System.Collections.Generic;
using SepCore.Components;
using SepCore.CustomUtility;
using SepCore.Definition;
using DG.Tweening;
using SepCore.Entity;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.Entity
{
public class WeaponLance : WeaponBase
{
private Sequence _attackSequence;
private Transform _attackParent;
// 前刺动画耗时。
[SerializeField] private float _attackDuration = 0.12f;
// 收枪返回耗时。
[SerializeField] private float _returnDuration = 0.18f;
[SerializeField] private int _maxHitColliders = 32;
private IWeaponAttackEffect _attackEffect;
private Collider[] _hitResults;
private readonly HashSet<int> _hitEntityIds = new();
public float HitHalfWidth => WeaponData.GetParamFloat(ParamType.HitHalfWidth);
public float HitHeight => WeaponData.GetParamFloat(ParamType.HitHeight);
public float PierceLength => WeaponData.GetParamFloat(ParamType.PierceLength);
public Vector3 StrikeDirection => _strikeDirection;
// 本次攻击锁定的前戳方向,避免受位移动画中的武器位置影响。
private Vector3 _strikeDirection = Vector3.forward;
// 本次攻击锁定的矩形判定中心。
private Vector3 _strikeCenter;
protected override void BuildStates()
{
RegisterState(new WeaponLanceIdleState());
RegisterState(new WeaponLanceAimState());
RegisterState(new WeaponLanceSeekState());
RegisterState(new WeaponLanceAttackState());
}
public override void Attack()
{
StopAttackTween(false);
FaceTargetImmediately();
_attackParent = CachedTransform.parent;
CacheStrikeSnapshot();
_isAttacking = true;
CachedTransform.SetParent(null);
Vector3 targetPos = CachedTransform.position + _strikeDirection * PierceLength;
_attackSequence = DOTween.Sequence();
_attackSequence.Append(CachedTransform.DOMove(targetPos, _attackDuration).SetEase(Ease.OutQuad));
_attackSequence.AppendCallback(() =>
{
_attackEffect.Play(this, _strikeCenter, _target, HitHalfWidth);
ApplyPierceDamage();
if (_attackParent != null)
{
CachedTransform.SetParent(_attackParent);
}
});
_attackSequence.Append(CachedTransform.DOLocalMove(Vector3.zero, _returnDuration).SetEase(Ease.InQuad));
_attackSequence.AppendCallback(() =>
{
_isAttacking = false;
_attackSequence = null;
_attackParent = null;
});
}
private void CacheStrikeSnapshot()
{
_strikeDirection = ResolvePlanarForward();
Vector3 strikeStart = CachedTransform.position;
strikeStart.y = ResolveStrikePlaneY();
_strikeCenter = strikeStart + _strikeDirection * (PierceLength * 0.5f);
}
private Vector3 ResolvePlanarForward()
{
Vector3 forward = CachedTransform.forward;
forward.y = 0f;
if (forward.sqrMagnitude <= Mathf.Epsilon)
{
forward = Vector3.forward;
}
forward.Normalize();
return forward;
}
private void ApplyPierceDamage()
{
if (HitHalfWidth <= 0f || PierceLength <= 0f) return;
Vector3 strikeDirection = _strikeDirection;
if (strikeDirection.sqrMagnitude <= Mathf.Epsilon) return;
Vector3 broadPhaseCenter = _strikeCenter;
Quaternion broadPhaseRotation = Quaternion.LookRotation(strikeDirection, Vector3.up);
if (TryQueueRectangleCollisionQuery(broadPhaseCenter, HitHalfWidth, PierceLength * 0.5f,
strikeDirection, Mathf.Max(1, _maxHitColliders)))
{
return;
}
if (_hitResults == null || _hitResults.Length == 0) return;
Vector3 halfExtents = new(HitHalfWidth, HitHeight * 0.5f, PierceLength * 0.5f);
int hitCount = Physics.OverlapBoxNonAlloc(broadPhaseCenter, halfExtents, _hitResults, broadPhaseRotation,
_hitMask, QueryTriggerInteraction.Collide);
_hitEntityIds.Clear();
for (int i = 0; i < hitCount; i++)
{
Collider collider = _hitResults[i];
if (collider == null) continue;
TargetableObject targetable = collider.GetComponentInParent<TargetableObject>();
if (targetable == null || !targetable.Available || targetable.IsDead) continue;
if (!_hitEntityIds.Add(targetable.Id)) continue;
if (!IsTargetInsidePierce(targetable, broadPhaseCenter, strikeDirection)) continue;
AIUtility.PerformCollision(targetable, this);
}
}
private bool IsTargetInsidePierce(TargetableObject targetable, Vector3 strikeCenter, Vector3 strikeDirection)
{
Vector3 delta = targetable.CachedTransform.position - strikeCenter;
delta.y = 0f;
Vector3 right = Vector3.Cross(Vector3.up, strikeDirection);
float forwardDistance = Vector3.Dot(delta, strikeDirection);
float lateralDistance = Vector3.Dot(delta, right);
float targetRadius = ResolveTargetCollisionRadius(targetable);
return Mathf.Abs(forwardDistance) <= PierceLength * 0.5f + targetRadius &&
Mathf.Abs(lateralDistance) <= HitHalfWidth + targetRadius;
}
private static float ResolveTargetCollisionRadius(TargetableObject targetable)
{
if (targetable == null)
{
return 0f;
}
MovementComponent movementComponent = targetable.GetComponent<MovementComponent>();
return movementComponent != null ? Mathf.Max(0f, movementComponent.EnemyBodyRadius) : 0f;
}
private float ResolveStrikePlaneY()
{
if (_target != null && _target.Available)
{
return _target.CachedTransform.position.y;
}
return _attackParent != null ? _attackParent.position.y : CachedTransform.position.y;
}
protected override bool OnWeaponShow(object userData)
{
int colliderCapacity = Mathf.Max(1, _maxHitColliders);
if (_hitResults == null || _hitResults.Length != colliderCapacity)
{
_hitResults = new Collider[colliderCapacity];
}
_attackEffect = new LanceThrustAttackEffect();
return true;
}
protected override void OnWeaponHide(object userData)
{
StopAttackTween(true);
_attackEffect = null;
}
protected override void OnWeaponDetach(EntityLogic parentEntity, object userData)
{
StopAttackTween(true);
base.OnWeaponDetach(parentEntity, userData);
}
protected override void OnEnabledChanged(bool enabled)
{
if (!enabled)
{
StopAttackTween(true);
}
}
private void StopAttackTween(bool resetTransform)
{
if (_attackSequence != null)
{
_attackSequence.Kill();
_attackSequence = null;
}
_isAttacking = false;
if (resetTransform && _attackParent != null)
{
CachedTransform.SetParent(_attackParent);
CachedTransform.localPosition = Vector3.zero;
CachedTransform.rotation = _cachedRotation;
}
_attackParent = null;
_hitEntityIds.Clear();
}
}
}