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

tools-unity-physics

Unity physics patterns including character controllers, collision handling, and tunneling prevention.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md13.1 KB

SKILL.md(原文)

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

Unity Physics

Overview

Unity physics handles collision detection, rigidbody simulation, and character movement. This skill covers patterns to prevent common issues like floor penetration.

When to Use

  • Character movement and controllers
  • Projectile physics
  • Collision detection
  • Trigger zones
  • Ragdoll systems

Character Controller Safety

Preventing Floor Penetration

public class SafeCharacterController : MonoBehaviour
{
    [SerializeField] private float _moveSpeed = 5f;
    [SerializeField] private float _gravity = -20f;
    [SerializeField] private float _groundCheckDistance = 0.1f;
    [SerializeField] private LayerMask _groundLayer;
    
    private CharacterController _controller;
    private Vector3 _velocity;
    private bool _isGrounded;
    
    private void Awake()
    {
        _controller = GetComponent<CharacterController>();
    }
    
    private void Update()
    {
        // Ground check with spherecast for reliability
        _isGrounded = Physics.SphereCast(
            transform.position + Vector3.up * (_controller.radius + 0.1f),
            _controller.radius * 0.9f,
            Vector3.down,
            out _,
            _groundCheckDistance + 0.1f,
            _groundLayer
        );
        
        if (_isGrounded && _velocity.y < 0)
        {
            // Small negative to keep grounded
            _velocity.y = -2f;
        }
        
        // Movement
        Vector3 move = GetMoveInput() * _moveSpeed;
        _controller.Move(move * Time.deltaTime);
        
        // Gravity
        _velocity.y += _gravity * Time.deltaTime;
        _controller.Move(_velocity * Time.deltaTime);
        
        // CRITICAL: Clamp to ground if penetrating
        ClampToGround();
    }
    
    private void ClampToGround()
    {
        if (Physics.Raycast(transform.position + Vector3.up, Vector3.down, out var hit, 2f, _groundLayer))
        {
            if (transform.position.y < hit.point.y)
            {
                transform.position = new Vector3(
                    transform.position.x,
                    hit.point.y,
                    transform.position.z
                );
            }
        }
    }
    
    private Vector3 GetMoveInput()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        return new Vector3(h, 0, v).normalized;
    }
}

Physics-Based Character Movement

public class PhysicsCharacterController : MonoBehaviour
{
    [SerializeField] private float _moveSpeed = 5f;
    [SerializeField] private float _jumpForce = 8f;
    [SerializeField] private float _groundDrag = 6f;
    [SerializeField] private float _airDrag = 1f;
    [SerializeField] private LayerMask _groundLayer;
    
    private Rigidbody _rb;
    private CapsuleCollider _collider;
    private bool _isGrounded;
    
    private void Awake()
    {
        _rb = GetComponent<Rigidbody>();
        _collider = GetComponent<CapsuleCollider>();
        
        // Prevent tunneling
        _rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
        _rb.interpolation = RigidbodyInterpolation.Interpolate;
    }
    
    private void FixedUpdate()
    {
        CheckGrounded();
        ApplyDrag();
        Move();
    }
    
    private void CheckGrounded()
    {
        _isGrounded = Physics.SphereCast(
            transform.position + Vector3.up * (_collider.radius + 0.1f),
            _collider.radius * 0.95f,
            Vector3.down,
            out _,
            0.2f,
            _groundLayer
        );
    }
    
    private void ApplyDrag()
    {
        _rb.linearDamping = _isGrounded ? _groundDrag : _airDrag;
    }
    
    private void Move()
    {
        Vector3 moveDir = GetMoveInput();
        
        if (_isGrounded)
        {
            _rb.AddForce(moveDir * _moveSpeed * 10f, ForceMode.Force);
        }
        else
        {
            _rb.AddForce(moveDir * _moveSpeed * 5f, ForceMode.Force);
        }
        
        // Clamp horizontal velocity
        Vector3 flatVel = new Vector3(_rb.linearVelocity.x, 0, _rb.linearVelocity.z);
        if (flatVel.magnitude > _moveSpeed)
        {
            Vector3 limitedVel = flatVel.normalized * _moveSpeed;
            _rb.linearVelocity = new Vector3(limitedVel.x, _rb.linearVelocity.y, limitedVel.z);
        }
    }
}

