Use when working with a game engine's physics — rigid bodies, colliders, collision layers and masks, character controllers, joints, forces versus impulses, raycasts and shapecasts, or when a simulation is unstable (tunnelling, jitter, missed overlaps). Covers the Godot, Unity and Unreal equivalents. NOT rendering or shader-driven visual jitter (that is `gamedev-shaders`), NOT navigation, pathfinding or navmesh (that is `gamedev-pathing`).
npx skills add https://github.com/ericrisco/rsc-harness --skill gamedev-physics
Make bodies collide, move, and stay stable across Godot, Unity, and Unreal. This skill owns the
mental model (body types, colliders, layers/masks, character controllers, joints, queries,
determinism) and maps every concept onto each engine's real API. When physics "feels broken" —
jitter, tunneling, sinking, ghost collisions — the cause is almost always one of: wrong body type,
a layer/mask mismatch, or moving/integrating outside the fixed physics tick. Check those first.
| Engine | Target | Banned / deprecated | Use instead |
| --- | --- | --- | --- |
| Godot | 4.x | move_and_slide(velocity, up, ...) with arguments (that is Godot 3) | Godot 4 move_and_slide() takes no args; set the velocity property first |
| Godot | 4.x | KinematicBody2D/3D, RayShape, WorldMarginShape, linear_velocity *= delta before move_and_slide() | CharacterBody2D/3D, SeparationRayShape2D/3D, WorldBoundaryShape2D/3D; move_and_slide() is already delta-scaled |
| Unity | Unity 6 (6000.x) | Rigidbody.velocity / Rigidbody2D.velocity, rb.drag, rb.angularDrag | linearVelocity (+ linearVelocityX/Y), linearDamping, angularDamping; new AddForceX/Y |
| Unity | Unity 6 | moving a dynamic Rigidbody from Update; transform.position = on a physics body | move in FixedUpdate; use MovePosition/AddForce, never write transform on a simulated body |
| Unreal | UE5 (Chaos) | any PhysX/APEX API, bUseAsyncScene, NvCloth | Chaos is the only physics backend in UE5; use UPrimitiveComponent physics + PhysicsConstraintComponent |
If you are unsure a symbol exists in the user's exact version, say so instead of inventing it.
Use when: designing which body type a thing should be; something falls through, sinks into, or
passes through geometry; a rigid body jitters, drifts, or launches; setting up collision
layers/masks/channels; building or fixing a character/player controller; adding joints; casting
rays/shapes or overlap tests; or physics behaves differently at different frame rates.
When NOT to use (delegate):
*executes* the move; pathing decides *where*).
you the *deterministic fixed-tick* foundation multiplayer builds on).
Three categories exist in every engine; choosing wrong is the root of most bugs.
static body every frame; the broadphase caches it. If it must move, it is not static.
them, ignores forces and gravity unless you add them. Players, moving platforms, doors, elevators.
ragdolls, debris, vehicles. You influence it with forces/impulses, never by writing its transform.
Move each the right way:
| | Static | Kinematic / character | Dynamic |
| --- | --- | --- | --- |
| Godot | StaticBody2D/3D (leave still) | CharacterBody2D/3D + move_and_slide(); AnimatableBody2D/3D for platforms (set sync_to_physics) | RigidBody2D/3D (apply_impulse, apply_force) |
| Unity | Collider, no Rigidbody (or Rigidbody2D bodyType Static) | CharacterController.Move(), or Rigidbody.isKinematic=true + MovePosition in FixedUpdate | Rigidbody (AddForce) |
| Unreal | Static/Movable mobility, Simulate Physics off | Character + CharacterMovementComponent; movable component moved by code | Simulate Physics on (SetSimulatePhysics(true)) |
A Godot RigidBody can be temporarily frozen: set freeze = true with `freeze_mode =
FREEZE_MODE_KINEMATIC` to move it by transform without waking the solver wrongly. Prefer
AnimatableBody for a permanent kinematic mover.
Collider ≠ visual mesh. Give every body a separate, simpler collision shape.
characters (rounded ends slide over steps and seams). Use these whenever possible.
convex pieces** (a compound), not one concave hull.
concave-mesh collider is a top cause of tunneling and solver blowups. Never put a trimesh on a
dynamic body.
CollisionShape children. Unity:multiple Collider components. Unreal: multiple primitives / a body setup.
Area2D/3D (signalsbody_entered / area_entered); Unity collider Is Trigger (OnTriggerEnter/Stay/Exit); Unreal
set response to Overlap + Generate Overlap Events (OnComponentBeginOverlap). Use for
pickups, damage zones, checkpoints, sensors.
Layer = "what I am." Mask = "what I scan for." They are separate bit sets.
collision_mask includes the other'scollision_layer — it is an OR**, so detection can be asymmetric (A sees B without B seeing
A). Keep them symmetric unless you deliberately want one-way detection. An Area's mask decides
what it detects; its layer decides what detects *it*.
decide which layer pairs collide. Raycasts/overlaps take a LayerMask argument. Toggle a pair at
runtime with Physics.IgnoreLayerCollision.
Ignore. Package as a reusable Collision Preset. Trace channels (Visibility, Camera) are for
queries; object channels are for physical collision.
Full worked examples (player/enemy/pickup/wall, bit math, one-way platforms) →
references/layers-and-masks.md.
Two philosophies — decide up front, don't mix:
sweeping helper that resolves collisions and slides. Precise, snappy, no solver fighting. Godot
CharacterBody, Unity CharacterController, Unreal CharacterMovementComponent.
or a physics material, angular constraints (freeze rotation), and tuning to stop tipping/sliding.
Godot 4 (2D — the 3D version is identical with Vector3 and get_gravity()):
extends CharacterBody2D
const SPEED := 300.0
const JUMP_VELOCITY := -400.0
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta # accumulate accel: scale by delta
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
var dir := Input.get_axis("move_left", "move_right")
velocity.x = dir * SPEED if dir else move_toward(velocity.x, 0.0, SPEED)
move_and_slide() # Godot 4: NO args, uses `velocity`, already delta-scaled
is_on_floor() / is_on_wall(), floor_max_angle (slope limit), floor_snap_length (stick to
ground on ramps/stairs), and up_direction handle ground/slope/step behavior. get_gravity() is
Godot 4.3+; on older 4.x read ProjectSettings gravity. Full per-engine controllers (Unity
CharacterController + custom gravity, Unreal movement modes, ground detection, slopes, steps,
moving platforms) → references/character-controllers.md.
Joints connect two bodies with a constraint. Godot: PinJoint, HingeJoint3D, SliderJoint3D,
Generic6DOFJoint3D, DampedSpringJoint2D. Unity: HingeJoint, FixedJoint, SpringJoint,
ConfigurableJoint, CharacterJoint. Unreal: PhysicsConstraintComponent (one 6-DOF constraint
covers hinge/slider/ball). Keep connected bodies' mass ratios close — a heavy body chained to a
light one is the classic joint-explosion.
Force vs impulse vs direct velocity (apply all in the fixed tick):
| Want | Use | Godot | Unity | Unreal |
| --- | --- | --- | --- | --- |
| Continuous push (thrust, wind), mass-scaled | Force | apply_central_force | AddForce(f, Force) | AddForce |
| Instant kick (jump, explosion, hit), mass-scaled | Impulse | apply_central_impulse | AddForce(f, Impulse) | AddImpulse |
| Instant velocity change, ignoring mass | mass-independent impulse | set linear_velocity | AddForce(f, VelocityChange) | SetPhysicsLinearVelocity |
Setting velocity directly on a *dynamic* body teleports its momentum and can fight the solver — fine
for character/kinematic bodies, use sparingly on dynamics (prefer forces/impulses).
Run queries from the fixed physics tick so results match the simulated state.
get_world_2d().direct_space_state (or 3d), buildPhysicsRayQueryParameters2D/3D / PhysicsShapeQueryParameters2D/3D, call intersect_ray,
intersect_shape, cast_motion. Node helpers: RayCast2D/3D, ShapeCast2D/3D. Queries respect
collision_mask.
Physics.Raycast, SphereCast/CapsuleCast, OverlapSphere, CheckSphere (2D:Physics2D.*). Always pass a LayerMask; prefer non-allocating RaycastNonAlloc/RaycastAll.
LineTraceSingleByChannel, SweepSingleByChannel, OverlapMultiByChannel (and...ByObjectType). Use a trace channel, set the query params (bTraceComplex).
Details, snippets, and the tunneling-safe shapecast pattern → references/determinism-and-queries.md.
The rules that keep physics from jittering, drifting, or tunneling:
_physics_process(delta), Unity FixedUpdate(),Unreal substepping / async physics tick. Read input in the frame update, *apply* it in the fixed
tick. Never simulate in the render frame.
delta /fixedDeltaTime — but not the output of a helper that already integrates time (Godot
move_and_slide(), Unity CharacterController.Move when you pass a per-second velocity).
Collision Detection: Godot continuous_cd, Unity collisionDetectionMode = Continuous*, Unreal
Use CCD. Enable only on the fast bodies (it costs). Or use a shapecast/raycast-then-move.
transform/position on a dynamic body —both corrupt the broadphase and cause explosions or ghost collisions. Move via the API.
Rigidbody.interpolation = Interpolate. This is visual only — it never changes the simulation.
Deeper: fixed-timestep math, substepping, sleeping, why transform writes break things →
references/determinism-and-queries.md.
AnimatableBody withsync_to_physics, Unity kinematic Rigidbody MovePosition), not a static body.
Area's mask must include the body's layer; in Unreal enable Generate Overlap Events on both.
writing its transform. Stop writing the transform.
floor_snap_length / stop-on-slope).overlap/Area, not solid response.
output not double-scaled).
transform/position writes on dynamics; noruntime collider scaling.
move_and_slide() no args; Unity 6linearVelocity; UE5 Chaos — no PhysX).
Take ericrisco/gamedev-physics from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.