tools-unity-physics
Unity physics patterns including character controllers, collision handling, and tunneling prevention.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Unity physics patterns including character controllers, collision handling, and tunneling prevention.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
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.
Mobile-specific Unity optimization patterns for memory, battery, thermal, and performance.
End-of-story completion review. Reads the story file, verifies each acceptance criterion against the implementation, checks for GDD/ADR deviations, prompts code review, updates story status to Complete, and surfaces the next ready story from the sprint.
Unity Addressables patterns for asset loading, memory management, reference counting, and remote content delivery.
Unity animation patterns including Animancer, state machines, and performance optimization.
Behavior Designer patterns for AI behavior trees including task creation, shared variables, conditionals, and debugging.
| name | tools-unity-physics |
| description | Unity physics patterns including character controllers, collision handling, and tunneling prevention. |
Unity physics handles collision detection, rigidbody simulation, and character movement. This skill covers patterns to prevent common issues like 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;
}
}
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);
}
}
}
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);
}
}
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);
}
}
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);
}
}
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
}
}
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);
}
}
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;
}
}
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;
}
}
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();
}
}
| Issue | Solution |
|---|---|
| Objects fall through floor | Use ContinuousDynamic, clamp position |
| Jittery movement | Enable Interpolation |
| Collision missed | Increase solver iterations |
| Physics too slow | Reduce collider complexity |
| Character stuck | Check collider radius vs step offset |
| Floating point drift | Reset position periodically |