mcpField
U
unity-physics-queries

unity-physics-queries

github.com/nice-wolf-studio开发4069 次安装6 周前更新
代码仓库

关于 unity-physics-queries

面向 Unity 开发者的物理查询正确性模式技能,帮助选择合适的查询类型并理解常见 API 陷阱。内容涉及命中排序、非分配缓冲区、LayerMask、触发器及场景专属查询。

主要功能

  1. 01按需求选择查询类型
  2. 02处理起点与碰撞体重叠
  3. 03排序多命中结果
  4. 04管理 NonAlloc 缓冲区
  5. 05正确构造 LayerMask
  6. 06控制触发器与场景查询

适用场景

  1. 01修正角色接地检测漏判
  2. 02处理穿透多个物体的射线结果
  3. 03避免 NonAlloc 查询丢失命中
  4. 04隔离附加场景中的物理检测

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-physics-queries

安装后在 Claude Code 中输入/unity-physics-queries确认已加载。

附带文件references/query-decision-tree.md

详细介绍

物理查询模式——正确性模式

前置技能: unity-physics(Rigidbody、碰撞体、射线检测 API)、unity-foundations(层、GameObject)

这些模式针对最常见的物理查询错误:使用了错误的查询类型、误解内存分配,以及忽略会导致静默失败的细微默认设置。


模式:选择查询类型

何时:选择要使用哪种物理查询

错误(Claude 默认):

// Always defaulting to Raycast for everything
Physics.Raycast(origin, direction, out hit, maxDistance);

正确——使用决策树:

Need to detect...
  |
  +-- "Is anything there?" (binary yes/no)
  |     --> CheckSphere, CheckBox, CheckCapsule (returns bool, cheapest)
  |
  +-- "What's the nearest thing along a line?"
  |     --> Raycast (single closest hit along an infinitely thin line)
  |
  +-- "What's the nearest thing along a volume?"
  |     --> SphereCast, BoxCast, CapsuleCast (sweep a shape, single closest hit)
  |
  +-- "Everything along a line?"
  |     --> RaycastAll / RaycastNonAlloc (all hits, not just closest)
  |
  +-- "Everything inside an area?"
        --> OverlapSphere, OverlapBox, OverlapCapsule (all colliders in region)

注意:Raycast 只返回最近的命中结果。如果需要穿透多个对象,请使用 RaycastAll 或 RaycastNonAlloc。如果需要检测某个区域内的所有对象(例如爆炸半径),应使用 OverlapSphere,而不是 SphereCast。


模式:投射起点位于碰撞体内部

何时:SphereCast/BoxCast/CapsuleCast 的起点与现有碰撞体重叠

错误(Claude 默认):

// Expecting to detect the ground when the sphere starts inside it
if (Physics.SphereCast(feetPosition, radius, Vector3.down, out hit, 0.1f))
{
    grounded = true; // May MISS if sphere starts inside the ground collider
}

正确:

// Casts do NOT detect colliders that the shape starts inside of
// Use Overlap for "what am I currently touching?"
grounded = Physics.CheckSphere(feetPosition, radius, groundMask);

// Or use OverlapSphere to get the actual colliders:
Collider[] touching = Physics.OverlapSphere(feetPosition, radius, groundMask);

注意:这适用于所有投射查询(SphereCast、BoxCast、CapsuleCast、Raycast)。如果起点位于碰撞体内部,该碰撞体就会被忽略。这是“我的地面检测不起作用”问题的头号来源。从 MeshCollider 内部开始的射线检测也会漏掉该碰撞体。请使用 Overlap* 或 Check* 检测当前重叠情况。


模式:不保证命中结果的顺序

何时:使用 RaycastAll 或 RaycastNonAlloc 并期望结果已排序

错误(Claude 默认):

RaycastHit[] hits = Physics.RaycastAll(origin, direction, maxDist);
// Assuming hits[0] is the closest
ProcessHit(hits[0]);

正确:

RaycastHit[] hits = Physics.RaycastAll(origin, direction, maxDist);
// Results are NOT sorted by distance -- sort manually
System.Array.Sort(hits, (a, b) => a.distance.CompareTo(b.distance));
if (hits.Length > 0)
    ProcessHit(hits[0]); // Now this is the closest

注意:普通的 Physics.Raycast(单次命中)始终返回最近的命中结果。只有 RaycastAll 和 RaycastNonAlloc 返回的结果未排序。SphereCastAll/SphereCastNonAlloc 等也同样如此。对于 NonAlloc,只对返回数量范围内的结果排序,不要对整个缓冲区排序。


