# Godot Rapier Physics Godot Rapier Physics is a 2D and 3D physics engine plugin for the Godot game engine that serves as a drop-in replacement for the default physics implementation. Built on the Rapier physics engine and Salva fluids simulation library, it provides enhanced stability, performance, and features including fluid simulation, deterministic physics, state serialization, and elimination of ghost collisions. The plugin integrates seamlessly with Godot through the GDExtension system, replacing the standard PhysicsServer2D or PhysicsServer3D with high-performance Rust-based implementations. The architecture uses a singleton pattern with two distinct data layers: a Godot Data Layer that maps to Godot's API requirements, and a Rapier Data Layer that handles the underlying physics simulation. Physics objects (bodies, areas, shapes, joints, and fluids) are managed through Godot's RID (Resource ID) system, allowing efficient communication between the engine and the physics server. The plugin supports multiple platforms including desktop, mobile, and web, with optional features like SIMD acceleration, parallel solving, cross-platform determinism, and serialization capabilities. ## Installation and Configuration Enable Rapier Physics in project settings ```gdscript # In Godot Editor: # Go to Project -> Project Settings -> Advanced Settings # Navigate to Physics -> 2D (or 3D) # Change "Physics Engine" dropdown to "Rapier2D" or "Rapier3D" # Verify the physics engine is active func _ready(): var physics_server = PhysicsServer2D.get_singleton() print("Active physics engine: ", physics_server.get_class()) # Output: RapierPhysicsServer2D ``` Configure advanced physics settings ```gdscript # Set project settings before runtime (in project.godot or via Project Settings) # Solver preset (use this for quick configuration) physics/rapier/solver/preset = 0 # 0=Performance, 1=Stability, 2=Custom # Solver precision and iterations physics/rapier/solver/num_iterations = 4 physics/rapier/solver/num_internal_pgs_iterations = 1 # PGS sub-iterations physics/rapier/solver/num_internal_stabilization_iterations = 2 physics/rapier/solver/max_ccd_substeps = 1 # Length unit: 100 pixels = 1 meter in 2D, 1 unit = 1 meter in 3D physics/rapier/solver/length_unit_2d = 100.0 physics/rapier/solver/length_unit_3d = 1.0 # Contact parameters for softer/harder collisions physics/rapier/solver/contact_damping_ratio = 5.0 physics/rapier/solver/contact_natural_frequency = 30.0 # Advanced solver parameters physics/rapier/solver/normalized_allowed_linear_error = 0.001 # Position correction tolerance physics/rapier/solver/normalized_max_corrective_velocity = 1.0 # Max correction speed physics/rapier/solver/normalized_prediction_distance = 0.002 # Predictive contact distance physics/rapier/solver/predictive_contact_allowance_threshold = 0.01 # Contact culling threshold # Joint stiffness parameters physics/rapier/joint/damping_ratio = 1.0 physics/rapier/joint/natural_frequency = 2.0 # Fluid simulation parameters physics/rapier/fluid/fluid_particle_radius_2d = 1.0 physics/rapier/fluid/fluid_particle_radius_3d = 0.025 physics/rapier/fluid/fluid_smoothing_factor = 2.0 physics/rapier/fluid/fluid_boundary_coefficient = 0.3 # Ghost collision prevention physics/rapier/solver/ghost_collision_distance_2d = 0.1 physics/rapier/solver/ghost_collision_distance_3d = 0.001 # Parallel processing (if built with parallel feature) physics/rapier/solver/num_threads = 4 # Number of threads for parallel solver # Access settings at runtime func check_physics_config(): var preset = ProjectSettings.get_setting("physics/rapier/solver/preset") var iterations = ProjectSettings.get_setting("physics/rapier/solver/num_iterations") var length_unit = ProjectSettings.get_setting("physics/rapier/solver/length_unit_2d") var prediction_distance = ProjectSettings.get_setting("physics/rapier/solver/normalized_prediction_distance") print("Preset: ", preset) print("Solver iterations: ", iterations) print("Length unit: ", length_unit) print("Prediction distance: ", prediction_distance) ``` ## Solver Presets Choose between performance and stability presets ```gdscript # In Project Settings, configure solver preset: # physics/rapier/solver/preset = 0 # Performance (default) # physics/rapier/solver/preset = 1 # Stability (recommended for complex scenes) # physics/rapier/solver/preset = 2 # Custom (automatically set when manually tuning) # Performance preset (fast, suitable for most games) # - PGS iterations: 4 # - Stabilization iterations: 2 # - Contact damping: 5.0 # - Natural frequency: 30.0 # Stability preset (slower but better for stacking/complex scenarios) # - PGS iterations: 4 # - Stabilization iterations: 4 # - Contact damping: 20.0 # - Natural frequency: 50.0 func check_current_preset(): var preset = ProjectSettings.get_setting("physics/rapier/solver/preset") match preset: 0: print("Using Performance preset") 1: print("Using Stability preset") 2: print("Using Custom preset (manual configuration)") ``` ## Body Extra Parameters Set contact skin, dominance, soft CCD, and massless properties ```gdscript # Contact skin determines collision detection margin # Dominance controls which body is prioritized in collision resolution # Soft CCD enables predictive collision detection for fast-moving objects # Massless makes bodies have no mass without changing the mass value extends RigidBody2D func _ready(): var body_rid = get_rid() # Set contact skin (param 0): larger values = more stable but less precise RapierPhysicsServer2D.body_set_extra_param(body_rid, 0, 0.08) # Set dominance (param 1): higher values dominate in collisions # Useful for player characters that should push other objects RapierPhysicsServer2D.body_set_extra_param(body_rid, 1, 10) # Set soft CCD (param 2): predictive contact distance for fast objects # Helps prevent tunneling through thin objects RapierPhysicsServer2D.body_set_extra_param(body_rid, 2, 0.1) # Set massless (param 3): treat body as massless in physics # Useful for kinematic-like behavior with collision response RapierPhysicsServer2D.body_set_extra_param(body_rid, 3, true) # Get current values var contact_skin = RapierPhysicsServer2D.body_get_extra_param(body_rid, 0) var dominance = RapierPhysicsServer2D.body_get_extra_param(body_rid, 1) var soft_ccd = RapierPhysicsServer2D.body_get_extra_param(body_rid, 2) var massless = RapierPhysicsServer2D.body_get_extra_param(body_rid, 3) print("Contact skin: ", contact_skin, " Dominance: ", dominance) print("Soft CCD: ", soft_ccd, " Massless: ", massless) ``` ## State Serialization and Determinism Save and load complete physics state for replays ```gdscript # Attach Rapier2DState node to scene for automatic state management class_name Rapier2DState extends Node var state: Dictionary = {} # Save entire physics world state func save_physics_world(): var state_manager = Rapier2DState.new() add_child(state_manager) # Returns hash of state for verification var state_hash = state_manager.save_state(false) print("Saved state hash: ", state_hash) # Export to file for persistence state_manager.export_state("user://my_physics_state.json") return state_hash # Load physics state for replay func load_physics_world(): var state_manager = Rapier2DState.new() add_child(state_manager) state_manager.import_state("user://my_physics_state.json") var loaded_hash = state_manager.load_state() print("Loaded state hash: ", loaded_hash) return loaded_hash # Save individual physics object (body, shape, joint, or space) func save_individual_object(object_rid: RID) -> PackedByteArray: var binary_data = RapierPhysicsServer2D.export_binary(object_rid) # Or use JSON (slower but human-readable) var json_string = RapierPhysicsServer2D.export_json(object_rid) return binary_data # Load individual physics object func load_individual_object(object_rid: RID, saved_data: PackedByteArray): RapierPhysicsServer2D.import_binary(object_rid, saved_data) # Deterministic replay system example var recorded_inputs = [] var recorded_state_hashes = [] func record_frame(frame_num: int): recorded_inputs.append(Input.get_vector("left", "right", "up", "down")) var hash = save_state(false) recorded_state_hashes.append(hash) func replay_from_beginning(): load_state() # Load initial state for i in range(recorded_inputs.size()): # Apply recorded input apply_input(recorded_inputs[i]) # Advance physics await get_tree().physics_frame # Verify determinism var current_hash = save_state(false) assert(current_hash == recorded_state_hashes[i], "Physics is deterministic") ``` ## Fluid Simulation (2D) Create and manipulate fluid particles ```gdscript # Create fluid from GDScript using Fluid2D node extends Node2D func create_water_simulation(): # Add Fluid2D node var fluid = Fluid2D.new() add_child(fluid) # Set fluid properties fluid.density = 1000.0 # kg/m³, water density fluid.lifetime = 5.0 # Particles disappear after 5 seconds (0 = infinite) fluid.debug_draw = true # Visualize particles # Create rectangular fluid area (10x20 grid of particles) var points = fluid.create_rectangle_points(10, 20) fluid.set_points(points) # Set collision layers (which layers fluid interacts with) fluid.collision_mask = 0b00000001 # Collides with layer 1 fluid.collision_layer = 0b00000010 # Fluid is on layer 2 return fluid # Create circular fluid splash func create_water_splash(position: Vector2, radius: int): var fluid = Fluid2D.new() add_child(fluid) fluid.position = position var points = fluid.create_circle_points(radius) fluid.set_points(points) # Add initial velocity to particles var velocities = PackedVector2Array() for i in range(points.size()): velocities.append(Vector2(randf_range(-100, 100), randf_range(-200, 0))) # Note: Velocities are set through physics server fluid API return fluid # Read fluid particle data for custom rendering func update_fluid_visualization(fluid: Fluid2D): var positions = fluid.get_points() var velocities = fluid.get_velocities() var accelerations = fluid.get_accelerations() var remaining_times = fluid.get_remaining_times() for i in range(positions.size()): var pos = positions[i] var vel = velocities[i] var alpha = 1.0 if fluid.lifetime > 0: alpha = remaining_times[i] / fluid.lifetime # Use this data for custom shader or particle rendering draw_circle(pos, fluid.radius, Color(0.2, 0.5, 1.0, alpha)) # Continuous fluid source (faucet effect) var faucet_timer = 0.0 var faucet_interval = 0.1 func _physics_process(delta): faucet_timer += delta if faucet_timer >= faucet_interval: faucet_timer = 0.0 spawn_water_droplet(Vector2(400, 100)) func spawn_water_droplet(spawn_pos: Vector2): var fluid = Fluid2D.new() add_child(fluid) fluid.position = spawn_pos fluid.density = 1000.0 fluid.lifetime = 3.0 var points = PackedVector2Array() points.append(Vector2.ZERO) fluid.set_points(points) # Query and manipulate fluid particles in specific regions func query_and_modify_particles(fluid: Fluid2D): var fluid_rid = fluid.get_rid() # Get all particles in a circular region var circle_center = Vector2(400, 300) var circle_radius = 100.0 var particles_in_circle = RapierPhysicsServer2D.fluid_get_particles_in_circle( fluid_rid, circle_center, circle_radius ) print("Found ", particles_in_circle.size(), " particles in circle") # Get particles in rectangular region var rect = Rect2(300, 200, 200, 200) var particles_in_rect = RapierPhysicsServer2D.fluid_get_particles_in_aabb( fluid_rid, rect ) print("Found ", particles_in_rect.size(), " particles in rectangle") # Delete specific particles (e.g., those in the circle) if particles_in_circle.size() > 0: RapierPhysicsServer2D.fluid_delete_points(fluid_rid, particles_in_circle) # Add new particles with velocities var new_points = PackedVector2Array([Vector2(100, 100), Vector2(150, 100)]) var new_velocities = PackedVector2Array([Vector2(50, -100), Vector2(-50, -100)]) RapierPhysicsServer2D.fluid_add_points_and_velocities( fluid_rid, new_points, new_velocities ) ``` Configure fluid effects for realistic behavior ```gdscript # Fluid effects control viscosity, surface tension, and elasticity extends Fluid2D func _ready(): # Viscosity effect (fluid thickness/resistance) var viscosity = FluidEffect2DViscosityArtificial.new() viscosity.viscosity_coefficient = 0.5 # Surface tension (cohesion between particles) var surface_tension = FluidEffect2DSurfaceTensionAkinci.new() surface_tension.fluid_tension_coefficient = 0.01 # Elasticity (bouncy fluid behavior) var elasticity = FluidEffect2DElasticity.new() elasticity.young_modulus = 100000.0 elasticity.poisson_ratio = 0.3 # Apply effects to fluid var effect_array: Array[Resource] = [viscosity, surface_tension, elasticity] set_effects(effect_array) ``` ## Fluid Simulation (3D) Create 3D fluid volumes and interactions ```gdscript extends Node3D func create_water_tank(): # Create 3D fluid box var fluid = Fluid3D.new() add_child(fluid) fluid.density = 1000.0 fluid.debug_draw = true # Create box-shaped fluid volume (10x10x10 particles) var points = fluid.create_box_points(10, 10, 10) fluid.set_points(points) # Set collision properties fluid.collision_mask = 1 fluid.collision_layer = 2 return fluid func create_sphere_fluid(center: Vector3, particle_radius: int): var fluid = Fluid3D.new() add_child(fluid) fluid.position = center var points = fluid.create_sphere_points(particle_radius) fluid.set_points(points) return fluid # Query and manipulate 3D fluid particles func query_3d_fluid_particles(fluid: Fluid3D): var fluid_rid = fluid.get_rid() # Get particles in spherical region var sphere_center = Vector3(0, 5, 0) var sphere_radius = 2.0 var particles_in_sphere = RapierPhysicsServer3D.fluid_get_particles_in_sphere( fluid_rid, sphere_center, sphere_radius ) print("Found ", particles_in_sphere.size(), " particles in sphere") # Get particles in box region var box = AABB(Vector3(-1, 0, -1), Vector3(2, 3, 2)) var particles_in_box = RapierPhysicsServer3D.fluid_get_particles_in_aabb( fluid_rid, box ) print("Found ", particles_in_box.size(), " particles in box") # Add particles with initial velocity (explosive effect) var explosion_center = Vector3(0, 2, 0) var new_points = PackedVector3Array() var new_velocities = PackedVector3Array() for i in range(100): var angle = randf() * TAU var elevation = randf() * PI - PI/2 var offset = Vector3( cos(angle) * cos(elevation), sin(elevation), sin(angle) * cos(elevation) ) * randf_range(0.1, 0.5) new_points.append(explosion_center + offset) new_velocities.append(offset.normalized() * randf_range(2.0, 5.0)) RapierPhysicsServer3D.fluid_add_points_and_velocities( fluid_rid, new_points, new_velocities ) # Render 3D fluid with custom mesh or shader func visualize_3d_fluid(fluid: Fluid3D): var positions = fluid.get_points() var velocities = fluid.get_velocities() # Create multimesh for efficient particle rendering var multimesh = MultiMesh.new() multimesh.transform_format = MultiMesh.TRANSFORM_3D multimesh.instance_count = positions.size() # Create sphere mesh for each particle var sphere_mesh = SphereMesh.new() sphere_mesh.radius = 0.1 multimesh.mesh = sphere_mesh # Set transforms for each particle for i in range(positions.size()): var transform = Transform3D() transform.origin = positions[i] multimesh.set_instance_transform(i, transform) # Color based on velocity magnitude var speed = velocities[i].length() var color = Color(0.2, 0.5, 1.0).lerp(Color(1.0, 1.0, 1.0), min(speed / 10.0, 1.0)) multimesh.set_instance_color(i, color) # Add to scene var multimesh_instance = MultiMeshInstance3D.new() multimesh_instance.multimesh = multimesh add_child(multimesh_instance) # 3D fluid effects configuration func setup_realistic_water(): var fluid = Fluid3D.new() add_child(fluid) # DFSPH viscosity (accurate fluid simulation) var viscosity = FluidEffect3DViscosityDFSPH.new() # WCSPH surface tension var surface_tension = FluidEffect3DSurfaceTensionWCSPH.new() surface_tension.fluid_tension_coefficient = 0.02 var effects: Array[Resource] = [viscosity, surface_tension] fluid.set_effects(effects) ``` ## Collision Detection and Queries Raycast and shape intersection queries ```gdscript extends Node2D func _physics_process(_delta): var space_state = get_world_2d().direct_space_state # Raycast from mouse to world var mouse_pos = get_global_mouse_position() var ray_origin = mouse_pos var ray_end = mouse_pos + Vector2(0, 1000) var ray_query = PhysicsRayQueryParameters2D.create(ray_origin, ray_end) ray_query.collision_mask = 1 ray_query.collide_with_bodies = true ray_query.collide_with_areas = false var result = space_state.intersect_ray(ray_query) if result: print("Hit: ", result.collider) print("Position: ", result.position) print("Normal: ", result.normal) queue_redraw() # Shape intersection query func check_area_for_bodies(center: Vector2, radius: float) -> Array: var space_state = get_world_2d().direct_space_state # Create circle shape for query var shape = CircleShape2D.new() shape.radius = radius var query = PhysicsShapeQueryParameters2D.new() query.shape = shape query.transform = Transform2D(0, center) query.collision_mask = 1 query.collide_with_bodies = true var results = space_state.intersect_shape(query) var bodies = [] for result in results: bodies.append(result.collider) return bodies # Point intersection (check what's at a position) func get_objects_at_point(point: Vector2) -> Array: var space_state = get_world_2d().direct_space_state var query = PhysicsPointQueryParameters2D.new() query.position = point query.collision_mask = 0xFFFFFFFF # All layers var results = space_state.intersect_point(query) return results # Shape cast (continuous collision detection) func cast_shape_along_motion(from: Vector2, motion: Vector2, shape: Shape2D) -> Dictionary: var space_state = get_world_2d().direct_space_state var query = PhysicsShapeQueryParameters2D.new() query.shape = shape query.transform = Transform2D(0, from) query.motion = motion var result = space_state.cast_motion(query) if result.size() > 0: return { "safe": result[0], # Safe fraction of motion (0.0 to 1.0) "unsafe": result[1] # First collision fraction } return {} # Collision shape query with context func collide_shape_detailed(shape: Shape2D, transform: Transform2D) -> Array: var space_state = get_world_2d().direct_space_state var query = PhysicsShapeQueryParameters2D.new() query.shape = shape query.transform = transform query.collision_mask = 1 query.margin = 0.08 # Collision detection margin return space_state.collide_shape(query) ``` ## Character Controller Advanced character movement with proper collision handling ```gdscript extends CharacterBody2D const SPEED = 300.0 const JUMP_VELOCITY = -400.0 var gravity = ProjectSettings.get_setting("physics/2d/default_gravity") func _physics_process(delta): # Apply gravity if not is_on_floor(): velocity.y += gravity * delta # Jump with floor detection if Input.is_action_just_pressed("ui_accept") and is_on_floor(): velocity.y = JUMP_VELOCITY # Horizontal movement var direction = Input.get_axis("ui_left", "ui_right") if direction: velocity.x = direction * SPEED else: velocity.x = move_toward(velocity.x, 0, SPEED) # Move with collision response move_and_slide() # Check collision details for i in range(get_slide_collision_count()): var collision = get_slide_collision(i) print("Collided with: ", collision.get_collider()) print("Normal: ", collision.get_normal()) print("Velocity: ", collision.get_collider_velocity()) # Precise collision testing func _physics_process_advanced(delta): # Configure floor detection floor_max_angle = deg_to_rad(45) # Maximum walkable slope floor_snap_length = 8 # Snap distance to floor floor_stop_on_slope = true floor_constant_speed = false floor_block_on_wall = true velocity.y += gravity * delta # Move and collide with more control var collision = move_and_collide(velocity * delta) if collision: var collider = collision.get_collider() var normal = collision.get_normal() # Bounce off surfaces velocity = velocity.bounce(normal) # Or slide along surfaces # velocity = velocity.slide(normal) # Platform detection and one-way collisions func setup_platform_detection(): # Enable platform floor layers floor_snap_length = 5 # One-way collision setup (in collision shape) var collision_shape = $CollisionShape2D collision_shape.one_way_collision = true collision_shape.one_way_collision_margin = 1.0 ``` ## Joints and Constraints Create physics joints between bodies ```gdscript extends Node2D func create_rope_chain(start_pos: Vector2, num_links: int, link_size: Vector2): var previous_body = null for i in range(num_links): # Create rope link var body = RigidBody2D.new() body.position = start_pos + Vector2(0, i * link_size.y) add_child(body) var collision = CollisionShape2D.new() var shape = RectangleShape2D.new() shape.size = link_size collision.shape = shape body.add_child(collision) # Connect to previous link with pin joint if previous_body: var joint = PinJoint2D.new() joint.position = Vector2(0, -link_size.y / 2) body.add_child(joint) joint.node_a = previous_body.get_path() joint.node_b = body.get_path() joint.softness = 0.0 # Note: Rapier doesn't support softness previous_body = body return previous_body # Damped spring joint (suspension) func create_suspension(body_a: RigidBody2D, body_b: RigidBody2D, anchor: Vector2): var spring = DampedSpringJoint2D.new() add_child(spring) spring.node_a = body_a.get_path() spring.node_b = body_b.get_path() spring.length = 100 # Rest length spring.rest_length = 100 spring.stiffness = 64.0 spring.damping = 1.0 return spring # Groove joint (sliding constraint) func create_sliding_door(door: RigidBody2D, wall: StaticBody2D): var groove = GrooveJoint2D.new() add_child(groove) groove.node_a = wall.get_path() groove.node_b = door.get_path() groove.length = 200 # Slide distance groove.initial_offset = 0 return groove # Motor joint example (rotating platform) func create_rotating_platform(): var static_base = StaticBody2D.new() static_base.position = Vector2(400, 300) add_child(static_base) var platform = RigidBody2D.new() platform.position = Vector2(400, 300) add_child(platform) var collision = CollisionShape2D.new() var shape = RectangleShape2D.new() shape.size = Vector2(200, 20) collision.shape = shape platform.add_child(collision) # Pin joint with motor var joint = PinJoint2D.new() static_base.add_child(joint) joint.node_a = static_base.get_path() joint.node_b = platform.get_path() joint.motor_enabled = true joint.motor_target_velocity = 2.0 # rad/s return platform ``` ## Multibody Joints and Inverse Kinematics Create articulated chains with IK solving capabilities ```gdscript extends Node2D # Joint types: # - JOINT_TYPE_IMPULSE_JOINT (0): Standard impulse-based joints # - JOINT_TYPE_MULTIBODY_JOINT (1): Articulated multibody joints # - JOINT_TYPE_MULTIBODY_KINEMATIC_JOINT (2): Kinematic multibody joints func create_robot_arm_with_ik(): var base = StaticBody2D.new() base.position = Vector2(400, 300) add_child(base) var segments = [] var joints = [] var previous_body = base # Create 4-segment robot arm for i in range(4): var segment = RigidBody2D.new() segment.position = previous_body.position + Vector2(0, 50) add_child(segment) var collision = CollisionShape2D.new() var shape = RectangleShape2D.new() shape.size = Vector2(10, 40) collision.shape = shape segment.add_child(collision) # Create multibody joint var joint = PinJoint2D.new() previous_body.add_child(joint) joint.node_a = previous_body.get_path() joint.node_b = segment.get_path() # Convert to multibody joint for IK var joint_rid = joint.get_rid() RapierPhysicsServer2D.joint_set_extra_param( joint_rid, RapierPhysicsServer2D.JOINT_PARAM_TYPE, RapierPhysicsServer2D.JOINT_TYPE_MULTIBODY_JOINT ) segments.append(segment) joints.append(joint_rid) previous_body = segment return {"segments": segments, "joints": joints, "end_effector": previous_body} # Solve IK to reach target position func solve_ik_to_target(robot_arm: Dictionary, target_pos: Vector2): var end_joint = robot_arm.joints[-1] # Last joint in chain # Create target transform (2D: rotation angle, position) var target_transform = Transform2D(0, target_pos) # Solve IK RapierPhysicsServer2D.joint_solve_inverse_kinematics(end_joint, target_transform) # Configure IK solver with advanced options func setup_ik_solver(joint_rid: RID): var damping = 0.5 # Damping factor (0-1) var max_iterations = 100 # Max IK solver iterations var constrained_axes = 3 # Constrain XY translation (bits: 1=X, 2=Y, 4=Z, etc.) var epsilon_linear = 0.001 # Linear convergence threshold var epsilon_angular = 0.001 # Angular convergence threshold RapierPhysicsServer2D.joint_set_ik_options( joint_rid, damping, max_iterations, constrained_axes, epsilon_linear, epsilon_angular ) # Reset IK options to defaults func reset_ik(joint_rid: RID): RapierPhysicsServer2D.joint_reset_ik_options(joint_rid) # 3D example: Robot arm reaching in 3D space func create_3d_robot_arm_with_ik(): var base = StaticBody3D.new() base.position = Vector3(0, 0, 0) add_child(base) var previous_body = base var joints = [] for i in range(5): var segment = RigidBody3D.new() segment.position = previous_body.position + Vector3(0, 0.5, 0) add_child(segment) var collision = CollisionShape3D.new() var shape = BoxShape3D.new() shape.size = Vector3(0.1, 0.4, 0.1) collision.shape = shape segment.add_child(collision) # Create hinge joint var joint = HingeJoint3D.new() previous_body.add_child(joint) joint.node_a = previous_body.get_path() joint.node_b = segment.get_path() # Convert to multibody joint var joint_rid = joint.get_rid() RapierPhysicsServer3D.joint_set_extra_param( joint_rid, RapierPhysicsServer3D.JOINT_PARAM_TYPE, RapierPhysicsServer3D.JOINT_TYPE_MULTIBODY_JOINT ) joints.append(joint_rid) previous_body = segment return {"joints": joints, "end_effector": previous_body} func solve_3d_ik_to_target(robot_arm: Dictionary, target_pos: Vector3): var end_joint = robot_arm.joints[-1] var target_transform = Transform3D(Basis.IDENTITY, target_pos) # Constrain all 6 DOF (3 translation + 3 rotation) var constrained_axes = 63 # 111111 in binary = all axes RapierPhysicsServer3D.joint_set_ik_options(end_joint, 0.5, 100, constrained_axes, 0.001, 0.001) # Solve IK RapierPhysicsServer3D.joint_solve_inverse_kinematics(end_joint, target_transform) # Interactive IK example var robot_arm = null func _ready(): robot_arm = create_robot_arm_with_ik() func _physics_process(_delta): if robot_arm and Input.is_action_pressed("ui_accept"): var mouse_pos = get_global_mouse_position() solve_ik_to_target(robot_arm, mouse_pos) ``` ## Advanced Physics Configuration Fine-tune collision behavior and performance ```gdscript extends RigidBody2D func _ready(): # Mass and inertia mass = 10.0 inertia = 100.0 # Resistance to rotation center_of_mass_mode = RigidBody2D.CENTER_OF_MASS_MODE_CUSTOM center_of_mass = Vector2(0, 10) # Offset center of mass # Damping (air resistance) linear_damp = 0.1 # Slow down linear motion angular_damp = 0.5 # Slow down rotation linear_damp_mode = RigidBody2D.DAMP_MODE_COMBINE # Combine with physics space damping # Continuous Collision Detection (CCD) for fast-moving objects continuous_cd = RigidBody2D.CCD_MODE_CAST_SHAPE # Collision properties max_contacts_reported = 4 contact_monitor = true # Enable collision callbacks # Lock axes lock_rotation = false freeze = false freeze_mode = RigidBody2D.FREEZE_MODE_STATIC # Collision layer management func setup_collision_layers(): # Set which layer this body is on (can be multiple) collision_layer = 0b00000001 # Layer 1 # Set which layers this body collides with collision_mask = 0b00000011 # Collides with layers 1 and 2 # Helper functions set_collision_layer_value(1, true) # Enable layer 1 set_collision_mask_value(3, false) # Disable collision with layer 3 # Contact reporting and callbacks func _ready(): contact_monitor = true max_contacts_reported = 10 body_entered.connect(_on_body_entered) body_exited.connect(_on_body_exited) func _on_body_entered(body: Node): print("Collision started with: ", body.name) func _on_body_exited(body: Node): print("Collision ended with: ", body.name) # Direct body state access for advanced control func _integrate_forces(state: PhysicsDirectBodyState2D): # Direct access to physics state during physics step var transform = state.get_transform() var linear_vel = state.get_linear_velocity() var angular_vel = state.get_angular_velocity() # Apply custom forces state.apply_central_force(Vector2(100, 0)) state.apply_torque(50.0) # Apply impulses (instant velocity change) state.apply_central_impulse(Vector2(0, -500)) # Direct state modification state.set_linear_velocity(linear_vel * 0.98) # Custom damping # Check contacts var contact_count = state.get_contact_count() for i in range(contact_count): var contact_pos = state.get_contact_local_position(i) var contact_normal = state.get_contact_local_normal(i) var contact_shape = state.get_contact_local_shape(i) var collider = state.get_contact_collider(i) var collider_vel = state.get_contact_collider_velocity_at_position(i) ``` ## Manual Physics Control Manually step physics simulation for precise control ```gdscript extends Node # Manual stepping allows custom physics loops, substeps, or replay systems # Useful for deterministic simulations, custom time control, or slow-motion effects func setup_manual_physics(): # Get the default 2D physics space var space_rid = get_world_2d().space # Disable automatic physics stepping # You'll need to call space_step manually each frame return space_rid func _physics_process(delta): # Option 1: Manual stepping with custom delta var space_rid = get_world_2d().space var custom_delta = delta * 0.5 # Slow-motion effect # Step the physics space manually RapierPhysicsServer2D.space_step(space_rid, custom_delta) # Flush queries to update spatial query results RapierPhysicsServer2D.space_flush_queries(space_rid) # Advanced: Multiple substeps for higher precision func physics_step_with_substeps(space_rid: RID, delta: float, substeps: int): var substep_delta = delta / substeps for i in range(substeps): RapierPhysicsServer2D.space_step(space_rid, substep_delta) # Only flush queries once after all substeps RapierPhysicsServer2D.space_flush_queries(space_rid) # Get active bodies in the simulation func get_active_bodies_list(space_rid: RID) -> Array: var active_bodies = RapierPhysicsServer2D.space_get_active_bodies(space_rid) print("Active bodies: ", active_bodies.size()) return active_bodies # Efficiently get transforms of multiple bodies at once func get_multiple_body_transforms(space_rid: RID, body_rids: Array) -> Array: # This is more efficient than calling get_transform on each body individually var transforms = RapierPhysicsServer2D.space_get_bodies_transform(space_rid, body_rids) return transforms # Example: Bullet time effect var bullet_time_active = false var normal_time_scale = 1.0 var slow_time_scale = 0.1 func _physics_process(delta): var space_rid = get_world_2d().space if Input.is_action_just_pressed("activate_bullet_time"): bullet_time_active = !bullet_time_active var time_scale = slow_time_scale if bullet_time_active else normal_time_scale RapierPhysicsServer2D.space_step(space_rid, delta * time_scale) RapierPhysicsServer2D.space_flush_queries(space_rid) # Example: Frame-perfect replay system class ReplaySystem: var recorded_states = [] var current_frame = 0 func record_frame(space_rid: RID): var active_bodies = RapierPhysicsServer2D.space_get_active_bodies(space_rid) var transforms = RapierPhysicsServer2D.space_get_bodies_transform(space_rid, active_bodies) recorded_states.append({ "bodies": active_bodies, "transforms": transforms, "frame": current_frame }) current_frame += 1 func replay_frame(frame_num: int): if frame_num < recorded_states.size(): var state = recorded_states[frame_num] # Apply recorded transforms to bodies for i in range(state.bodies.size()): var body_rid = state.bodies[i] var transform = state.transforms[i] # Set body transform (would need additional API for this) # This is a conceptual example ``` ## Area Nodes and Space Overrides Create trigger zones and modify physics properties ```gdscript extends Area2D func _ready(): # Area properties monitorable = true # Can be detected by other areas monitoring = true # Can detect other bodies/areas # Collision detection collision_layer = 0b00000100 # Layer 3 collision_mask = 0b00000001 # Detect layer 1 # Connect signals body_entered.connect(_on_body_entered) body_exited.connect(_on_body_exited) area_entered.connect(_on_area_entered) area_exited.connect(_on_area_exited) # Space override (modify physics in this area) space_override = Area2D.SPACE_OVERRIDE_COMBINE gravity_space_override = Area2D.SPACE_OVERRIDE_COMBINE gravity = 200 # Custom gravity magnitude gravity_direction = Vector2(0, 1) # Down # Linear damping override (water resistance effect) linear_damp_space_override = Area2D.SPACE_OVERRIDE_COMBINE linear_damp = 2.0 # Angular damping override angular_damp_space_override = Area2D.SPACE_OVERRIDE_COMBINE angular_damp = 1.5 func _on_body_entered(body: Node2D): print(body.name, " entered the area") if body is RigidBody2D: # Apply effects to entering body body.apply_central_impulse(Vector2(0, -300)) func _on_body_exited(body: Node2D): print(body.name, " exited the area") # Water zone example func create_water_zone(rect: Rect2): var water_area = Area2D.new() add_child(water_area) var collision = CollisionShape2D.new() var shape = RectangleShape2D.new() shape.size = rect.size collision.shape = shape water_area.add_child(collision) water_area.position = rect.position # Override physics water_area.space_override = Area2D.SPACE_OVERRIDE_COMBINE water_area.gravity_direction = Vector2(0, 1) water_area.gravity = 50 # Reduced gravity in water water_area.linear_damp_space_override = Area2D.SPACE_OVERRIDE_COMBINE water_area.linear_damp = 5.0 # Heavy damping for water resistance return water_area # Zero gravity zone func create_space_zone(center: Vector2, radius: float): var space_area = Area2D.new() add_child(space_area) space_area.position = center var collision = CollisionShape2D.new() var shape = CircleShape2D.new() shape.radius = radius collision.shape = shape space_area.add_child(collision) # Disable gravity space_area.space_override = Area2D.SPACE_OVERRIDE_REPLACE space_area.gravity_space_override = Area2D.SPACE_OVERRIDE_REPLACE space_area.gravity = 0 return space_area ``` ## Build Configuration Compile the physics extension with custom features ```bash # Prerequisites: Install Rust and cargo # https://rustup.rs/ # Build for 2D with standard precision and SIMD cargo build --release --features="build2d" --no-default-features # Build for 3D with standard precision cargo build --release --features="build3d" --no-default-features # Build with double precision (large world coordinates) cargo build --release --features="build2d-f64" --no-default-features # Build with cross-platform determinism (slower but consistent across platforms) cargo build --release --features="build2d,enhanced-determinism" --no-default-features # Build with parallel solver (not available on web) cargo build --release --features="build2d,parallel" --no-default-features # Build for web (threads) cargo build --release --target wasm32-unknown-emscripten \ --features="build2d,experimental-wasm" --no-default-features # Build for web (no threads) cargo build --release --target wasm32-unknown-emscripten \ --features="build2d,experimental-wasm-nothreads" --no-default-features # Copy compiled library to addon directory # macOS example: cp target/release/libgodot_rapier.dylib \ bin2d/addons/godot-rapier2d/bin/libgodot_rapier.macos.framework/ # Windows example: cp target/release/godot_rapier.dll \ bin2d/addons/godot-rapier2d/bin/godot_rapier.windows.x86_64-pc-windows-msvc.dll # Linux example: cp target/release/libgodot_rapier.so \ bin2d/addons/godot-rapier2d/bin/libgodot_rapier.linux.x86_64-unknown-linux-gnu.so ``` Configure Cargo.toml features for development ```toml [features] default = ["build2d", "test"] build2d = ["single-dim2", "serde-serialize", "simd-stable"] build3d = ["single-dim3", "serde-serialize", "simd-stable"] build2d-f64 = ["double-dim2", "serde-serialize", "simd-stable"] build3d-f64 = ["double-dim3", "serde-serialize", "simd-stable"] # Feature combinations single-dim2 = ["single", "dim2", "rapier2d", "salva2d"] double-dim2 = ["double", "dim2", "rapier2d-f64", "salva2d-f64"] single-dim3 = ["single", "dim3", "rapier3d", "salva3d"] double-dim3 = ["double", "dim3", "rapier3d-f64", "salva3d-f64"] # Performance features simd-stable = ["rapier2d/simd-stable", "rapier3d/simd-stable"] parallel = ["rapier2d/parallel", "rapier3d/parallel"] # Determinism feature (slower but reproducible) enhanced-determinism = [ "rapier2d/enhanced-determinism", "rapier3d/enhanced-determinism" ] # Serialization support serde-serialize = [ "serde", "bincode", "serde_json", "rapier2d/serde-serialize", "rapier3d/serde-serialize" ] # Debug builds with optimization level 1 for faster iteration [profile.dev] opt-level = 1 # Release builds with maximum optimization [profile.release] opt-level = 3 lto = "fat" codegen-units = 1 ``` ## Summary Godot Rapier Physics provides a production-ready alternative physics engine for Godot projects requiring enhanced stability, determinism, or fluid simulation capabilities. The plugin maintains full compatibility with Godot's standard physics API, allowing existing projects to switch physics engines with minimal code changes by simply changing the project settings. All standard Godot physics nodes (RigidBody2D/3D, CharacterBody2D/3D, Area2D/3D, joints, and raycasts) work seamlessly with Rapier, while gaining benefits like improved contact resolution, elimination of ghost collisions, better performance with large numbers of colliders, support for physics state serialization, and advanced features like inverse kinematics for articulated bodies. The unique features of Rapier Physics—deterministic simulation with optional cross-platform consistency, complete state serialization for replay systems, integrated fluid simulation through Salva with spatial particle queries, multibody joints with inverse kinematics solving, manual physics stepping for precise control, soft continuous collision detection (CCD) for fast-moving objects, configurable solver presets for performance vs stability tradeoffs, and fine-grained control over contact behavior—make it particularly suitable for multiplayer games requiring rollback netcode, robotics simulations needing IK solutions, physics-based puzzles needing exact reproducibility, games with complex articulated characters or mechanisms, simulation applications requiring custom physics loops, and games with water or fluid mechanics. The plugin supports all major platforms including desktop (Windows, macOS, Linux), mobile (Android, iOS), and web (WebAssembly with or without threads), with build configurations for different precision levels (single/double), performance characteristics (SIMD, parallel solving), and determinism guarantees.