优化 WeaponLance 的攻击逻辑

This commit is contained in:
SepComet 2026-03-20 09:55:52 +08:00
parent 8d21d53c4d
commit 0d7df18324
16 changed files with 321 additions and 89 deletions

View File

@ -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}

View File

@ -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>

View File

@ -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));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c77c590c7233e544a9295ed41ff8827
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);

View File

@ -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
: paramsData != null && paramsData.HitRadius > 0f
? paramsData.HitRadius
: 0.45f; : 0.45f;
_thrustDistance = paramsData != null && paramsData.ThrustDistance > 0f _hitHalfWidth = Mathf.Max(0.1f, configuredHalfWidth);
? paramsData.ThrustDistance _hitHeight = paramsData != null && paramsData.HitHeight > 0f
: _weaponData.AttackRange; ? 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;
} }
} }

View File

@ -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)
}); });
} }

View File

@ -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;

View File

@ -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;
} }
} }
} }

View File

@ -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;
} }
} }
} }

View File

@ -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;
if (query.SourceType == CollisionSourceTypeArea)
{
playerPosition.y = query.Position.y; 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;
} }

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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.