模式:NonAlloc 缓冲区大小和返回数量

何时:使用 RaycastNonAlloc、OverlapSphereNonAlloc 或类似的零分配查询

错误(Claude 默认):

// Buffer of 1 -- silently drops extra results
RaycastHit[] buffer = new RaycastHit[1];
int count = Physics.RaycastNonAlloc(ray, buffer, maxDist);

正确:

// Pre-allocate a reasonably sized buffer as a class field
private readonly RaycastHit[] _hitBuffer = new RaycastHit[16];

void DetectHits()
{
    int count = Physics.RaycastNonAlloc(ray, _hitBuffer, maxDist, layerMask);

    // ONLY iterate up to count, not buffer.Length
    for (int i = 0; i < count; i++)
    {
        ProcessHit(_hitBuffer[i]);
    }

    // If count == buffer.Length, you may have missed results
    if (count == _hitBuffer.Length)
        Debug.LogWarning("Hit buffer full -- may have missed results");
}

注意:NonAlloc 会填充提供的缓冲区,并返回写入的结果数量。如果结果数量超过缓冲区容量,多余结果会被静默丢弃,不会报错。请根据使用场景,将缓冲区大小设为预期结果数量上限。常见大小:地面检测 = 4,爆炸半径 = 32,广域扫描 = 64。


模式:LayerMask 位移与 GetMask

何时:为物理查询构造层遮罩

错误(Claude 默认):

// DOUBLE-SHIFTING: GetMask already returns a bitmask, not a layer index
int mask = 1 << LayerMask.GetMask("Ground"); // WRONG -- shifts a bitmask by a bitmask amount

正确:

// GetMask returns the final bitmask -- use directly
int groundMask = LayerMask.GetMask("Ground");
int multiMask = LayerMask.GetMask("Ground", "Water", "Default");

// NameToLayer returns the layer INDEX -- this one needs the shift
int groundLayer = LayerMask.NameToLayer("Ground"); // Returns e.g. 8
int groundMask2 = 1 << groundLayer;                // Correct: 1 << 8 = 256

// Combining with bitwise OR
int combinedMask = (1 << LayerMask.NameToLayer("Ground")) | (1 << LayerMask.NameToLayer("Water"));

// Inverting a mask (everything EXCEPT these layers)
int everythingButGround = ~LayerMask.GetMask("Ground");

注意:LayerMask.GetMask("Ground") = 位掩码(例如 256)。LayerMask.NameToLayer("Ground") = 索引(例如 8)。gameObject.layer = 索引。需要传入掩码时却传入层索引(反之亦然),会静默筛选错误的层,且不会报错。


模式:QueryTriggerInteraction 默认值

何时:射线检测或其他查询意外命中触发器碰撞体

错误(Claude 默认):

// Assuming triggers are ignored by queries
if (Physics.Raycast(origin, direction, out hit, maxDist, layerMask))
{
    // hit.collider might be a trigger!
}

正确:

// Explicitly control trigger interaction
if (Physics.Raycast(origin, direction, out hit, maxDist, layerMask, QueryTriggerInteraction.Ignore))
{
    // Guaranteed to only hit non-trigger colliders
}

// Or check at the hit level
if (Physics.Raycast(origin, direction, out hit, maxDist, layerMask))
{
    if (!hit.collider.isTrigger)
    {
        // Process only non-trigger hits
    }
}

注意:默认值为 QueryTriggerInteraction.UseGlobal,它会读取 Physics.queriesHitTriggers。该全局默认值为true,这意味着查询默认会命中触发器。这会让许多开发者措手不及。如果命中触发器会导致问题(地面检测、视线检测、子弹轨迹检测),请显式设置此值。


模式:SphereCast 半径与距离

何时:使用 SphereCast 时混淆参数含义

错误(Claude 默认):

// Confusing parameters: treating radius as detection range
Physics.SphereCast(origin, detectionRange, direction, out hit);
// This creates a sphere with radius=detectionRange that travels infinitely far

正确:

// radius = SIZE of the sphere being swept
// maxDistance = how FAR the sphere travels
float sphereRadius = 0.5f;
float castDistance = 10f;
if (Physics.SphereCast(origin, sphereRadius, direction, out hit, castDistance, layerMask))
{
    // hit.distance = distance the sphere CENTER traveled, not the surface
    // hit.point = point on the surface of the OTHER collider (not the sphere)
}