Collision Detection

Safe Collision Handling

public class CollisionHandler : MonoBehaviour
{
    public event Action<Collision> OnCollisionEnterSafe;
    public event Action<Collider> OnTriggerEnterSafe;
    
    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject == null) return;
        if (collision.contactCount == 0) return;
        
        OnCollisionEnterSafe?.Invoke(collision);
    }
    
    private void OnTriggerEnter(Collider other)
    {
        if (other == null) return;
        if (!other.gameObject.activeInHierarchy) return;
        
        OnTriggerEnterSafe?.Invoke(other);
    }
}

Layer-Based Collision

public class CollisionLayerManager
{
    // Define layers
    public const int PlayerLayer = 8;
    public const int EnemyLayer = 9;
    public const int ProjectileLayer = 10;
    public const int EnvironmentLayer = 11;
    
    public static void Initialize()
    {
        // Player ignores other players
        Physics.IgnoreLayerCollision(PlayerLayer, PlayerLayer, true);
        
        // Projectiles pass through each other
        Physics.IgnoreLayerCollision(ProjectileLayer, ProjectileLayer, true);
        
        // Enemy projectiles don't hit enemies
        // (Configure in project settings)
    }
    
    public static LayerMask GetPlayerHitLayers()
    {
        return (1 << EnemyLayer) | (1 << EnvironmentLayer);
    }
    
    public static LayerMask GetEnemyHitLayers()
    {
        return (1 << PlayerLayer) | (1 << EnvironmentLayer);
    }
}

Preventing Tunneling

Continuous Collision Detection

public class ProjectilePhysics : MonoBehaviour
{
    [SerializeField] private float _speed = 50f;
    [SerializeField] private LayerMask _hitLayers;
    
    private Rigidbody _rb;
    private Vector3 _lastPosition;
    
    private void Awake()
    {
        _rb = GetComponent<Rigidbody>();
        
        // Use continuous for fast objects
        _rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
        _rb.interpolation = RigidbodyInterpolation.Interpolate;
    }
    
    private void Start()
    {
        _rb.linearVelocity = transform.forward * _speed;
        _lastPosition = transform.position;
    }
    
    private void FixedUpdate()
    {
        // Manual raycast for very fast projectiles
        var direction = transform.position - _lastPosition;
        var distance = direction.magnitude;
        
        if (distance > 0.01f)
        {
            if (Physics.Raycast(_lastPosition, direction.normalized, out var hit, distance, _hitLayers))
            {
                OnHit(hit);
            }
        }
        
        _lastPosition = transform.position;
    }
    
    private void OnHit(RaycastHit hit)
    {
        // Handle impact
        Destroy(gameObject);
    }
}

SubStepping for Fast Movement

public class HighSpeedMovement : MonoBehaviour
{
    [SerializeField] private float _speed = 100f;
    [SerializeField] private LayerMask _collisionLayers;
    
    private const float MaxStepDistance = 0.5f;
    
    public void MoveTowards(Vector3 target)
    {
        Vector3 direction = (target - transform.position).normalized;
        float totalDistance = Vector3.Distance(transform.position, target);
        float distanceMoved = 0f;
        
        while (distanceMoved < totalDistance)
        {
            float stepDistance = Mathf.Min(MaxStepDistance, totalDistance - distanceMoved);
            Vector3 nextPosition = transform.position + direction * stepDistance;
            
            // Check for collision in this step
            if (Physics.Raycast(transform.position, direction, out var hit, stepDistance, _collisionLayers))
            {
                transform.position = hit.point - direction * 0.01f;
                OnCollision(hit);
                return;
            }
            
            transform.position = nextPosition;
            distanceMoved += stepDistance;
        }
    }
    
    private void OnCollision(RaycastHit hit)
    {
        // Handle collision
    }
}

Physics Queries

SphereCast for Ground Detection

public class GroundChecker
{
    private readonly float _radius;
    private readonly float _distance;
    private readonly LayerMask _groundMask;
    
    public GroundChecker(float radius, float distance, LayerMask groundMask)
    {
        _radius = radius;
        _distance = distance;
        _groundMask = groundMask;
    }
    
