Physics Overview
Live Demo: Physics
Nightshade integrates Rapier3D for physics simulation, providing rigid body dynamics, collision detection, and character controllers.
Enabling Physics
Physics is enabled with the physics feature:
[dependencies]
nightshade = { git = "...", features = ["engine", "physics"] }
Physics World
The physics world is accessed through resources:
#![allow(unused)] fn main() { let physics = &mut world.resources.physics; // Configuration physics.gravity = Vec3::new(0.0, -9.81, 0.0); physics.fixed_timestep = 1.0 / 60.0; }
Core Concepts
Rigid Bodies
Objects that can move and be affected by forces:
- Dynamic: Affected by gravity and forces
- Kinematic: Moved by code, affects dynamic bodies
- Fixed: Immovable, infinite mass
Colliders
Shapes used for collision detection:
- Cuboid, Ball, Capsule, Cylinder
- Triangle mesh, Heightfield
- Compound shapes
Character Controllers
Kinematic bodies with special handling for player movement.
Quick Start
#![allow(unused)] fn main() { use nightshade::ecs::physics::*; fn initialize(&mut self, world: &mut World) { spawn_cube_at(world, Vec3::new(0.0, -0.5, 0.0)); for index in 0..10 { spawn_cube_at(world, Vec3::new(0.0, 2.0 + index as f32 * 1.5, 0.0)); } } }
Physics Synchronization
Physics runs at a fixed timestep. Transforms are automatically synchronized:
- Game logic updates entity transforms
- Physics simulation steps (may run multiple times per frame)
- Physics transforms sync back to entities
- Interpolation smooths visual positions
Querying Physics
Picking (Raycasting)
The picking system casts rays from screen coordinates to find entities:
#![allow(unused)] fn main() { let (width, height) = world.resources.window.cached_viewport_size.unwrap_or((800, 600)); let screen_center = Vec2::new(width as f32 / 2.0, height as f32 / 2.0); if let Some(hit) = pick_closest_entity(world, screen_center) { let hit_entity = hit.entity; let hit_distance = hit.distance; let hit_position = hit.world_position; } }
For precise trimesh-based raycasting (requires physics feature):
#![allow(unused)] fn main() { if let Some(hit) = pick_closest_entity_trimesh(world, screen_center) { let hit_entity = hit.entity; let hit_position = hit.world_position; } }
Physics Materials
Friction, restitution, and density are set directly on the ColliderComponent:
#![allow(unused)] fn main() { world.core.set_collider(entity, ColliderComponent::new_cuboid(0.5, 0.5, 0.5) .with_friction(0.5) .with_restitution(0.3) .with_density(1.0)); }
Debug Visualization
Enable physics debug drawing:
#![allow(unused)] fn main() { world.resources.physics.debug_draw = true; // In run_systems physics_debug_draw_system(world); }
This renders:
- Collider shapes (wireframe)
- Contact points
- Collision normals
Performance Tips
- Use simple collider shapes (boxes, spheres) when possible
- Disable collision between groups that don't need it
- Use compound colliders instead of many small colliders
- Set bodies to sleep when inactive
- Use appropriate fixed timestep (60 Hz is standard)
Joints
Connect bodies with joints for:
- Doors (revolute)
- Drawers (prismatic)
- Ropes (rope/spring)
- Chains (spherical)
See Physics Joints for details.