注意:hit.distance 是球体中心在接触前移动的距离,而不是从起点到命中表面的总距离。实际接触表面位于 hit.point。radius=0 的 SphereCast 行为与 Raycast 相同。如果球体很大而投射距离很短,可能会因“起点位于碰撞体内部”问题而漏掉附近对象。


模式:CapsuleCast 点参数

适用场景:设置 CapsuleCast 端点

错误(Claude 默认):

// Using center + full height
Physics.CapsuleCast(center, center + Vector3.up * height, radius, direction, out hit);

正确:

// point1 and point2 are the centers of the two HEMISPHERES (not the full endpoints)
// For a character with height 2.0 and radius 0.5:
float height = 2.0f;
float radius = 0.5f;
Vector3 point1 = center + Vector3.up * (height * 0.5f - radius); // Top hemisphere center
Vector3 point2 = center - Vector3.up * (height * 0.5f - radius); // Bottom hemisphere center
Physics.CapsuleCast(point1, point2, radius, direction, out hit, maxDistance, layerMask);

注意:胶囊体总高度 = |point2 - point1| + 2 * radius。如果 point1 == point2,则会退化为 SphereCast。CapsuleCollider 组件采用不同的定义方式(center + height + radius),因此从 CapsuleCollider 转换时需要使用:point1 = center + up * (height/2 - radius),point2 = center - up * (height/2 - radius)。


模式:背面检测

适用场景:从背面向 MeshCollider 进行射线检测

错误(Claude 默认):

// Assuming raycasts hit both sides of a mesh triangle
if (Physics.Raycast(insidePoint, direction, out hit))
{
    // May not hit if ray goes through backface of MeshCollider
}

正确:

// Enable backface hits globally (affects all queries)
Physics.queriesHitBackfaces = true;

// Or design around it:
// Convex MeshColliders are always hit from both sides
// Primitive colliders (Box, Sphere, Capsule) are always hit from both sides
// Only non-convex MeshColliders have single-sided detection by default

注意:默认情况下,Physics.queriesHitBackfaces = false。这只影响非凸 MeshCollider。Box、Sphere、Capsule 和凸 MeshCollider 都能检测来自任意方向的命中。如果需要从非凸网格内部进行射线检测(例如房间内部),可以启用背面查询,或对内部使用凸碰撞体。


模式:特定场景查询

适用场景:使用具有独立物理模拟的叠加场景

错误(Claude 默认):

// Global queries search ALL physics scenes
Collider[] results = Physics.OverlapSphere(center, radius);

正确:

// Get the physics scene for a specific Unity scene
PhysicsScene physScene = gameObject.scene.GetPhysicsScene();

// Query only within that physics scene
RaycastHit hit;
if (physScene.Raycast(origin, direction, out hit, maxDist, layerMask))
{
    // Only hits colliders in this physics scene
}

// OverlapSphere with scene scope
Collider[] buffer = new Collider[32];
int count = physScene.OverlapSphere(center, radius, buffer, layerMask);

注意:默认情况下,所有场景共享同一个 Physics.defaultPhysicsScene。只有在明确使用 LocalPhysicsMode.Physics3D 创建场景时,特定场景的物理设置才会起作用。大多数项目都不需要这样做——但对于多人游戏预测、并行模拟或编辑器预览场景,这一点至关重要。


反模式速查表

反模式问题修正方法
在 Update 中使用 Physics.Raycast,但不设置图层掩码会命中所有内容,包括 UI 碰撞体始终传入 LayerMask 参数
每帧都分配新的 RaycastHit[]增加 GC 压力使用 NonAlloc 和缓存的缓冲区
使用半径为 0 的 OverlapSphere不会返回任何结果半径必须大于 0;点检测请使用 CheckSphere
比较不同查询类型的 hit.distanceSphereCast 距离不等于 Raycast 距离SphereCast 距离表示中心移动的距离,而非表面距离
使用 maxDistance = Mathf.Infinity查询整个场景,开销很大根据使用场景设置合理的最大距离
在地面检测中忘记设置 QueryTriggerInteraction.Ignore触发器区域会错误地报告“已着地”显式传入 QueryTriggerInteraction.Ignore

相关技能

  • unity-physics —— Rigidbody、碰撞体、碰撞/触发器事件、物理设置 API
  • unity-3d-math —— 射线投影、Plane 数学、坐标空间
  • unity-performance —— 物理查询分析、优化模式

其他资源

评论

支持 Markdown