    public bool IsGrounded(Vector3 position, out RaycastHit hitInfo)
    {
        Vector3 origin = position + Vector3.up * (_radius + 0.05f);
        
        return Physics.SphereCast(
            origin,
            _radius * 0.9f,
            Vector3.down,
            out hitInfo,
            _distance + 0.1f,
            _groundMask,
            QueryTriggerInteraction.Ignore
        );
    }
    
    public float GetGroundAngle(RaycastHit hit)
    {
        return Vector3.Angle(hit.normal, Vector3.up);
    }
}

OverlapSphere with Filtering

public class AreaDetector
{
    private readonly Collider[] _results = new Collider[32];
    
    public int DetectInRadius<T>(
        Vector3 center, 
        float radius, 
        LayerMask layers, 
        List<T> output) where T : Component
    {
        output.Clear();
        
        int count = Physics.OverlapSphereNonAlloc(
            center, 
            radius, 
            _results, 
            layers, 
            QueryTriggerInteraction.Ignore
        );
        
        for (int i = 0; i < count; i++)
        {
            if (_results[i].TryGetComponent<T>(out var component))
            {
                output.Add(component);
            }
        }
        
        return output.Count;
    }
}

Physics Performance

Optimized Physics Settings

public class PhysicsOptimizer
{
    public static void ApplyMobileSettings()
    {
        // Reduce solver iterations for mobile
        Physics.defaultSolverIterations = 4;
        Physics.defaultSolverVelocityIterations = 1;
        
        // Increase sleep threshold
        Physics.sleepThreshold = 0.05f;
        
        // Reduce bounce threshold
        Physics.bounceThreshold = 2f;
        
        // Lower fixed timestep for mobile (45 Hz instead of 50)
        Time.fixedDeltaTime = 1f / 45f;
    }
    
    public static void ApplyDesktopSettings()
    {
        Physics.defaultSolverIterations = 6;
        Physics.defaultSolverVelocityIterations = 1;
        Physics.sleepThreshold = 0.01f;
        Time.fixedDeltaTime = 1f / 60f;
    }
}

Physics Job System

using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;

[BurstCompile]
public struct RaycastJob : IJobParallelFor
{
    [ReadOnly] public NativeArray<RaycastCommand> Commands;
    public NativeArray<RaycastHit> Results;
    
    public void Execute(int index)
    {
        // Results are written by Unity's job system
    }
}

public class BatchRaycaster
{
    public void PerformBatchRaycasts(
        Vector3[] origins, 
        Vector3[] directions, 
        float[] distances,
        LayerMask layerMask,
        RaycastHit[] results)
    {
        int count = origins.Length;
        
        var commands = new NativeArray<RaycastCommand>(count, Allocator.TempJob);
        var hits = new NativeArray<RaycastHit>(count, Allocator.TempJob);
        
        for (int i = 0; i < count; i++)
        {
            commands[i] = new RaycastCommand(
                origins[i],
                directions[i],
                QueryParameters.Default,
                distances[i]
            );
        }
        
        var handle = RaycastCommand.ScheduleBatch(commands, hits, 1);
        handle.Complete();
        
        hits.CopyTo(results);
        
        commands.Dispose();
        hits.Dispose();
    }
}

Best Practices

  1. Use ContinuousDynamic for fast-moving objects
  2. SphereCast for ground - More reliable than raycast
  3. Clamp positions after physics update
  4. Use NonAlloc methods for queries (OverlapSphereNonAlloc)
  5. Configure layer matrix - Avoid unnecessary collisions
  6. Sleep inactive rigidbodies - Set appropriate thresholds
  7. Profile FixedUpdate - Physics runs at fixed rate
  8. Use compound colliders sparingly
  9. Batch physics queries with job system
  10. Test at low framerates - Tunneling is worse

Troubleshooting

IssueSolution
Objects fall through floorUse ContinuousDynamic, clamp position
Jittery movementEnable Interpolation
Collision missedIncrease solver iterations
Physics too slowReduce collider complexity
Character stuckCheck collider radius vs step offset
Floating point driftReset position periodically

レビュー

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

同じリポジトリのスキル

概要と使いどころ

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 のスキルをすべて見る

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