优化 WeaponLance 的攻击逻辑
This commit is contained in:
parent
8d21d53c4d
commit
0d7df18324
|
|
@ -5,4 +5,4 @@
|
||||||
2 202 手枪 Almighty_Icon White 130 0.05 120 1 15 10000 {} []
|
2 202 手枪 Almighty_Icon White 130 0.05 120 1 15 10000 {} []
|
||||||
3 203 斧头 Almighty_Icon White 100 0.1 150 2 5 10000 {"sectorAngle":120} []
|
3 203 斧头 Almighty_Icon White 100 0.1 150 2 5 10000 {"sectorAngle":120} []
|
||||||
4 204 闪电 Almighty_Icon White 150 0.08 80 3 12 10000 {"hitRadius":3} []
|
4 204 闪电 Almighty_Icon White 150 0.08 80 3 12 10000 {"hitRadius":3} []
|
||||||
5 205 长枪 Almighty_Icon White 100 0.1 100 1.5 7 10000 {"hitRadius":0.3,"thrustDistance":1.2,"pierceLength":0.3}
|
5 205 长枪 Almighty_Icon White 100 0.1 100 1.5 5 10000 {"hitHalfWidth":0.7,"pierceLength":4.5,"hitHeight":0.5,"hitCenterYOffset":0}
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,35 @@ namespace Entity.EntityData
|
||||||
public sealed class WeaponLanceParamsData
|
public sealed class WeaponLanceParamsData
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 枪尖命中半径。
|
/// 横向半宽,表示前戳矩形判定的一半宽度。
|
||||||
|
/// </summary>
|
||||||
|
public float HitHalfWidth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 旧字段兼容,未配置 HitHalfWidth 时回退使用。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float HitRadius { get; set; }
|
public float HitRadius { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 武器模型前刺的位移距离。
|
/// 前戳判定盒体的总高度。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float ThrustDistance { get; set; }
|
public float HitHeight { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 实际判定的前刺长度。
|
/// 判定盒体中心相对战斗平面的高度偏移。
|
||||||
|
/// </summary>
|
||||||
|
public float HitCenterYOffset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 前刺距离,同时驱动武器位移和命中长度。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float PierceLength { get; set; }
|
public float PierceLength { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 旧字段兼容,未配置 PierceLength 时回退使用。
|
||||||
|
/// </summary>
|
||||||
|
public float ThrustDistance { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 判定起点相对武器当前位置的前置偏移。
|
/// 判定起点相对武器当前位置的前置偏移。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Entity.Weapon
|
||||||
|
{
|
||||||
|
public sealed class LanceThrustAttackEffect : IWeaponAttackEffect
|
||||||
|
{
|
||||||
|
private readonly float _duration = 0.18f;
|
||||||
|
private readonly float _yOffset = 0.06f;
|
||||||
|
private readonly float _lineWidth = 0.05f;
|
||||||
|
private readonly Color _color = new(0.2f, 0.95f, 0.7f, 0.92f);
|
||||||
|
|
||||||
|
public LanceThrustAttackEffect()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public LanceThrustAttackEffect(float duration, float yOffset, float lineWidth, Color color)
|
||||||
|
{
|
||||||
|
_duration = duration;
|
||||||
|
_yOffset = yOffset;
|
||||||
|
_lineWidth = lineWidth;
|
||||||
|
_color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
|
||||||
|
{
|
||||||
|
if (weapon is not WeaponLance lance) return;
|
||||||
|
|
||||||
|
Vector3 forward = lance.StrikeDirection;
|
||||||
|
forward.y = 0f;
|
||||||
|
if (forward.sqrMagnitude <= Mathf.Epsilon)
|
||||||
|
{
|
||||||
|
forward = Vector3.forward;
|
||||||
|
}
|
||||||
|
|
||||||
|
forward.Normalize();
|
||||||
|
Vector3 right = Vector3.Cross(Vector3.up, forward);
|
||||||
|
float halfWidth = Mathf.Max(0.1f, lance.HitHalfWidth);
|
||||||
|
float halfLength = Mathf.Max(0.1f, lance.PierceLength * 0.5f);
|
||||||
|
Vector3 center = position + Vector3.up * _yOffset;
|
||||||
|
|
||||||
|
Vector3 frontCenter = center + forward * halfLength;
|
||||||
|
Vector3 backCenter = center - forward * halfLength;
|
||||||
|
|
||||||
|
Vector3[] corners =
|
||||||
|
{
|
||||||
|
frontCenter - right * halfWidth,
|
||||||
|
frontCenter + right * halfWidth,
|
||||||
|
backCenter + right * halfWidth,
|
||||||
|
backCenter - right * halfWidth,
|
||||||
|
};
|
||||||
|
|
||||||
|
GameObject indicator = new GameObject("LanceThrustIndicator");
|
||||||
|
indicator.transform.position = center;
|
||||||
|
|
||||||
|
LineRenderer line = indicator.AddComponent<LineRenderer>();
|
||||||
|
line.loop = true;
|
||||||
|
line.useWorldSpace = true;
|
||||||
|
line.positionCount = corners.Length;
|
||||||
|
line.startWidth = _lineWidth;
|
||||||
|
line.endWidth = _lineWidth;
|
||||||
|
line.startColor = _color;
|
||||||
|
line.endColor = _color;
|
||||||
|
|
||||||
|
Shader shader = Shader.Find("Sprites/Default");
|
||||||
|
if (shader == null)
|
||||||
|
{
|
||||||
|
shader = Shader.Find("Unlit/Color");
|
||||||
|
}
|
||||||
|
|
||||||
|
Material material = new Material(shader);
|
||||||
|
material.color = _color;
|
||||||
|
line.material = material;
|
||||||
|
|
||||||
|
for (int i = 0; i < corners.Length; i++)
|
||||||
|
{
|
||||||
|
line.SetPosition(i, corners[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.Destroy(indicator, Mathf.Max(0.01f, _duration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8c77c590c7233e544a9295ed41ff8827
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -246,6 +246,20 @@ namespace Entity.Weapon
|
||||||
halfAngleDeg, maxTargets);
|
halfAngleDeg, maxTargets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected bool TryQueueRectangleCollisionQuery(in Vector3 center, float halfWidth, float halfLength,
|
||||||
|
in Vector3 direction, int maxTargets = 16)
|
||||||
|
{
|
||||||
|
var simulationWorld = GameEntry.SimulationWorld;
|
||||||
|
if (simulationWorld == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
|
||||||
|
return simulationWorld.TryRequestRectangleCollision(Id, ownerEntityId, in center, halfWidth, halfLength,
|
||||||
|
in direction, maxTargets);
|
||||||
|
}
|
||||||
|
|
||||||
protected void SetTargetSelector(TargetSelectorType selectorType)
|
protected void SetTargetSelector(TargetSelectorType selectorType)
|
||||||
{
|
{
|
||||||
TargetSelector = CreateSelector(selectorType);
|
TargetSelector = CreateSelector(selectorType);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Components;
|
||||||
using CustomUtility;
|
using CustomUtility;
|
||||||
using Definition.DataStruct;
|
using Definition.DataStruct;
|
||||||
using Definition.Enum;
|
using Definition.Enum;
|
||||||
|
|
@ -33,14 +34,20 @@ namespace Entity.Weapon
|
||||||
private Collider[] _hitResults;
|
private Collider[] _hitResults;
|
||||||
private readonly HashSet<int> _hitEntityIds = new();
|
private readonly HashSet<int> _hitEntityIds = new();
|
||||||
|
|
||||||
// 枪尖判定半径。
|
// 前戳矩形判定的横向半宽。
|
||||||
private float _hitRadius;
|
private float _hitHalfWidth;
|
||||||
|
// 前戳矩形判定盒体的总高度。
|
||||||
|
private float _hitHeight;
|
||||||
|
// 盒体中心相对战斗平面的高度偏移。
|
||||||
|
private float _hitCenterYOffset;
|
||||||
// 从判定起点向前延伸的有效刺击长度。
|
// 从判定起点向前延伸的有效刺击长度。
|
||||||
private float _pierceLength;
|
private float _pierceLength;
|
||||||
// 判定起点相对武器当前位置的前移量。
|
// 判定起点相对武器当前位置的前移量。
|
||||||
private float _forwardOffset;
|
private float _forwardOffset;
|
||||||
// 武器模型本身向前突刺的位移长度。
|
// 本次攻击锁定的前戳方向,避免受位移动画中的武器位置影响。
|
||||||
private float _thrustDistance;
|
private Vector3 _strikeDirection = Vector3.forward;
|
||||||
|
// 本次攻击锁定的矩形判定中心。
|
||||||
|
private Vector3 _strikeCenter;
|
||||||
|
|
||||||
public override ImpactData GetImpactData()
|
public override ImpactData GetImpactData()
|
||||||
{
|
{
|
||||||
|
|
@ -59,18 +66,18 @@ namespace Entity.Weapon
|
||||||
{
|
{
|
||||||
StopAttackTween(false);
|
StopAttackTween(false);
|
||||||
FaceTargetImmediately();
|
FaceTargetImmediately();
|
||||||
|
CacheStrikeSnapshot();
|
||||||
|
|
||||||
_isAttacking = true;
|
_isAttacking = true;
|
||||||
_attackParent = CachedTransform.parent;
|
_attackParent = CachedTransform.parent;
|
||||||
CachedTransform.SetParent(null);
|
CachedTransform.SetParent(null);
|
||||||
|
|
||||||
Vector3 targetPos = CachedTransform.position + CachedTransform.forward * _thrustDistance;
|
Vector3 targetPos = CachedTransform.position + _strikeDirection * _pierceLength;
|
||||||
_attackSequence = DOTween.Sequence();
|
_attackSequence = DOTween.Sequence();
|
||||||
_attackSequence.Append(CachedTransform.DOMove(targetPos, _attackDuration).SetEase(Ease.OutQuad));
|
_attackSequence.Append(CachedTransform.DOMove(targetPos, _attackDuration).SetEase(Ease.OutQuad));
|
||||||
_attackSequence.AppendCallback(() =>
|
_attackSequence.AppendCallback(() =>
|
||||||
{
|
{
|
||||||
Vector3 strikeCenter = GetStrikeCenter();
|
_attackEffect?.Play(this, _strikeCenter, _target, _hitHalfWidth);
|
||||||
_attackEffect?.Play(this, strikeCenter, _target, _hitRadius);
|
|
||||||
ApplyPierceDamage();
|
ApplyPierceDamage();
|
||||||
|
|
||||||
if (_attackParent != null)
|
if (_attackParent != null)
|
||||||
|
|
@ -122,7 +129,17 @@ namespace Entity.Weapon
|
||||||
CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up);
|
CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector3 GetStrikeStart()
|
private void CacheStrikeSnapshot()
|
||||||
|
{
|
||||||
|
_strikeDirection = ResolvePlanarForward();
|
||||||
|
Vector3 strikeStart = CachedTransform.position;
|
||||||
|
strikeStart.y = ResolveStrikePlaneY();
|
||||||
|
strikeStart += _strikeDirection * _forwardOffset;
|
||||||
|
_strikeCenter = strikeStart + _strikeDirection * (_pierceLength * 0.5f);
|
||||||
|
_strikeCenter.y += _hitCenterYOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 ResolvePlanarForward()
|
||||||
{
|
{
|
||||||
Vector3 forward = CachedTransform.forward;
|
Vector3 forward = CachedTransform.forward;
|
||||||
forward.y = 0f;
|
forward.y = 0f;
|
||||||
|
|
@ -132,59 +149,35 @@ namespace Entity.Weapon
|
||||||
}
|
}
|
||||||
|
|
||||||
forward.Normalize();
|
forward.Normalize();
|
||||||
return CachedTransform.position + forward * _forwardOffset;
|
return forward;
|
||||||
}
|
|
||||||
|
|
||||||
private Vector3 GetStrikeEnd()
|
|
||||||
{
|
|
||||||
Vector3 start = GetStrikeStart();
|
|
||||||
Vector3 forward = CachedTransform.forward;
|
|
||||||
forward.y = 0f;
|
|
||||||
if (forward.sqrMagnitude <= Mathf.Epsilon)
|
|
||||||
{
|
|
||||||
forward = Vector3.forward;
|
|
||||||
}
|
|
||||||
|
|
||||||
forward.Normalize();
|
|
||||||
return start + forward * _pierceLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector3 GetStrikeCenter()
|
|
||||||
{
|
|
||||||
return Vector3.Lerp(GetStrikeStart(), GetStrikeEnd(), 0.5f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyPierceDamage()
|
private void ApplyPierceDamage()
|
||||||
{
|
{
|
||||||
if (_hitRadius <= 0f || _pierceLength <= 0f) return;
|
if (_hitHalfWidth <= 0f || _pierceLength <= 0f) return;
|
||||||
|
|
||||||
Vector3 strikeStart = GetStrikeStart();
|
Vector3 strikeDirection = _strikeDirection;
|
||||||
Vector3 strikeEnd = GetStrikeEnd();
|
|
||||||
Vector3 strikeDirection = strikeEnd - strikeStart;
|
|
||||||
strikeDirection.y = 0f;
|
|
||||||
if (strikeDirection.sqrMagnitude <= Mathf.Epsilon) return;
|
if (strikeDirection.sqrMagnitude <= Mathf.Epsilon) return;
|
||||||
|
|
||||||
strikeDirection.Normalize();
|
Vector3 broadPhaseCenter = _strikeCenter;
|
||||||
float halfAngle = Mathf.Rad2Deg * Mathf.Atan2(_hitRadius, Mathf.Max(0.01f, _pierceLength));
|
Quaternion broadPhaseRotation = Quaternion.LookRotation(strikeDirection, Vector3.up);
|
||||||
if (TryQueueSectorCollisionQuery(strikeStart, _pierceLength, in strikeDirection, halfAngle,
|
|
||||||
Mathf.Max(1, _maxHitColliders)))
|
if (TryQueueRectangleCollisionQuery(broadPhaseCenter, _hitHalfWidth, _pierceLength * 0.5f,
|
||||||
|
strikeDirection, Mathf.Max(1, _maxHitColliders)))
|
||||||
{
|
{
|
||||||
_hitEntityIds.Clear();
|
_hitEntityIds.Clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int capacity = Mathf.Max(1, _maxHitColliders);
|
int capacity = Mathf.Max(1, _maxHitColliders);
|
||||||
float broadPhaseRadius = _pierceLength * 0.5f + _hitRadius;
|
|
||||||
Vector3 broadPhaseCenter = Vector3.Lerp(strikeStart, strikeEnd, 0.5f);
|
|
||||||
|
|
||||||
if (_hitResults == null || _hitResults.Length != capacity)
|
if (_hitResults == null || _hitResults.Length != capacity)
|
||||||
{
|
{
|
||||||
_hitResults = new Collider[capacity];
|
_hitResults = new Collider[capacity];
|
||||||
}
|
}
|
||||||
|
|
||||||
int hitCount = Physics.OverlapSphereNonAlloc(broadPhaseCenter, broadPhaseRadius, _hitResults, _hitMask,
|
Vector3 halfExtents = new(_hitHalfWidth, _hitHeight * 0.5f, _pierceLength * 0.5f);
|
||||||
QueryTriggerInteraction.Collide);
|
int hitCount = Physics.OverlapBoxNonAlloc(broadPhaseCenter, halfExtents, _hitResults, broadPhaseRotation,
|
||||||
|
_hitMask, QueryTriggerInteraction.Collide);
|
||||||
_hitEntityIds.Clear();
|
_hitEntityIds.Clear();
|
||||||
for (int i = 0; i < hitCount; i++)
|
for (int i = 0; i < hitCount; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -195,24 +188,55 @@ namespace Entity.Weapon
|
||||||
if (targetable == null || !targetable.Available || targetable.IsDead) continue;
|
if (targetable == null || !targetable.Available || targetable.IsDead) continue;
|
||||||
if (!_hitEntityIds.Add(targetable.Id)) continue;
|
if (!_hitEntityIds.Add(targetable.Id)) continue;
|
||||||
|
|
||||||
if (!IsTargetInsidePierce(targetable, strikeStart, strikeDirection)) continue;
|
if (!IsTargetInsidePierce(targetable, broadPhaseCenter, strikeDirection)) continue;
|
||||||
|
|
||||||
AIUtility.PerformCollision(targetable, this);
|
AIUtility.PerformCollision(targetable, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsTargetInsidePierce(TargetableObject targetable, Vector3 strikeStart, Vector3 strikeDirection)
|
private bool IsTargetInsidePierce(TargetableObject targetable, Vector3 strikeCenter, Vector3 strikeDirection)
|
||||||
{
|
{
|
||||||
Vector3 toTarget = targetable.CachedTransform.position - strikeStart;
|
Vector3 delta = targetable.CachedTransform.position - strikeCenter;
|
||||||
toTarget.y = 0f;
|
|
||||||
|
|
||||||
float projection = Vector3.Dot(toTarget, strikeDirection);
|
|
||||||
if (projection < 0f || projection > _pierceLength) return false;
|
|
||||||
|
|
||||||
Vector3 closestPoint = strikeStart + strikeDirection * projection;
|
|
||||||
Vector3 delta = targetable.CachedTransform.position - closestPoint;
|
|
||||||
delta.y = 0f;
|
delta.y = 0f;
|
||||||
return delta.sqrMagnitude <= _hitRadius * _hitRadius;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_attackParent != null)
|
||||||
|
{
|
||||||
|
return _attackParent.position.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CachedTransform.parent != null)
|
||||||
|
{
|
||||||
|
return CachedTransform.parent.position.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CachedTransform.position.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnWeaponShow(object userData)
|
protected override bool OnWeaponShow(object userData)
|
||||||
|
|
@ -226,15 +250,21 @@ namespace Entity.Weapon
|
||||||
_cachedRotation = CachedTransform.rotation;
|
_cachedRotation = CachedTransform.rotation;
|
||||||
|
|
||||||
WeaponLanceParamsData paramsData = _weaponData.ParamsData;
|
WeaponLanceParamsData paramsData = _weaponData.ParamsData;
|
||||||
_hitRadius = paramsData != null && paramsData.HitRadius > 0f
|
float configuredHalfWidth = paramsData != null && paramsData.HitHalfWidth > 0f
|
||||||
? Mathf.Max(0.1f, paramsData.HitRadius)
|
? paramsData.HitHalfWidth
|
||||||
: 0.45f;
|
: paramsData != null && paramsData.HitRadius > 0f
|
||||||
_thrustDistance = paramsData != null && paramsData.ThrustDistance > 0f
|
? paramsData.HitRadius
|
||||||
? paramsData.ThrustDistance
|
: 0.45f;
|
||||||
: _weaponData.AttackRange;
|
_hitHalfWidth = Mathf.Max(0.1f, configuredHalfWidth);
|
||||||
|
_hitHeight = paramsData != null && paramsData.HitHeight > 0f
|
||||||
|
? Mathf.Max(0.2f, paramsData.HitHeight)
|
||||||
|
: 4f;
|
||||||
|
_hitCenterYOffset = paramsData != null ? paramsData.HitCenterYOffset : 0f;
|
||||||
_pierceLength = paramsData != null && paramsData.PierceLength > 0f
|
_pierceLength = paramsData != null && paramsData.PierceLength > 0f
|
||||||
? paramsData.PierceLength
|
? paramsData.PierceLength
|
||||||
: Mathf.Max(_weaponData.AttackRange, _thrustDistance);
|
: paramsData != null && paramsData.ThrustDistance > 0f
|
||||||
|
? paramsData.ThrustDistance
|
||||||
|
: _weaponData.AttackRange;
|
||||||
_forwardOffset = paramsData != null && paramsData.ForwardOffset > 0f
|
_forwardOffset = paramsData != null && paramsData.ForwardOffset > 0f
|
||||||
? paramsData.ForwardOffset
|
? paramsData.ForwardOffset
|
||||||
: 0f;
|
: 0f;
|
||||||
|
|
@ -260,7 +290,7 @@ namespace Entity.Weapon
|
||||||
_hitResults = new Collider[colliderCapacity];
|
_hitResults = new Collider[colliderCapacity];
|
||||||
}
|
}
|
||||||
|
|
||||||
_attackEffect = new KnifeRangeAttackEffect();
|
_attackEffect = new LanceThrustAttackEffect();
|
||||||
|
|
||||||
if (_weaponData.OwnerCamp == CampType.Player)
|
if (_weaponData.OwnerCamp == CampType.Player)
|
||||||
{
|
{
|
||||||
|
|
@ -321,5 +351,9 @@ namespace Entity.Weapon
|
||||||
_attackParent = null;
|
_attackParent = null;
|
||||||
_hitEntityIds.Clear();
|
_hitEntityIds.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float HitHalfWidth => _hitHalfWidth;
|
||||||
|
public float PierceLength => _pierceLength;
|
||||||
|
public Vector3 StrikeDirection => _strikeDirection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,13 +65,15 @@ namespace Simulation
|
||||||
MaxTargets = math.max(1, maxTargets),
|
MaxTargets = math.max(1, maxTargets),
|
||||||
ShapeType = CollisionShapeCircle,
|
ShapeType = CollisionShapeCircle,
|
||||||
Direction = new float3(0f, 0f, 1f),
|
Direction = new float3(0f, 0f, 1f),
|
||||||
HalfAngleDeg = 180f
|
HalfAngleDeg = 180f,
|
||||||
|
HalfWidth = 0f,
|
||||||
|
HalfLength = 0f
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddAreaCollisionQuery(int queryId, int sourceEntityId, int sourceOwnerEntityId,
|
private void AddAreaCollisionQuery(int queryId, int sourceEntityId, int sourceOwnerEntityId,
|
||||||
bool sourceWasActiveAtQueryTime, in Vector3 center, float radius, int maxTargets, int shapeType,
|
bool sourceWasActiveAtQueryTime, in Vector3 center, float radius, int maxTargets, int shapeType,
|
||||||
in Vector3 direction, float halfAngleDeg)
|
in Vector3 direction, float halfAngleDeg, float halfWidth, float halfLength)
|
||||||
{
|
{
|
||||||
if (!_collisionQueryInputs.IsCreated || radius <= 0f)
|
if (!_collisionQueryInputs.IsCreated || radius <= 0f)
|
||||||
{
|
{
|
||||||
|
|
@ -101,7 +103,9 @@ namespace Simulation
|
||||||
MaxTargets = math.max(1, maxTargets),
|
MaxTargets = math.max(1, maxTargets),
|
||||||
ShapeType = shapeType,
|
ShapeType = shapeType,
|
||||||
Direction = new float3(normalizedDirection.x, normalizedDirection.y, normalizedDirection.z),
|
Direction = new float3(normalizedDirection.x, normalizedDirection.y, normalizedDirection.z),
|
||||||
HalfAngleDeg = Mathf.Clamp(halfAngleDeg, 0f, 180f)
|
HalfAngleDeg = Mathf.Clamp(halfAngleDeg, 0f, 180f),
|
||||||
|
HalfWidth = Mathf.Max(0f, halfWidth),
|
||||||
|
HalfLength = Mathf.Max(0f, halfLength)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ namespace Simulation
|
||||||
private const int CollisionSourceTypeArea = 2;
|
private const int CollisionSourceTypeArea = 2;
|
||||||
private const int CollisionShapeCircle = 0;
|
private const int CollisionShapeCircle = 0;
|
||||||
private const int CollisionShapeSector = 1;
|
private const int CollisionShapeSector = 1;
|
||||||
|
private const int CollisionShapeRectangle = 2;
|
||||||
|
|
||||||
// Kept as top-level fields because current regression tests reflect them directly.
|
// Kept as top-level fields because current regression tests reflect them directly.
|
||||||
private NativeList<CollisionQueryData> _collisionQueryInputs;
|
private NativeList<CollisionQueryData> _collisionQueryInputs;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ namespace Simulation
|
||||||
public int ShapeType;
|
public int ShapeType;
|
||||||
public Vector3 Direction;
|
public Vector3 Direction;
|
||||||
public float HalfAngleDeg;
|
public float HalfAngleDeg;
|
||||||
|
public float HalfWidth;
|
||||||
|
public float HalfLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,6 +17,8 @@ namespace Simulation
|
||||||
public int ShapeType;
|
public int ShapeType;
|
||||||
public float3 Direction;
|
public float3 Direction;
|
||||||
public float HalfAngleDeg;
|
public float HalfAngleDeg;
|
||||||
|
public float HalfWidth;
|
||||||
|
public float HalfLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +23,6 @@ namespace Simulation
|
||||||
public void Execute(int index)
|
public void Execute(int index)
|
||||||
{
|
{
|
||||||
CollisionQueryData query = Queries[index];
|
CollisionQueryData query = Queries[index];
|
||||||
float radiusSqr = query.Radius * query.Radius;
|
|
||||||
int centerCellX = (int)math.floor(query.Position.x / CellSize);
|
int centerCellX = (int)math.floor(query.Position.x / CellSize);
|
||||||
int centerCellZ = (int)math.floor(query.Position.z / CellSize);
|
int centerCellZ = (int)math.floor(query.Position.z / CellSize);
|
||||||
int queryRange = math.max(1, (int)math.ceil(query.Radius / CellSize));
|
int queryRange = math.max(1, (int)math.ceil(query.Radius / CellSize));
|
||||||
|
|
@ -34,10 +33,14 @@ namespace Simulation
|
||||||
query.SourceOwnerEntityId != PlayerTargetEntityId)
|
query.SourceOwnerEntityId != PlayerTargetEntityId)
|
||||||
{
|
{
|
||||||
float3 playerPosition = PlayerPosition;
|
float3 playerPosition = PlayerPosition;
|
||||||
playerPosition.y = query.Position.y;
|
if (query.SourceType == CollisionSourceTypeArea)
|
||||||
|
{
|
||||||
|
playerPosition.y = query.Position.y;
|
||||||
|
}
|
||||||
float3 playerDelta = playerPosition - query.Position;
|
float3 playerDelta = playerPosition - query.Position;
|
||||||
float playerSqrDistance = math.lengthsq(playerDelta);
|
float playerSqrDistance = math.lengthsq(playerDelta);
|
||||||
if (playerSqrDistance <= radiusSqr)
|
float playerRadiusSqr = query.Radius * query.Radius;
|
||||||
|
if (playerSqrDistance <= playerRadiusSqr)
|
||||||
{
|
{
|
||||||
Candidates.AddNoResize(new CollisionCandidateData
|
Candidates.AddNoResize(new CollisionCandidateData
|
||||||
{
|
{
|
||||||
|
|
@ -86,12 +89,19 @@ namespace Simulation
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float deltaY = query.SourceType == CollisionSourceTypeArea
|
||||||
|
? 0f
|
||||||
|
: enemy.Position.y - query.Position.y;
|
||||||
float3 delta = new float3(
|
float3 delta = new float3(
|
||||||
enemy.Position.x - query.Position.x,
|
enemy.Position.x - query.Position.x,
|
||||||
enemy.Position.y - query.Position.y,
|
deltaY,
|
||||||
enemy.Position.z - query.Position.z);
|
enemy.Position.z - query.Position.z);
|
||||||
float sqrDistance = math.lengthsq(delta);
|
float sqrDistance = math.lengthsq(delta);
|
||||||
if (sqrDistance > radiusSqr)
|
float targetRadius = query.SourceType == CollisionSourceTypeArea
|
||||||
|
? math.max(0f, enemy.EnemyBodyRadius)
|
||||||
|
: 0f;
|
||||||
|
float effectiveRadius = query.Radius + targetRadius;
|
||||||
|
if (sqrDistance > effectiveRadius * effectiveRadius)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,8 @@ namespace Simulation
|
||||||
AreaCollisionRequestData request = _areaCollisionRequests[i];
|
AreaCollisionRequestData request = _areaCollisionRequests[i];
|
||||||
AddAreaCollisionQuery(queryId, request.SourceEntityId, request.SourceOwnerEntityId,
|
AddAreaCollisionQuery(queryId, request.SourceEntityId, request.SourceOwnerEntityId,
|
||||||
request.SourceWasActiveAtQueryTime, in request.Center, request.Radius, request.MaxTargets,
|
request.SourceWasActiveAtQueryTime, in request.Center, request.Radius, request.MaxTargets,
|
||||||
request.ShapeType, in request.Direction, request.HalfAngleDeg);
|
request.ShapeType, in request.Direction, request.HalfAngleDeg, request.HalfWidth,
|
||||||
|
request.HalfLength);
|
||||||
queryId++;
|
queryId++;
|
||||||
builtAreaQueryCount++;
|
builtAreaQueryCount++;
|
||||||
if (request.Radius > maxQueryRadius)
|
if (request.Radius > maxQueryRadius)
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,29 @@ namespace Simulation
|
||||||
float radius, int maxTargets = 16)
|
float radius, int maxTargets = 16)
|
||||||
{
|
{
|
||||||
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
||||||
maxTargets, CollisionShapeCircle, Vector3.forward, 180f);
|
maxTargets, CollisionShapeCircle, Vector3.forward, 180f, 0f, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRequestSectorCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
public bool TryRequestSectorCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
||||||
float radius, in Vector3 direction, float halfAngleDeg, int maxTargets = 16)
|
float radius, in Vector3 direction, float halfAngleDeg, int maxTargets = 16)
|
||||||
{
|
{
|
||||||
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
||||||
maxTargets, CollisionShapeSector, direction, halfAngleDeg);
|
maxTargets, CollisionShapeSector, direction, halfAngleDeg, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryRequestRectangleCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
||||||
|
float halfWidth, float halfLength, in Vector3 direction, int maxTargets = 16)
|
||||||
|
{
|
||||||
|
float safeHalfWidth = Mathf.Max(0.01f, halfWidth);
|
||||||
|
float safeHalfLength = Mathf.Max(0.01f, halfLength);
|
||||||
|
float boundingRadius = Mathf.Sqrt(safeHalfWidth * safeHalfWidth + safeHalfLength * safeHalfLength);
|
||||||
|
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, boundingRadius,
|
||||||
|
maxTargets, CollisionShapeRectangle, direction, 0f, safeHalfWidth, safeHalfLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryRequestAreaCollisionInternal(int sourceEntityId, int sourceOwnerEntityId,
|
private bool TryRequestAreaCollisionInternal(int sourceEntityId, int sourceOwnerEntityId,
|
||||||
in Vector3 center, float radius, int maxTargets, int shapeType, in Vector3 direction, float halfAngleDeg)
|
in Vector3 center, float radius, int maxTargets, int shapeType, in Vector3 direction, float halfAngleDeg,
|
||||||
|
float halfWidth, float halfLength)
|
||||||
{
|
{
|
||||||
if (!_useSimulationMovement)
|
if (!_useSimulationMovement)
|
||||||
{
|
{
|
||||||
|
|
@ -58,7 +69,9 @@ namespace Simulation
|
||||||
MaxTargets = Mathf.Max(1, maxTargets),
|
MaxTargets = Mathf.Max(1, maxTargets),
|
||||||
ShapeType = shapeType,
|
ShapeType = shapeType,
|
||||||
Direction = normalizedDirection,
|
Direction = normalizedDirection,
|
||||||
HalfAngleDeg = Mathf.Clamp(halfAngleDeg, 0f, 180f)
|
HalfAngleDeg = Mathf.Clamp(halfAngleDeg, 0f, 180f),
|
||||||
|
HalfWidth = Mathf.Max(0f, halfWidth),
|
||||||
|
HalfLength = Mathf.Max(0f, halfLength)
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Components;
|
||||||
using CustomDebugger;
|
using CustomDebugger;
|
||||||
using CustomUtility;
|
using CustomUtility;
|
||||||
using Definition.DataStruct;
|
using Definition.DataStruct;
|
||||||
|
|
@ -188,7 +189,8 @@ namespace Simulation
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsAreaTargetInsidePreciseShape(in query, target))
|
float targetRadius = ResolveAreaTargetRadius(target);
|
||||||
|
if (!IsAreaTargetInsidePreciseShape(in query, target, targetRadius))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +232,24 @@ namespace Simulation
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsAreaTargetInsidePreciseShape(in CollisionQueryData query, TargetableObject target)
|
private float ResolveAreaTargetRadius(TargetableObject target)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target is EnemyBase && TryGetEnemyData(target.Id, out EnemySimData enemyData))
|
||||||
|
{
|
||||||
|
return Mathf.Max(0f, enemyData.EnemyBodyRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovementComponent movementComponent = target.GetComponent<MovementComponent>();
|
||||||
|
return movementComponent != null ? Mathf.Max(0f, movementComponent.EnemyBodyRadius) : 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAreaTargetInsidePreciseShape(in CollisionQueryData query, TargetableObject target,
|
||||||
|
float targetRadius)
|
||||||
{
|
{
|
||||||
if (target == null || target.CachedTransform == null)
|
if (target == null || target.CachedTransform == null)
|
||||||
{
|
{
|
||||||
|
|
@ -241,7 +260,7 @@ namespace Simulation
|
||||||
Vector3 toTarget = target.CachedTransform.position - center;
|
Vector3 toTarget = target.CachedTransform.position - center;
|
||||||
toTarget.y = 0f;
|
toTarget.y = 0f;
|
||||||
|
|
||||||
float radius = Mathf.Max(0.01f, query.Radius);
|
float radius = Mathf.Max(0.01f, query.Radius + Mathf.Max(0f, targetRadius));
|
||||||
float radiusSqr = radius * radius;
|
float radiusSqr = radius * radius;
|
||||||
float sqrDistance = toTarget.sqrMagnitude;
|
float sqrDistance = toTarget.sqrMagnitude;
|
||||||
if (sqrDistance > radiusSqr)
|
if (sqrDistance > radiusSqr)
|
||||||
|
|
@ -249,6 +268,27 @@ namespace Simulation
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (query.ShapeType == CollisionShapeRectangle)
|
||||||
|
{
|
||||||
|
Vector3 forwardRect = new Vector3(query.Direction.x, query.Direction.y, query.Direction.z);
|
||||||
|
forwardRect.y = 0f;
|
||||||
|
if (forwardRect.sqrMagnitude <= Mathf.Epsilon)
|
||||||
|
{
|
||||||
|
forwardRect = Vector3.forward;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
forwardRect.Normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 rightRect = Vector3.Cross(Vector3.up, forwardRect);
|
||||||
|
float halfWidth = Mathf.Max(0.01f, query.HalfWidth + Mathf.Max(0f, targetRadius));
|
||||||
|
float halfLength = Mathf.Max(0.01f, query.HalfLength + Mathf.Max(0f, targetRadius));
|
||||||
|
float forwardDistance = Vector3.Dot(toTarget, forwardRect);
|
||||||
|
float lateralDistance = Vector3.Dot(toTarget, rightRect);
|
||||||
|
return Mathf.Abs(forwardDistance) <= halfLength && Mathf.Abs(lateralDistance) <= halfWidth;
|
||||||
|
}
|
||||||
|
|
||||||
if (query.ShapeType != CollisionShapeSector)
|
if (query.ShapeType != CollisionShapeSector)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,13 @@ namespace CustomUtility
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<string, string> _paramsDict = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly Dictionary<string, string> _paramsDict = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{"hitRadius", "伤害范围"},
|
{"hitHalfWidth", "横向半宽"},
|
||||||
|
{"hitRadius", "攻击半宽"},
|
||||||
|
{"hitHeight", "判定高度"},
|
||||||
|
{"hitCenterYOffset", "判定高度偏移"},
|
||||||
{"sectorAngle", "攻击角度"},
|
{"sectorAngle", "攻击角度"},
|
||||||
{"pierceLength", "前戳距离"},
|
{"pierceLength", "前戳距离"},
|
||||||
{"thrustDistance", "枪尖长度"},
|
{"thrustDistance", "前戳距离(旧)"},
|
||||||
{"forwardOffset", "前置偏移"},
|
{"forwardOffset", "前置偏移"},
|
||||||
{"rotateSpeed", "转向速度"},
|
{"rotateSpeed", "转向速度"},
|
||||||
{"attackDuration", "突刺时长"},
|
{"attackDuration", "突刺时长"},
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue