name: bevy-physics description: Use when the user asks about physics simulation in Bevy, collision detection, rigid bodies, colliders, raycasting for physics, joints, character controllers, or integrating avian or bevy_rapier physics.
Bevy Physics — Rigid Bodies, Colliders & Simulation
Related skills: See
bevy-ecsfor the Entity Component System fundamentals that underpin all physics components and systems. Seebevy-ecosystemfor other third-party crate recommendations beyond physics.
Crate Choice
There are two physics ecosystems for Bevy. Pick one per project — do not mix them.
| avian (avian2d / avian3d) | bevy_rapier (bevy_rapier2d / bevy_rapier3d) | |
|---|---|---|
| Engine | Pure Rust, built for Bevy from scratch | Rust wrapper around the rapier engine |
| Recommendation | Recommended for new projects | Mature, battle-tested, larger community backlog |
| API style | Bevy-native components and resources | Thin Bevy wrapper over rapier types |
| Determinism | Designed for cross-platform determinism | Deterministic within same platform |
See references/physics-comparison.md for a full side-by-side API comparison.
avian Setup
Cargo.toml
# For 3D physics:
[dependencies]
avian3d = "0.2"
# For 2D physics:
[dependencies]
avian2d = "0.2"
Plugin Registration
use avian3d::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugins(PhysicsPlugins::default()) // Optional: visual debug overlay .add_plugins(PhysicsDebugPlugin::default()) .run(); }
Core Components
#![allow(unused)] fn main() { use avian3d::prelude::*; fn spawn_dynamic_body(mut commands: Commands) { commands.spawn(( // Physics role — Dynamic bodies are affected by forces and gravity RigidBody::Dynamic, // Shape used for collision detection Collider::sphere(0.5), // Movement LinearVelocity(Vec3::new(2.0, 0.0, 0.0)), AngularVelocity(Vec3::new(0.0, 1.0, 0.0)), // Physical properties Mass(1.0), Restitution::new(0.7), // Bounciness: 0.0 = no bounce, 1.0 = perfect bounce Friction::new(0.5), GravityScale(1.0), // 0.0 = no gravity, 2.0 = double gravity // Spatial placement Transform::from_xyz(0.0, 5.0, 0.0), )); } fn spawn_static_floor(mut commands: Commands) { commands.spawn(( RigidBody::Static, // Never moves, infinite mass Collider::cuboid(50.0, 0.5, 50.0), Transform::default(), )); } fn spawn_kinematic_platform(mut commands: Commands) { commands.spawn(( RigidBody::Kinematic, // Moved by code, not by physics Collider::cuboid(5.0, 0.5, 5.0), Transform::from_xyz(0.0, 2.0, 0.0), )); } }
RigidBody types:
Dynamic— Fully simulated (gravity, forces, collisions move it)Static— Immovable (floors, walls). Never set velocity on these.Kinematic— Moved by code viaTransformorLinearVelocity, but not affected by forces or gravity. Use for moving platforms, elevators.
bevy_rapier Setup
Cargo.toml
# For 3D physics:
[dependencies]
bevy_rapier3d = "0.28"
# For 2D physics:
[dependencies]
bevy_rapier2d = "0.28"
Plugin Registration
use bevy_rapier3d::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugins(RapierPhysicsPlugin::<NoUserData>::default()) // Optional: wireframe debug rendering for colliders .add_plugins(RapierDebugRenderPlugin::default()) .run(); }
Core Components
#![allow(unused)] fn main() { use bevy_rapier3d::prelude::*; fn spawn_dynamic_body(mut commands: Commands) { commands.spawn(( RigidBody::Dynamic, Collider::ball(0.5), Velocity { linvel: Vec3::new(2.0, 0.0, 0.0), angvel: Vec3::new(0.0, 1.0, 0.0), }, ExternalForce { force: Vec3::ZERO, torque: Vec3::ZERO, }, Damping { linear_damping: 0.5, angular_damping: 0.1, }, Restitution::coefficient(0.7), Friction::coefficient(0.5), ColliderMassProperties::Mass(1.0), Transform::from_xyz(0.0, 5.0, 0.0), )); } }
Collider Shapes
Both crates support the same fundamental shapes with slightly different syntax.
Important: Cuboid dimensions are half-extents, not full dimensions. Collider::cuboid(1.0, 1.0, 1.0) creates a 2x2x2 box.
Capsule parameter differences: avian uses capsule(radius, length) while rapier uses capsule_y(half_height, radius) — note both the naming and parameter order differ.
#![allow(unused)] fn main() { // ---- avian3d ---- Collider::sphere(0.5) // radius Collider::cuboid(1.0, 2.0, 1.0) // half-extents x, y, z Collider::capsule(0.5, 1.0) // radius, length Collider::cylinder(0.5, 2.0) // radius, height Collider::cone(0.5, 1.0) // radius, height Collider::triangle(a, b, c) // three Vec3 vertices Collider::trimesh_from_mesh(&mesh) // arbitrary triangle mesh Collider::convex_hull(points) // convex hull from point cloud Collider::compound(vec![ // multiple shapes combined (Vec3::ZERO, Quat::IDENTITY, Collider::sphere(0.5)), (Vec3::new(0.0, 1.0, 0.0), Quat::IDENTITY, Collider::cuboid(0.3, 0.3, 0.3)), ]) // ---- bevy_rapier3d ---- Collider::ball(0.5) // radius Collider::cuboid(1.0, 2.0, 1.0) // half-extents x, y, z Collider::capsule_y(1.0, 0.5) // half-height, radius Collider::cylinder(1.0, 0.5) // half-height, radius Collider::cone(1.0, 0.5) // half-height, radius Collider::triangle(a, b, c) // three Vec3 vertices Collider::trimesh(vertices, indices) // vertices + triangle indices Collider::convex_hull(&points) // convex hull from point cloud Collider::compound(vec![ // multiple shapes combined (Vec3::ZERO, Quat::IDENTITY, Collider::ball(0.5)), (Vec3::new(0.0, 1.0, 0.0), Quat::IDENTITY, Collider::cuboid(0.3, 0.3, 0.3)), ]) }
2D equivalents: In avian2d, use Collider::circle(r), Collider::rectangle(w, h), Collider::capsule(r, l). In bevy_rapier2d, use Collider::ball(r), Collider::cuboid(hx, hy), Collider::capsule_y(hl, r).
Collision Detection
Collision Events
#![allow(unused)] fn main() { // ---- avian ---- use avian3d::prelude::*; fn handle_collisions( mut collision_started: EventReader<CollisionStarted>, mut collision_ended: EventReader<CollisionEnded>, ) { for CollisionStarted(entity_a, entity_b) in collision_started.read() { println!("{entity_a:?} started colliding with {entity_b:?}"); } for CollisionEnded(entity_a, entity_b) in collision_ended.read() { println!("{entity_a:?} stopped colliding with {entity_b:?}"); } } // ---- bevy_rapier ---- use bevy_rapier3d::prelude::*; fn handle_collisions(mut collision_events: EventReader<CollisionEvent>) { for event in collision_events.read() { match event { CollisionEvent::Started(a, b, _flags) => { println!("{a:?} started colliding with {b:?}"); } CollisionEvent::Stopped(a, b, _flags) => { println!("{a:?} stopped colliding with {b:?}"); } } } } }
Collision Layers and Groups
Collision layers let you control which objects can collide with which:
#![allow(unused)] fn main() { // ---- avian ---- use avian3d::prelude::*; #[derive(PhysicsLayer, Default)] enum GameLayer { #[default] Default, Player, Enemy, Projectile, Terrain, } // Player collides with enemies and terrain, but not other players commands.spawn(( RigidBody::Dynamic, Collider::capsule(0.4, 1.0), CollisionLayers::new(GameLayer::Player, [GameLayer::Enemy, GameLayer::Terrain]), )); // ---- bevy_rapier ---- use bevy_rapier3d::prelude::*; const PLAYER_GROUP: Group = Group::GROUP_1; const ENEMY_GROUP: Group = Group::GROUP_2; const TERRAIN_GROUP: Group = Group::GROUP_3; commands.spawn(( RigidBody::Dynamic, Collider::capsule_y(0.5, 0.4), CollisionGroups::new(PLAYER_GROUP, ENEMY_GROUP | TERRAIN_GROUP), )); }
Sensors (Trigger Volumes)
Sensors detect overlap without generating physical contact responses. Use them for trigger zones, pickup areas, and damage regions.
#![allow(unused)] fn main() { // ---- avian ---- commands.spawn(( Collider::sphere(3.0), Sensor, // No physical response, just generates events CollisionLayers::new(GameLayer::Default, GameLayer::Player), )); // ---- bevy_rapier ---- commands.spawn(( Collider::ball(3.0), Sensor, ActiveEvents::COLLISION_EVENTS, // Required to receive events for sensors )); }
Raycasting
avian — RayCaster Component and SpatialQuery
#![allow(unused)] fn main() { use avian3d::prelude::*; // Approach 1: RayCaster component (persists, updates each frame) commands.spawn(( RayCaster::new(Vec3::ZERO, Direction3d::NEG_Y) .with_max_distance(100.0), Transform::from_xyz(0.0, 10.0, 0.0), )); fn read_raycast_hits(query: Query<(&RayCaster, &RayHits)>) { for (ray, hits) in &query { for hit in hits.iter() { println!("Hit entity {:?} at distance {}", hit.entity, hit.distance); } } } // Approach 2: SpatialQuery (one-shot, on-demand) fn cast_ray_on_demand(spatial_query: SpatialQuery) { if let Some(hit) = spatial_query.cast_ray( Vec3::new(0.0, 10.0, 0.0), // origin Direction3d::NEG_Y, // direction 100.0, // max distance true, // solid (hit interior of shapes) &SpatialQueryFilter::default(), ) { println!("Hit {:?} at distance {}", hit.entity, hit.distance); } } }
bevy_rapier — RapierContext
#![allow(unused)] fn main() { use bevy_rapier3d::prelude::*; fn cast_ray(rapier_context: Res<RapierContext>) { if let Some((entity, distance)) = rapier_context.cast_ray( Vec3::new(0.0, 10.0, 0.0), // origin Vec3::NEG_Y, // direction 100.0, // max distance true, // solid QueryFilter::default(), ) { println!("Hit {entity:?} at distance {distance}"); } } // With hit normal: fn cast_ray_with_normal(rapier_context: Res<RapierContext>) { if let Some((entity, intersection)) = rapier_context.cast_ray_and_get_normal( Vec3::new(0.0, 10.0, 0.0), Vec3::NEG_Y, 100.0, true, QueryFilter::default(), ) { println!("Hit {entity:?}, normal: {}", intersection.normal); } } }
Character Controllers
A character controller is a kinematic body that slides along surfaces, steps over small obstacles, and detects ground contact. Both crates provide built-in character controller components.
avian
#![allow(unused)] fn main() { use avian3d::prelude::*; fn spawn_character(mut commands: Commands) { commands.spawn(( RigidBody::Kinematic, Collider::capsule(0.4, 1.0), CharacterController, // Movement is applied via LinearVelocity on kinematic bodies LinearVelocity::default(), Transform::from_xyz(0.0, 1.0, 0.0), )); } fn move_character( mut query: Query<(&mut LinearVelocity, &CharacterController), With<CharacterController>>, input: Res<ButtonInput<KeyCode>>, ) { for (mut velocity, _controller) in &mut query { let mut direction = Vec3::ZERO; if input.pressed(KeyCode::KeyW) { direction.z -= 1.0; } if input.pressed(KeyCode::KeyS) { direction.z += 1.0; } if input.pressed(KeyCode::KeyA) { direction.x -= 1.0; } if input.pressed(KeyCode::KeyD) { direction.x += 1.0; } let speed = 5.0; velocity.0 = direction.normalize_or_zero() * speed; } } }
bevy_rapier
#![allow(unused)] fn main() { use bevy_rapier3d::prelude::*; fn spawn_character(mut commands: Commands) { commands.spawn(( RigidBody::KinematicPositionBased, Collider::capsule_y(0.5, 0.4), KinematicCharacterController { offset: CharacterLength::Absolute(0.01), max_slope_climb_angle: std::f32::consts::FRAC_PI_4, // 45 degrees min_slope_slide_angle: std::f32::consts::FRAC_PI_4, snap_to_ground: Some(CharacterLength::Absolute(0.2)), ..default() }, Transform::from_xyz(0.0, 1.0, 0.0), )); } fn move_character( mut query: Query<&mut KinematicCharacterController>, input: Res<ButtonInput<KeyCode>>, time: Res<Time>, ) { for mut controller in &mut query { let mut direction = Vec3::ZERO; if input.pressed(KeyCode::KeyW) { direction.z -= 1.0; } if input.pressed(KeyCode::KeyS) { direction.z += 1.0; } if input.pressed(KeyCode::KeyA) { direction.x -= 1.0; } if input.pressed(KeyCode::KeyD) { direction.x += 1.0; } let speed = 5.0; controller.translation = Some(direction.normalize_or_zero() * speed * time.delta_secs()); } } // Ground detection via KinematicCharacterControllerOutput fn check_grounded(query: Query<&KinematicCharacterControllerOutput>) { for output in &query { if output.grounded { println!("Character is on the ground"); } } } }
Joints
For avian and bevy_rapier joint setup examples, read references/joints.md.
2D vs 3D
Use the 2D crates (avian2d, bevy_rapier2d) for sprite/top-down/platformer games and 3D crates (avian3d, bevy_rapier3d) for spatial worlds. The APIs are nearly identical; types differ by dimension (Vec2 vs Vec3, Collider::circle vs Collider::sphere).