mcpField
B
bevy-physics

bevy-physics

github.com/chrisgliddonDevelopment2663 installsUpdated 9w ago
Repository

About bevy-physics

bevy-physics is a skill for building physics features in Bevy 0.19 projects with the Rapier integration. It covers fixed stepping, collision queries, character control, and testing considerations.

Key features

  1. 01Configures Rapier 2D and 3D physics
  2. 02Sets fixed steps and system ordering
  3. 03Handles collision events and spatial queries
  4. 04Implements character controllers and joints
  5. 05Covers headless physics and testing

Use cases

  1. 01Adding rigid bodies, colliders, or sensors to a Bevy game
  2. 02Diagnosing transform jitter from physics stepping
  3. 03Implementing ray queries or character movement
  4. 04Setting up headless physics tests

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/chrisgliddon/bevy-skills --skill bevy-physics

After installing, type/bevy-physicsin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
bodies-and-colliders.md
controllers-and-joints.md
determinism-and-testing.md
events-queries-and-filtering.md
setup-and-scheduling.md

Details

From the project README

Bevy 0.19 — physics with Rapier

When to use this skill

  • Add 2D/3D rigid bodies, colliders, sensors, joints, or character movement.
  • Decide between bevy_rapier and an alternative such as Avian.
  • Place input/gameplay systems around a physics step without transform jitter.
  • Read collision messages or perform ray, shape, point, and overlap queries.
  • Configure headless physics, interpolation, CCD, collision groups, or tests.

Bevy core does not include a physics engine. For Bevy 0.19, use bevy_rapier2d/bevy_rapier3d 0.36 for the mature Rapier integration. Consider avian2d/avian3d 0.7 when its ECS-native architecture or modular solver is the specific reason to choose it. Do not install both engines on the same simulated entities.

Canonical fixed-step Rapier 3D pattern

[dependencies]
bevy = "0.19"
bevy_rapier3d = "0.36"
use bevy::prelude::*;
use bevy_rapier3d::prelude::*;

const PHYSICS_HZ: f64 = 60.0;
const PHYSICS_DT: f32 = 1.0 / PHYSICS_HZ as f32;

#[derive(Component)]
struct Player;

#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum GamePhysics {
    Drive,
    React,
}

fn main() {
    let mut app = App::new();
    app.add_plugins(DefaultPlugins)
        .insert_resource(Time::<Fixed>::from_hz(PHYSICS_HZ))
        .insert_resource(TimestepMode::Fixed {
            dt: PHYSICS_DT,
            substeps: 1,
        })
        .add_plugins((
            RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
            RapierDebugRenderPlugin::default(),
        ))
        .configure_sets(
            FixedUpdate,
            (
                GamePhysics::Drive.before(PhysicsSet::SyncBackend),
                GamePhysics::React.after(PhysicsSet::Writeback),
            ),
        )
        .add_systems(Startup, setup)
        .add_systems(FixedUpdate, drive_player.in_set(GamePhysics::Drive))
        .add_systems(FixedUpdate, react_to_collisions.in_set(GamePhysics::React));
    app.run();
}

fn setup(mut commands: Commands) {
    commands.spawn((
        Camera3d::default(),
        Transform::from_xyz(8.0, 6.0, 12.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));

    // A collider without RigidBody is fixed. Cuboid arguments are half-extents.
    commands.spawn((
        Name::new("Ground"),
        Collider::cuboid(8.0, 0.25, 8.0),
        Transform::from_xyz(0.0, -0.25, 0.0),
    ));

    commands.spawn((
        Name::new("Player"),
        Player,
        RigidBody::Dynamic,
        Collider::ball(0.5),
        Velocity::linear(Vec3::new(1.5, 0.0, 0.0)),
        ActiveEvents::COLLISION_EVENTS,
        Transform::from_xyz(0.0, 4.0, 0.0),
    ));
}

fn drive_player(mut bodies: Query<&mut Velocity, With<Player>>) {
    for mut velocity in &mut bodies {
        velocity.linear.x = 1.5;
    }
}

fn react_to_collisions(mut events: MessageReader<CollisionEvent>) {
    for event in events.read() {
        if let CollisionEvent::Started(a, b, _) = event {
            let _pair = (*a, *b);
        }
    }
}

The plugin normally runs in PostUpdate with TimestepMode::Variable. This example deliberately moves it to FixedUpdate, makes Rapier's dt match Bevy's fixed clock, drives bodies before PhysicsSet::SyncBackend, and reacts after PhysicsSet::Writeback. Keep the default schedule if frame-coupled simulation is the intent; order systems in that same schedule.

Choose the relevant deep dive

ProblemRead
Cargo profiles, default vs fixed/interpolated stepping, multiple worldsSetup and scheduling
Body types, collider shapes, mass, units, CCD, transform authorityBodies and colliders
Collision messages, sensors, groups, ray/shape queries, hooksEvents, queries, and filtering
Kinematic character motion, controller output, jointsControllers and joints
Rollback, determinism limits, headless tests, performance evidenceDeterminism and testing

Bevy 0.19 / Rapier 0.36 gotchas

  • Velocity fields are linear and angular, not linvel and angvel.
  • CollisionEvent and ContactForceEvent are messages. At least one collider must enable the corresponding ActiveEvents flag.
  • ReadRapierContext is a SystemParam; call .single() and handle its Result. RapierContext is not a global resource and Rapier supports multiple worlds.
  • Dynamic bodies own their simulated pose. Repeatedly writing their Transform is a teleport and commonly causes jitter; write forces, impulses, or velocity instead.
  • Kinematic bodies follow the trajectory you prescribe and do not avoid obstacles by themselves. Use scene queries or KinematicCharacterController.
  • Rapier's default Cargo features pull Bevy asset/render helpers. Disable defaults for headless or external-renderer builds and enable the correct dim2/dim3 feature.
  • RapierDebugRenderPlugin is diagnostic visualization, not a renderer or a shipping requirement.

See also

Comments

Markdown supported