本文へ移動
cccskills
無料GitHub で公開

unity-physics-queries

Unity physics query correctness patterns. Catches common mistakes with Raycast, SphereCast, OverlapSphere, NonAlloc allocation, LayerMask construction, trigger interaction, hit ordering, and query type selection. PATTERN format: WHEN/WRONG/RIGHT/GOTCHA. Based on Unity 6.3 LTS.

インストール方法を見る

含まれるファイル(2)

  • SKILL.md12.9 KB
  • references/query-decision-tree.md7.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Physics Query Patterns -- Correctness Patterns

Prerequisite skills: unity-physics (Rigidbody, colliders, raycasting API), unity-foundations (layers, GameObjects)

These patterns target the most common physics query bugs: using the wrong query type, misunderstanding allocation, and ignoring subtle defaults that cause silent failures.


PATTERN: Query Type Selection

WHEN: Choosing which physics query to use

WRONG (Claude default):

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

RIGHT -- use the decision tree:

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 only returns the closest hit. If you need to pierce through multiple objects, use RaycastAll or RaycastNonAlloc. If you need to detect everything in an area (like an explosion radius), OverlapSphere is correct -- NOT SphereCast.


PATTERN: Cast Origin Inside Collider

WHEN: A SphereCast/BoxCast/CapsuleCast starts overlapping an existing collider

WRONG (Claude default):

// 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: This applies to ALL cast queries (SphereCast, BoxCast, CapsuleCast, Raycast). If the origin is inside a collider, that collider is ignored. This is the #1 source of "my ground check doesn't work" bugs. Raycasts that start inside a MeshCollider also miss it. Use Overlap* or Check* for current-overlap detection.


PATTERN: Hit Ordering Not Guaranteed

WHEN: Using RaycastAll or RaycastNonAlloc and expecting sorted results

WRONG (Claude default):

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: Regular Physics.Raycast (single hit) always returns the closest. Only RaycastAll and RaycastNonAlloc return unsorted results. The same applies to SphereCastAll/SphereCastNonAlloc, etc. For NonAlloc, sort only up to the returned count, not the full buffer.


PATTERN: NonAlloc Buffer Size and Return Count

WHEN: Using RaycastNonAlloc, OverlapSphereNonAlloc, or similar zero-allocation queries

WRONG (Claude default):

// 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 fills the provided buffer and returns how many results were written. If there are more results than buffer capacity, extras are silently dropped with no error. Size your buffer to the maximum expected results for your use case. Common sizes: ground check = 4, explosion radius = 32, broad scan = 64.


PATTERN: LayerMask Bitshift vs GetMask

WHEN: Constructing a layer mask for physics queries

WRONG (Claude default):

// 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") = bitmask (e.g., 256). LayerMask.NameToLayer("Ground") = index (e.g., 8). gameObject.layer = index. Passing a layer index where a mask is expected (or vice versa) silently filters wrong layers with no error.


PATTERN: QueryTriggerInteraction Default

WHEN: Raycasts or other queries are unexpectedly hitting trigger colliders

WRONG (Claude default):

// 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: The default is QueryTriggerInteraction.UseGlobal, which reads from Physics.queriesHitTriggers. That global default is true -- meaning queries DO hit triggers by default. This catches many developers off guard. Set it explicitly when trigger hits would cause bugs (ground checks, line-of-sight, bullet traces).


PATTERN: SphereCast Radius vs Distance

WHEN: Using SphereCast and confusing the parameters

WRONG (Claude default):

// 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 is the distance the sphere's center traveled before contact, NOT the total distance from origin to the hit surface. The actual contact surface is at hit.point. A SphereCast with radius=0 behaves like a Raycast. If the sphere is very large and the cast distance is short, you may miss nearby objects due to the "origin inside collider" issue.


PATTERN: CapsuleCast Point Parameters

WHEN: Setting up CapsuleCast endpoints

WRONG (Claude default):

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

GOTCHA: The total capsule height = |point2 - point1| + 2 * radius. If point1 == point2, it degenerates into a SphereCast. The CapsuleCollider component defines this differently (center + height + radius), so translating from a CapsuleCollider requires: point1 = center + up * (height/2 - radius), point2 = center - up * (height/2 - radius).


PATTERN: Backface Detection

WHEN: Raycasting against MeshColliders from behind

WRONG (Claude default):

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

GOTCHA: By default, Physics.queriesHitBackfaces = false. This only affects non-convex MeshColliders. Box, Sphere, Capsule, and convex MeshColliders detect hits from any direction. If you need to raycast from inside a non-convex mesh (e.g., room interior), either enable backface queries or use a convex collider for the interior.


PATTERN: Scene-Specific Queries

WHEN: Using additive scenes with separate physics simulations

WRONG (Claude default):

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

GOTCHA: By default, all scenes share the same Physics.defaultPhysicsScene. Scene-specific physics only matters when you explicitly create scenes with LocalPhysicsMode.Physics3D. Most projects never need this -- but it's critical for multiplayer prediction, parallel simulations, or editor preview scenes.


Anti-Patterns Quick Reference

Anti-PatternProblemFix
Physics.Raycast in Update without layer maskHits everything including UI collidersAlways pass a LayerMask parameter
Allocating new RaycastHit[] every frameGC pressureUse NonAlloc with a cached buffer
OverlapSphere with radius 0Returns nothingRadius must be > 0; use CheckSphere for point checks
Comparing hit.distance across different query typesSphereCast distance != Raycast distanceSphereCast distance is center travel, not surface distance
Using maxDistance = Mathf.InfinityQueries entire scene, expensiveUse a reasonable max distance for your use case
Forgetting QueryTriggerInteraction.Ignore on ground checksTrigger volumes falsely report "grounded"Pass QueryTriggerInteraction.Ignore explicitly

Related Skills

  • unity-physics -- Rigidbody, colliders, collision/trigger events, physics settings API
  • unity-3d-math -- Raycasting projection, Plane math, coordinate spaces
  • unity-performance -- Profiling physics queries, optimization patterns

Additional Resources

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

adopt

無料

Brownfield onboarding — audits existing project artifacts for template format compliance (not just existence), classifies gaps by impact, and produces a numbered migration plan. Run this when joining an in-progress project or upgrading from an older template version. Distinct from /project-stage-detect (which checks what exists) — this checks whether what exists will actually work with the template's skills.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Creates an Architecture Decision Record (ADR) documenting a significant technical decision, its context, alternatives considered, and consequences. Every major technical choice should have an ADR.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Validates completeness and consistency of the project architecture against all GDDs. Builds a traceability matrix mapping every GDD technical requirement to ADRs, identifies coverage gaps, detects cross-ADR conflicts, verifies engine compatibility consistency across all decisions, and produces a PASS/CONCERNS/FAIL verdict. The architecture equivalent of /design-review.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

art-bible

無料

Guided, section-by-section Art Bible authoring. Creates the visual identity specification that gates all asset production. Run after /brainstorm is approved and before /map-systems or any GDD authoring begins.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Audits game assets for compliance with naming conventions, file size budgets, format standards, and pipeline requirements. Identifies orphaned assets, missing references, and standard violations.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Generate per-asset visual specifications and AI generation prompts from GDDs, level docs, or character profiles. Produces structured spec files and updates the master asset manifest. Run after art bible and GDD/level design are approved, before production begins.

日本語の概要は準備中です。原文の説明を表示しています。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

IdoCohen560 のスキルをすべて見る

このスキルの問題を報告する