Unity 3D自定义射线检测形状:舰船火炮扇区瞄准问题求解
舰船火炮扇区目标分配问题
我制作了一艘可射击目标的舰船,要求右侧火炮不得瞄准舰船左侧的目标。使用Vector3.SignedAngle函数划分扇区后,测试基本可用但存在明显缺陷:
- 当前扇区为单点锥形(图1),无法检测舰船侧前方的近距离目标;
- 增大角度阈值后(图2),虽能覆盖更多目标,但会出现红色标记的错误扇区;
- 理想扇区应为底部较宽的锥形(图3),解决单点判断带来的问题。
尝试过BoxCast方法,但不适用当前场景。
现有目标分配脚本
foreach (Transform target in EnemyListManager.instance.enemyShips.ToArray()) { if (Vector3.Distance(transform.position, target.position) > ship.mainGunCaliber.range) continue; Vector3 toTarget = target.position - transform.position; print(Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up)); if (Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) >= bowMinAngle && Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) <= bowMaxAngle) { if (!bowTargets.Contains(target)) { RemoveFromOthers(target); bowTargets.Add(target); print("added target to Bow"); } continue; } if (Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) >= sbMinAngle && Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) <= sbMaxAngle) { if (!sbTargets.Contains(target)) { RemoveFromOthers(target); sbTargets.Add(target); print("added target to SB"); } continue; } if (Vector3.SignedAngle(-hullParent.forward, toTarget, Vector3.up) >= aftMinAngle && Vector3.SignedAngle(-hullParent.forward, toTarget, Vector3.up) <= aftMaxAngle) { if (!aftTargets.Contains(target)) { RemoveFromOthers(target); aftTargets.Add(target); print("added target to Aft"); } continue; } if (Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) >= psMinAngle && Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) <= psMaxAngle) { if (!psTargets.Contains(target)) { RemoveFromOthers(target); psTargets.Add(target); print("added target to PS"); } } }
解决方案
1. 基于舰船边界点的扇区判断
放弃单一中心角度判断,改用舰船侧舷的边界点来构建底部宽的锥形区域:
- 预先计算舰船左右侧舷的关键参考点(比如船首左侧边缘、船首右侧边缘、船尾对应点),可在
Start()方法中初始化; - 对目标同时计算从舰船中心、侧舷边界点到目标的角度,结合目标相对于船身的横向位置,判断是否属于对应火炮的允许区域。
示例代码片段:
// 初始化舰船边界点(需根据船身尺寸调整参数) Vector3 portBowPoint = hullParent.TransformPoint(new Vector3(-shipWidth / 2, 0, shipLength / 2)); Vector3 starboardBowPoint = hullParent.TransformPoint(new Vector3(shipWidth / 2, 0, shipLength / 2)); // 目标判断逻辑(以右舷火炮为例) Vector3 toTargetFromCenter = target.position - transform.position; float centerAngle = Vector3.SignedAngle(hullParent.forward, toTargetFromCenter, Vector3.up); // 获取目标在船身局部坐标系的X轴位置,判断是否在右舷允许范围内 float localTargetX = hullParent.InverseTransformPoint(target.position).x; // 右舷火炮允许条件:中心角度在右舷扇区范围内,且目标未完全处于船身左侧 bool isStarboardValid = centerAngle >= sbMinAngle && centerAngle <= sbMaxAngle && localTargetX >= -shipWidth / 4; if (isStarboardValid && !sbTargets.Contains(target)) { RemoveFromOthers(target); sbTargets.Add(target); print("added target to SB"); }
2. 多边形碰撞区域检测
将每个火炮的允许射击区域定义为多边形碰撞体,直接判断目标是否在区域内:
- 创建空物体作为扇区碰撞区域,添加
PolygonCollider2D(2D场景)或MeshCollider(3D场景),手动调整顶点形成底部宽的锥形; - 将碰撞区域设为触发器,避免参与物理碰撞;
- 对每个目标,使用
PolygonCollider2D.OverlapPoint(2D)或Physics.OverlapPoint(3D)判断是否在对应扇区内。
3. 动态角度阈值调整
根据目标距离动态调整角度范围,适配不同距离的目标:
- 近距离目标时,放宽角度阈值(船身宽度占比大,近距离左侧目标不会被右舷火炮命中);
- 远距离目标时,收窄角度阈值,保持原有的单点角度判断逻辑。
示例代码片段:
float distance = Vector3.Distance(transform.position, target.position); // 动态偏移量:近距离最大30度,远距离降为0度 float dynamicOffset = Mathf.Lerp(30f, 0f, distance / ship.mainGunCaliber.range); float signedAngle = Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up); // 右舷扇区范围:原阈值±动态偏移,同时限制目标横向位置 bool isStarboardValid = signedAngle >= sbMinAngle - dynamicOffset && signedAngle <= sbMaxAngle + dynamicOffset && hullParent.InverseTransformPoint(target.position).x >= -shipWidth / 4;
内容的提问来源于stack exchange,提问作者Mad Hatter
相关产品推荐
相关产品推荐

