unity-physics-queries について
Unity の物理クエリを扱う開発者向けに、正しいクエリ選択と API の注意点を解説するスキルです。ヒット順序、NonAlloc バッファー、LayerMask、トリガー、シーン固有のクエリを取り上げます。
主な機能
- 01用途に応じたクエリ選択
- 02コライダー内から始まるキャストへの対処
- 03複数ヒット結果のソート
- 04NonAlloc バッファーの管理
- 05LayerMask の正しい構築
- 06トリガーとシーン別クエリの制御
活用シーン
- 01キャラクターの接地判定漏れの修正
- 02複数オブジェクトを通るレイ結果の処理
- 03NonAlloc クエリでのヒット欠落防止
- 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 -- 物理クエリのプロファイリング、最適化パターン