diff --git a/Assets/GameMain/DataTables/Weapon.txt b/Assets/GameMain/DataTables/Weapon.txt index 5075f13..921e7c9 100644 --- a/Assets/GameMain/DataTables/Weapon.txt +++ b/Assets/GameMain/DataTables/Weapon.txt @@ -5,4 +5,4 @@ 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} [] 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} diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs index c3c3cce..b3183c1 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs @@ -7,20 +7,35 @@ namespace Entity.EntityData public sealed class WeaponLanceParamsData { /// - /// 枪尖命中半径。 + /// 横向半宽,表示前戳矩形判定的一半宽度。 + /// + public float HitHalfWidth { get; set; } + + /// + /// 旧字段兼容,未配置 HitHalfWidth 时回退使用。 /// public float HitRadius { get; set; } /// - /// 武器模型前刺的位移距离。 + /// 前戳判定盒体的总高度。 /// - public float ThrustDistance { get; set; } + public float HitHeight { get; set; } /// - /// 实际判定的前刺长度。 + /// 判定盒体中心相对战斗平面的高度偏移。 + /// + public float HitCenterYOffset { get; set; } + + /// + /// 前刺距离,同时驱动武器位移和命中长度。 /// public float PierceLength { get; set; } + /// + /// 旧字段兼容,未配置 PierceLength 时回退使用。 + /// + public float ThrustDistance { get; set; } + /// /// 判定起点相对武器当前位置的前置偏移。 /// diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/AttackEffects/LanceThrustAttackEffect.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/AttackEffects/LanceThrustAttackEffect.cs new file mode 100644 index 0000000..b358215 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/AttackEffects/LanceThrustAttackEffect.cs @@ -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(); + 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)); + } + } +} diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/AttackEffects/LanceThrustAttackEffect.cs.meta b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/AttackEffects/LanceThrustAttackEffect.cs.meta new file mode 100644 index 0000000..ebbae42 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/AttackEffects/LanceThrustAttackEffect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c77c590c7233e544a9295ed41ff8827 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs index 3a4fd7c..0f67bd6 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs @@ -246,6 +246,20 @@ namespace Entity.Weapon 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) { TargetSelector = CreateSelector(selectorType); @@ -304,4 +318,4 @@ namespace Entity.Weapon public abstract void OnLeave(); public override string ToString() => State.ToString(); } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs index 2a94a49..ddf076f 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Components; using CustomUtility; using Definition.DataStruct; using Definition.Enum; @@ -33,14 +34,20 @@ namespace Entity.Weapon private Collider[] _hitResults; private readonly HashSet _hitEntityIds = new(); - // 枪尖判定半径。 - private float _hitRadius; + // 前戳矩形判定的横向半宽。 + private float _hitHalfWidth; + // 前戳矩形判定盒体的总高度。 + private float _hitHeight; + // 盒体中心相对战斗平面的高度偏移。 + private float _hitCenterYOffset; // 从判定起点向前延伸的有效刺击长度。 private float _pierceLength; // 判定起点相对武器当前位置的前移量。 private float _forwardOffset; - // 武器模型本身向前突刺的位移长度。 - private float _thrustDistance; + // 本次攻击锁定的前戳方向,避免受位移动画中的武器位置影响。 + private Vector3 _strikeDirection = Vector3.forward; + // 本次攻击锁定的矩形判定中心。 + private Vector3 _strikeCenter; public override ImpactData GetImpactData() { @@ -59,18 +66,18 @@ namespace Entity.Weapon { StopAttackTween(false); FaceTargetImmediately(); + CacheStrikeSnapshot(); _isAttacking = true; _attackParent = CachedTransform.parent; CachedTransform.SetParent(null); - Vector3 targetPos = CachedTransform.position + CachedTransform.forward * _thrustDistance; + Vector3 targetPos = CachedTransform.position + _strikeDirection * _pierceLength; _attackSequence = DOTween.Sequence(); _attackSequence.Append(CachedTransform.DOMove(targetPos, _attackDuration).SetEase(Ease.OutQuad)); _attackSequence.AppendCallback(() => { - Vector3 strikeCenter = GetStrikeCenter(); - _attackEffect?.Play(this, strikeCenter, _target, _hitRadius); + _attackEffect?.Play(this, _strikeCenter, _target, _hitHalfWidth); ApplyPierceDamage(); if (_attackParent != null) @@ -122,7 +129,17 @@ namespace Entity.Weapon 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; forward.y = 0f; @@ -132,59 +149,35 @@ namespace Entity.Weapon } forward.Normalize(); - return CachedTransform.position + forward * _forwardOffset; - } - - 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); + return forward; } private void ApplyPierceDamage() { - if (_hitRadius <= 0f || _pierceLength <= 0f) return; + if (_hitHalfWidth <= 0f || _pierceLength <= 0f) return; - Vector3 strikeStart = GetStrikeStart(); - Vector3 strikeEnd = GetStrikeEnd(); - Vector3 strikeDirection = strikeEnd - strikeStart; - strikeDirection.y = 0f; + Vector3 strikeDirection = _strikeDirection; if (strikeDirection.sqrMagnitude <= Mathf.Epsilon) return; - strikeDirection.Normalize(); - float halfAngle = Mathf.Rad2Deg * Mathf.Atan2(_hitRadius, Mathf.Max(0.01f, _pierceLength)); - if (TryQueueSectorCollisionQuery(strikeStart, _pierceLength, in strikeDirection, halfAngle, - Mathf.Max(1, _maxHitColliders))) + Vector3 broadPhaseCenter = _strikeCenter; + Quaternion broadPhaseRotation = Quaternion.LookRotation(strikeDirection, Vector3.up); + + if (TryQueueRectangleCollisionQuery(broadPhaseCenter, _hitHalfWidth, _pierceLength * 0.5f, + strikeDirection, Mathf.Max(1, _maxHitColliders))) { _hitEntityIds.Clear(); return; } 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) { _hitResults = new Collider[capacity]; } - int hitCount = Physics.OverlapSphereNonAlloc(broadPhaseCenter, broadPhaseRadius, _hitResults, _hitMask, - QueryTriggerInteraction.Collide); - + 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++) { @@ -195,24 +188,55 @@ namespace Entity.Weapon if (targetable == null || !targetable.Available || targetable.IsDead) continue; if (!_hitEntityIds.Add(targetable.Id)) continue; - if (!IsTargetInsidePierce(targetable, strikeStart, strikeDirection)) continue; + if (!IsTargetInsidePierce(targetable, broadPhaseCenter, strikeDirection)) continue; 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; - 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; + Vector3 delta = targetable.CachedTransform.position - strikeCenter; 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(); + 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) @@ -226,15 +250,21 @@ namespace Entity.Weapon _cachedRotation = CachedTransform.rotation; WeaponLanceParamsData paramsData = _weaponData.ParamsData; - _hitRadius = paramsData != null && paramsData.HitRadius > 0f - ? Mathf.Max(0.1f, paramsData.HitRadius) - : 0.45f; - _thrustDistance = paramsData != null && paramsData.ThrustDistance > 0f - ? paramsData.ThrustDistance - : _weaponData.AttackRange; + float configuredHalfWidth = paramsData != null && paramsData.HitHalfWidth > 0f + ? paramsData.HitHalfWidth + : paramsData != null && paramsData.HitRadius > 0f + ? paramsData.HitRadius + : 0.45f; + _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 ? paramsData.PierceLength - : Mathf.Max(_weaponData.AttackRange, _thrustDistance); + : paramsData != null && paramsData.ThrustDistance > 0f + ? paramsData.ThrustDistance + : _weaponData.AttackRange; _forwardOffset = paramsData != null && paramsData.ForwardOffset > 0f ? paramsData.ForwardOffset : 0f; @@ -260,7 +290,7 @@ namespace Entity.Weapon _hitResults = new Collider[colliderCapacity]; } - _attackEffect = new KnifeRangeAttackEffect(); + _attackEffect = new LanceThrustAttackEffect(); if (_weaponData.OwnerCamp == CampType.Player) { @@ -321,5 +351,9 @@ namespace Entity.Weapon _attackParent = null; _hitEntityIds.Clear(); } + + public float HitHalfWidth => _hitHalfWidth; + public float PierceLength => _pierceLength; + public Vector3 StrikeDirection => _strikeDirection; } } diff --git a/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.CollisionTransient.cs b/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.CollisionTransient.cs index ec2f324..a18242b 100644 --- a/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.CollisionTransient.cs +++ b/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.CollisionTransient.cs @@ -65,13 +65,15 @@ namespace Simulation MaxTargets = math.max(1, maxTargets), ShapeType = CollisionShapeCircle, Direction = new float3(0f, 0f, 1f), - HalfAngleDeg = 180f + HalfAngleDeg = 180f, + HalfWidth = 0f, + HalfLength = 0f }); } private void AddAreaCollisionQuery(int queryId, int sourceEntityId, int sourceOwnerEntityId, 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) { @@ -101,7 +103,9 @@ namespace Simulation MaxTargets = math.max(1, maxTargets), ShapeType = shapeType, 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) }); } diff --git a/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs b/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs index a4211b5..5dd7ac6 100644 --- a/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs +++ b/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs @@ -10,6 +10,7 @@ namespace Simulation private const int CollisionSourceTypeArea = 2; private const int CollisionShapeCircle = 0; private const int CollisionShapeSector = 1; + private const int CollisionShapeRectangle = 2; // Kept as top-level fields because current regression tests reflect them directly. private NativeList _collisionQueryInputs; diff --git a/Assets/GameMain/Scripts/Simulation/JobStruct/AreaCollisionRequestData.cs b/Assets/GameMain/Scripts/Simulation/JobStruct/AreaCollisionRequestData.cs index 27df2b4..0e51176 100644 --- a/Assets/GameMain/Scripts/Simulation/JobStruct/AreaCollisionRequestData.cs +++ b/Assets/GameMain/Scripts/Simulation/JobStruct/AreaCollisionRequestData.cs @@ -15,6 +15,8 @@ namespace Simulation public int ShapeType; public Vector3 Direction; public float HalfAngleDeg; + public float HalfWidth; + public float HalfLength; } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Simulation/JobStruct/CollisionQueryData.cs b/Assets/GameMain/Scripts/Simulation/JobStruct/CollisionQueryData.cs index cdf7c23..75d6cd9 100644 --- a/Assets/GameMain/Scripts/Simulation/JobStruct/CollisionQueryData.cs +++ b/Assets/GameMain/Scripts/Simulation/JobStruct/CollisionQueryData.cs @@ -17,6 +17,8 @@ namespace Simulation public int ShapeType; public float3 Direction; public float HalfAngleDeg; + public float HalfWidth; + public float HalfLength; } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs b/Assets/GameMain/Scripts/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs index 3765232..b654f41 100644 --- a/Assets/GameMain/Scripts/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs +++ b/Assets/GameMain/Scripts/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs @@ -23,7 +23,6 @@ namespace Simulation public void Execute(int index) { CollisionQueryData query = Queries[index]; - float radiusSqr = query.Radius * query.Radius; int centerCellX = (int)math.floor(query.Position.x / CellSize); int centerCellZ = (int)math.floor(query.Position.z / CellSize); int queryRange = math.max(1, (int)math.ceil(query.Radius / CellSize)); @@ -34,10 +33,14 @@ namespace Simulation query.SourceOwnerEntityId != PlayerTargetEntityId) { float3 playerPosition = PlayerPosition; - playerPosition.y = query.Position.y; + if (query.SourceType == CollisionSourceTypeArea) + { + playerPosition.y = query.Position.y; + } float3 playerDelta = playerPosition - query.Position; float playerSqrDistance = math.lengthsq(playerDelta); - if (playerSqrDistance <= radiusSqr) + float playerRadiusSqr = query.Radius * query.Radius; + if (playerSqrDistance <= playerRadiusSqr) { Candidates.AddNoResize(new CollisionCandidateData { @@ -86,12 +89,19 @@ namespace Simulation continue; } + float deltaY = query.SourceType == CollisionSourceTypeArea + ? 0f + : enemy.Position.y - query.Position.y; float3 delta = new float3( enemy.Position.x - query.Position.x, - enemy.Position.y - query.Position.y, + deltaY, enemy.Position.z - query.Position.z); 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; } @@ -119,4 +129,4 @@ namespace Simulation } } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs index d7a3c90..9803c85 100644 --- a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs @@ -58,7 +58,8 @@ namespace Simulation AreaCollisionRequestData request = _areaCollisionRequests[i]; AddAreaCollisionQuery(queryId, request.SourceEntityId, request.SourceOwnerEntityId, 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++; builtAreaQueryCount++; if (request.Radius > maxQueryRadius) diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs index 84e8ebb..4b790c6 100644 --- a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs @@ -12,18 +12,29 @@ namespace Simulation float radius, int maxTargets = 16) { 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, float radius, in Vector3 direction, float halfAngleDeg, int maxTargets = 16) { 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, - 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) { @@ -58,7 +69,9 @@ namespace Simulation MaxTargets = Mathf.Max(1, maxTargets), ShapeType = shapeType, 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; diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs index 5caeb77..065d9e8 100644 --- a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs @@ -1,3 +1,4 @@ +using Components; using CustomDebugger; using CustomUtility; using Definition.DataStruct; @@ -188,7 +189,8 @@ namespace Simulation continue; } - if (!IsAreaTargetInsidePreciseShape(in query, target)) + float targetRadius = ResolveAreaTargetRadius(target); + if (!IsAreaTargetInsidePreciseShape(in query, target, targetRadius)) { continue; } @@ -230,7 +232,24 @@ namespace Simulation 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(); + 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) { @@ -241,7 +260,7 @@ namespace Simulation Vector3 toTarget = target.CachedTransform.position - center; 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 sqrDistance = toTarget.sqrMagnitude; if (sqrDistance > radiusSqr) @@ -249,6 +268,27 @@ namespace Simulation 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) { return true; diff --git a/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs b/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs index 35f582d..3a416ea 100644 --- a/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs +++ b/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs @@ -13,10 +13,13 @@ namespace CustomUtility { private static readonly Dictionary _paramsDict = new(StringComparer.OrdinalIgnoreCase) { - {"hitRadius", "伤害范围"}, + {"hitHalfWidth", "横向半宽"}, + {"hitRadius", "攻击半宽"}, + {"hitHeight", "判定高度"}, + {"hitCenterYOffset", "判定高度偏移"}, {"sectorAngle", "攻击角度"}, {"pierceLength", "前戳距离"}, - {"thrustDistance", "枪尖长度"}, + {"thrustDistance", "前戳距离(旧)"}, {"forwardOffset", "前置偏移"}, {"rotateSpeed", "转向速度"}, {"attackDuration", "突刺时长"}, diff --git a/数据表/Entity/Weapon.xlsx b/数据表/Entity/Weapon.xlsx index e3c5955..28a267a 100644 Binary files a/数据表/Entity/Weapon.xlsx and b/数据表/Entity/Weapon.xlsx differ