mcpField
U
unity-physics-queries

unity-physics-queries

github.com/nice-wolf-studio開発4069 回インストール6 週間前に更新
リポジトリ

unity-physics-queries について

Unity の物理クエリを扱う開発者向けに、正しいクエリ選択と API の注意点を解説するスキルです。ヒット順序、NonAlloc バッファー、LayerMask、トリガー、シーン固有のクエリを取り上げます。

主な機能

  1. 01用途に応じたクエリ選択
  2. 02コライダー内から始まるキャストへの対処
  3. 03複数ヒット結果のソート
  4. 04NonAlloc バッファーの管理
  5. 05LayerMask の正しい構築
  6. 06トリガーとシーン別クエリの制御

活用シーン

  1. 01キャラクターの接地判定漏れの修正
  2. 02複数オブジェクトを通るレイ結果の処理
  3. 03NonAlloc クエリでのヒット欠落防止
  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)

これらのパターンでは、物理クエリに関するよくあるバグを取り上げます。誤ったクエリの種類を使う、割り当ての仕組みを誤解する、ひそかな失敗につながる微妙なデフォルト設定を見落とす、といったバグです。


PATTERN:クエリの種類の選択

WHEN:使用する物理クエリを選ぶとき

WRONG(Claude のデフォルト):

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

RIGHT -- 次の判断ツリーを使います:

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)

GOTCHA:Raycast が返すのは最も近いヒットだけです。複数のオブジェクトを貫通する必要がある場合は、RaycastAll または RaycastNonAlloc を使います。爆発範囲のように、エリア内のすべてを検出する必要がある場合は、SphereCast ではなく OverlapSphere を使います。


PATTERN:コライダーの内部からのキャスト開始

WHEN:SphereCast/BoxCast/CapsuleCast の開始位置が、既存のコライダーと重なっているとき

WRONG(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
}

RIGHT:

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

GOTCHA:これはすべてのキャストクエリ(SphereCast、BoxCast、CapsuleCast、Raycast)に当てはまります。開始位置がコライダー内にある場合、そのコライダーは無視されます。これは「接地チェックが機能しない」というバグの最大の原因です。MeshCollider の内部から開始するレイキャストも、そのコライダーを検出できません。現在の重なりを検出するには、Overlap* または Check* を使います。


PATTERN:ヒットの順序は保証されない

WHEN:RaycastAll または RaycastNonAlloc を使い、結果がソート済みだと想定しているとき

WRONG(Claude のデフォルト):

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

RIGHT:

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

GOTCHA:通常の Physics.Raycast(単一ヒット)は、常に最も近いヒットを返します。ソートされていない結果を返すのは RaycastAll と RaycastNonAlloc だけです。同様のことは SphereCastAll/SphereCastNonAlloc などにも当てはまります。NonAlloc の場合は、バッファー全体ではなく、返された件数分だけをソートしてください。


PATTERN:NonAlloc のバッファーサイズと戻り値の件数

WHEN:RaycastNonAlloc、OverlapSphereNonAlloc、または同様のゼロアロケーションのクエリを使うとき

WRONG(Claude のデフォルト):

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

RIGHT:

// 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");
}

GOTCHA:NonAlloc は指定されたバッファーに結果を書き込み、書き込まれた件数を返します。結果がバッファーの容量を超えた場合、超過分はエラーなしで黙って破棄されます。用途に応じて、想定される最大結果数を格納できるバッファーサイズを指定してください。一般的なサイズは、接地チェックが 4、爆発範囲が 32、広範囲のスキャンが 64 です。


PATTERN:LayerMask のビットシフトと GetMask

WHEN:物理クエリ用のレイヤーマスクを作成するとき

WRONG(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

RIGHT:

// 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");

GOTCHA:LayerMask.GetMask("Ground") はビットマスク(例:256)を返します。LayerMask.NameToLayer("Ground") はインデックス(例:8)を返します。gameObject.layer もインデックスです。マスクが必要なところにレイヤーのインデックスを渡したり、その逆をしたりすると、エラーなしで誤ったレイヤーが除外されます。


PATTERN:QueryTriggerInteraction のデフォルト

WHEN:レイキャストなどのクエリが、意図せずトリガーコライダーにヒットするとき

WRONG(Claude のデフォルト):

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

RIGHT:

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

GOTCHA:デフォルトは QueryTriggerInteraction.UseGlobal で、Physics.queriesHitTriggers の値が使われます。このグローバル設定のデフォルトは true です。つまり、デフォルトでクエリはトリガーにヒットします。これは多くの開発者の意表を突きます。トリガーへのヒットがバグの原因になる場合は(接地チェック、視線判定、弾丸の軌跡など)、明示的に設定してください。


PATTERN:SphereCast の半径と距離

WHEN:SphereCast を使うときにパラメーターを混同しているとき

WRONG(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

RIGHT:

// 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)
}

GOTCHA:hit.distance は接触するまでに球の中心が移動した距離であり、原点からヒットした表面までの合計距離ではありません。実際に接触した表面の位置は hit.point です。radius=0 の SphereCast は Raycast と同じように動作します。球が非常に大きく、キャスト距離が短い場合、「コライダーの内部からの開始」の問題によって、近くのオブジェクトを見落とすことがあります。


パターン:CapsuleCast の点パラメーター

WHEN:CapsuleCast のエンドポイントを設定する場合

WRONG(Claude のデフォルト):

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

RIGHT:

// 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) とします。


パターン:裏面の検出

WHEN:背面から MeshCollider に対してレイキャストする場合

WRONG(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
}

RIGHT:

// 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 は、どの方向からでもヒットを検出します。非凸メッシュの内側(たとえば、部屋の内部)からレイキャストする必要がある場合は、裏面クエリを有効にするか、内部に凸コライダーを使用してください。


パターン:シーン固有のクエリ

WHEN:個別の物理シミュレーションを行うアディティブシーンを使用する場合

WRONG(Claude のデフォルト):

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

RIGHT:

// 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 パラメーターを渡します
毎フレーム new RaycastHit[] を割り当てるGC の負荷が増加しますキャッシュしたバッファーと NonAlloc を使用します
半径 0 で OverlapSphere を使用する何も返しません半径は 0 より大きくする必要があります。点の判定には CheckSphere を使用します
異なるクエリタイプ間で hit.distance を比較するSphereCast の距離 != Raycast の距離SphereCast の距離は表面ではなく中心の移動距離です
maxDistance = Mathf.Infinity を使用するシーン全体をクエリするため、コストが高くなります用途に応じた妥当な最大距離を使用します
地面判定で QueryTriggerInteraction.Ignore を指定し忘れるトリガーボリュームが誤って「接地」と判定されますQueryTriggerInteraction.Ignore を明示的に渡します

関連スキル

  • unity-physics -- Rigidbody、コライダー、衝突/トリガーイベント、物理設定 API
  • unity-3d-math -- レイキャストの投影、Plane の計算、座標空間
  • unity-performance -- 物理クエリのプロファイリング、最適化パターン

追加リソース

コメント