Rust Game Dev: Rapier and Macroquad #
In this post on using Rapier Physics with Macroquad, we take a look at getting started with Rapier and how you might set it up in a Macroquad project. In recent posts, we have looked at Rust game dev engines and Rust game physics engines. Here, we take one from each of those posts and create a couple of apps. The first is just a bouncing ball, based on the Rapier Getting Started Guide. Next, we level things up, and use Rapier height fields, sensors, collision events for a slightly more sophisticated simulation.
Macroquad is a fast and lightweight Rust game development library, intended to be easy to use and well-suited to game prototyping. Rapier, is probably the most established game physics engine in the Rust game dev ecosystem.
It is a collection of crates for 2D and 3D game physics with regular (f32
) and high precision (f64
) offerings. Rapier is fully-featured, and also compiles to WASM, for use on the web.
With the introductions out of the way, let’s take a closer look at what we are working on. I am new to game development in Rust, so please reach out if you have alternative ways of proceeding here, which I could try to incorporate.
🧱 What we’re Building #
Please enable JavaScript to watch the video 📼
We look at a couple of examples. The first, has just two physical rigid bodies: a ball and a ground surface. Porting the Rapier Getting Started guide to Macroquad, we see the ball bounce on the ground before coming to rest.
![Rapier Physics with Macroquad: A collection of yellow, orange, and blue balls have floated to the top of the window in a screen-capture. They are tightly packed, though not evenly distributed, with the collection being more balls deep at the centre of the window.](https://rodneylab-nebula-a-1zqt.shuttle.app/v1/lab/post/rapier-physics-with-macroquad/rapier-physics-with-macroquad-bubbles-example.920a95a58187.png?w=592&h=333&fit=min&auto=format)
Stepping up a notch, the second example uses more Rapier features. Reversing gravity, bubbles fired in random directions float to the top of the screen, where they are caught by a Rapier height field.
⚙️ Rapier Physics with Macroquad Project Config #
To start, here is the Cargo.toml
:
[package]name = "rapier-example"version = "0.1.0"edition = "2021"license = "BSD-3-Clause"repository = "https://github.com/rodneylab/rapier-example"# macroquad 0.3.26 requires MSRV 1.73rust-version = "1.73"description = "Rapier Physics with Macroquad 🗡️ building a basic game physics simulation in Rust using rapier physics and Macroquad for rendering 🖥️"[dependencies]crossbeam = "0.8.4"macroquad = { version = "0.3.26", default-features = false }rand = "0.8.5"rand_distr = "0.4.3"rapier2d = { version = "0.22.0", features = ["simd-stable"] }
-
The world is not particularly large, or fast-paced for either demo, so the
f32
version of rapier should suffice, and we will just use 2D simulation. -
The bubble demo uses
rand
andrand_distr
to generate a standard normal distribution for randomly deciding the initial ball velocity. -
We use
crossbeam
for Rapier collision event handling.
👋🏽 Rapier Physics with Macroquad Hello World #
I set the project up as a series of examples, so you can place the source code for the hello world
example in a new examples
folder as examples/hello_world.rs
. Here is the full Macroquad code for this first example (based on Rapier Rust Getting Started ):
examples/hello_world.rs
— click to expand code.
1 use macroquad::{2 color::Color,3 input::{is_key_released, KeyCode},4 shapes::draw_circle,5 window::{clear_background, next_frame, Conf},6 };7 use rapier2d::{8 dynamics::{9 CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,10 RigidBodyBuilder, RigidBodySet,11 },12 geometry::{BroadPhaseMultiSap, ColliderBuilder, ColliderSet, NarrowPhase},13 na::{vector, Vector2},14 pipeline::{PhysicsPipeline, QueryPipeline},15 prelude::nalgebra,16 };1718 pub const CARROT_ORANGE: Color = Color {19 r: 247.0 / 255.0,20 g: 152.0 / 255.0,21 b: 36.0 / 255.0,22 a: 1.0,23 };24 pub const GUNMETAL: Color = Color {25 r: 49.0 / 255.0,26 g: 57.0 / 255.0,27 b: 60.0 / 255.0,28 a: 1.0,29 };3031 const WINDOW_WIDTH: f32 = 1366.0;32 const WINDOW_HEIGHT: f32 = 768.0;33 // 1 metre is 50 pixels34 const PHYSICS_SCALE: f32 = 50.0;3536 #[derive(Debug)]37 struct Ball {38 radius: f32,39 position: Vector2<f32>,40 }4142 impl Default for Ball {43 fn default() -> Ball {44 Ball {45 radius: 0.6,46 position: vector![47 WINDOW_WIDTH / (2.0 * PHYSICS_SCALE),48 WINDOW_HEIGHT / (2.0 * PHYSICS_SCALE),49 ],50 }51 }52 }5354 fn conf() -> Conf {55 #[allow(clippy::cast_possible_truncation)]56 Conf {57 window_title: String::from("Rapier Macroquad Hello World"),58 window_width: WINDOW_WIDTH as i32,59 window_height: WINDOW_HEIGHT as i32,60 high_dpi: true,61 ..Default::default()62 }63 }6465 #[macroquad::main(conf)]66 async fn main() {67 let mut ball = Ball::default();6869 let mut rigid_body_set = RigidBodySet::new();70 let mut collider_set = ColliderSet::new();7172 // Create the ground73 let collider_half_thickness = 0.05;74 let collider = ColliderBuilder::cuboid(100.0, collider_half_thickness)75 .translation(vector![76 0.0,77 (WINDOW_HEIGHT / -PHYSICS_SCALE) - collider_half_thickness78 ])79 .build();80 collider_set.insert(collider);8182 // Create the bouncing ball83 let rigid_body = RigidBodyBuilder::dynamic()84 .translation(ball.position)85 .build();86 let collider = ColliderBuilder::ball(0.6).restitution(0.7).build();87 let ball_body_handle = rigid_body_set.insert(rigid_body);88 collider_set.insert_with_parent(collider, ball_body_handle, &mut rigid_body_set);8990 // Create other structures necessary for the simulation91 let gravity = vector![0.0, -9.81];92 let integration_parameters = IntegrationParameters::default();93 let mut physics_pipeline = PhysicsPipeline::new();94 let mut island_manager = IslandManager::new();95 let mut broad_phase = BroadPhaseMultiSap::new();96 let mut narrow_phase = NarrowPhase::new();97 let mut impulse_joint_set = ImpulseJointSet::new();98 let mut multibody_joint_set = MultibodyJointSet::new();99 let mut ccd_solver = CCDSolver::new();100 let mut query_pipeline = QueryPipeline::new();101 let physics_hooks = ();102 let event_handler = ();103104 // run the game loop, stepping the simulation once per frame.105 loop {106 clear_background(GUNMETAL);107108 if is_key_released(KeyCode::Escape) {109 break;110 }111112 draw_circle(113 PHYSICS_SCALE * ball.position.x,114 -PHYSICS_SCALE * ball.position.y,115 PHYSICS_SCALE * ball.radius,116 CARROT_ORANGE,117 );118 physics_pipeline.step(119 &gravity,120 &integration_parameters,121 &mut island_manager,122 &mut broad_phase,123 &mut narrow_phase,124 &mut rigid_body_set,125 &mut collider_set,126 &mut impulse_joint_set,127 &mut multibody_joint_set,128 &mut ccd_solver,129 Some(&mut query_pipeline),130 &physics_hooks,131 &event_handler,132 );133134 let ball_body = &rigid_body_set[ball_body_handle];135 println!("Ball altitude: {}", ball_body.translation().y);136137 // update ball position (used for drawing)138 ball.position = *ball_body.translation();139140 next_frame().await;141 }142 }
The Rapier docs are fantastic, both the User Guide and the library API docs. The Common Mistakes section of the User Guide is a good port of call for initial issues.
-
we use SI units with the Rapier model, so scale pixels (used by Macroquad for rendering) with a
factor of 50 pixels per metre (line
34
). The Common Mistakes doc (just mentioned) recommends scaling. - for colliders, we are generally working in half extents, so we give the radius of the ball and the half-thickness of the collider.
-
we set gravity to
-9.81
in line91
(we are working in SI units). Notice the Rapier Physics and Macroquad rendering vertical axes increase in opposite directions, with the rendering scale zero at the top and increasing downwards. -
we don’t need physics hooks or an event handler in this example, so set them each to the
unit type (lines
101
&102
).
Collider Properties #
Restitution is a measure of how much kinetic energy the ball retains on collision. With zero restitution, it will not bounce, while with 1.0, it will rebound with equal and opposite force. You can set other physical properties on colliders in the builder. Density, for example, is the easiest way to make a body heavier (hence adjusting movement and collision characteristics).
Running the Hello World Example #
To run the example, use the following command in the Terminal:
cargo run --example hello_world
![Rapier Physics with Macroquad: A carrot orange ball at the centre of a gun metal window hovers, presumably falling.](https://rodneylab-nebula-a-1zqt.shuttle.app/v1/lab/post/rapier-physics-with-macroquad/rapier-physics-with-macroquad-hello-world-example.4d6869ac93fe.png?w=592&h=333&fit=min&auto=format)
🫧 Levelling Up: Floating Ball Example #
Next, let’s turn to the floating ball example. In this example, we spawn floating balls that get caught at the top of the screen, by a collider. I used a standard normal distributed random variable to set the ball’s initial velocity; we do not just fire them vertically.
Example Features #
The ceiling is composed of a saw-tooth-like height field, instead of a flat cuboid. The idea here is to stop the balls sliding straight to the corners when they hit the ceiling. We look closer at the height field in a moment.
A new ball is only spawned once the existing ones have come to rest. This island manager helps here to check if they are still any active dynamic balls, before spawning a new one.
Finally, there is a sensor collider at the bottom of the window. Sensors just check a collision has occurred, and are inert. Typically, you can use them to find out when an object has entered an area. As the windows fills, eventually, there will be no more space for a freshly spawned ball, and it will bounce off other balls and hit the bottom of the window. I use an event handler so when a ball hits the sensor at the bottom of the window, I can set the simulation to end.
![Rapier Physics with Macroquad: A collection of yellow, orange, and blue balls have floated to the top of the window in a screen-capture. They are tightly packed, though not evenly distributed, with the ball reaching almost down to the ground in the centre. A lone ball sits bottom centre on the floor of the window.](https://rodneylab-nebula-a-1zqt.shuttle.app/v1/lab/post/rapier-physics-with-macroquad/rapier-physics-with-macroquad-bubbles-example-end.ac0d15b7d5cc.png?w=592&h=333&fit=min&auto=format)
Bubble Example Code #
Here is the code for the example, which I saved to examples/bubbles.rs
:
examples/bubbles.rs
— click to expand code.
1 use macroquad::{2 color::Color,3 input::{is_key_released, KeyCode},4 miniquad::date,5 rand::{self as macroquad_rand, srand},6 shapes::draw_circle,7 window::{clear_background, next_frame, Conf},8 };9 use rand::{rngs::StdRng, Rng, SeedableRng};10 use rand_distr::Standard;11 use rapier2d::{12 dynamics::{13 CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,14 RigidBodyBuilder, RigidBodyHandle, RigidBodySet,15 },16 geometry::{17 BroadPhaseMultiSap, ColliderBuilder, ColliderSet, CollisionEvent, CollisionEventFlags, NarrowPhase,18 },19 math::Isometry,20 na::{vector, DVector, Vector2},21 pipeline::{ActiveEvents, ChannelEventCollector, PhysicsPipeline, QueryPipeline},22 prelude::nalgebra,23 };2425 pub const BLUE_CRAYOLA: Color = Color {26 r: 33.0 / 255.0,27 g: 118.0 / 255.0,28 b: 1.0,29 a: 1.0,30 };3132 pub const CARROT_ORANGE: Color = Color {33 r: 247.0 / 255.0,34 g: 152.0 / 255.0,35 b: 36.0 / 255.0,36 a: 1.0,37 };38 pub const CELESTIAL_BLUE: Color = Color {39 r: 51.0 / 255.0,40 g: 161.0 / 255.0,41 b: 253.0,42 a: 1.0,43 };44 pub const GUNMETAL: Color = Color {45 r: 49.0 / 255.0,46 g: 57.0 / 255.0,47 b: 60.0 / 255.0,48 a: 1.0,49 };50 pub const SUNGLOW: Color = Color {51 r: 4253.0 / 255.0,52 g: 202.0 / 255.0,53 b: 64.0 / 255.0,54 a: 1.0,55 };5657 const BALL_COLOURS: [Color; 4] = [BLUE_CRAYOLA, CARROT_ORANGE, CELESTIAL_BLUE, SUNGLOW];5859 const WINDOW_WIDTH: f32 = 1366.0;60 const WINDOW_HEIGHT: f32 = 768.0;61 // 1 metre is 50 pixels62 const PHYSICS_SCALE: f32 = 50.0;63 const BALL_RADIUS: f32 = 0.6;6465 #[derive(Debug)]66 struct Ball {67 radius: f32,68 position: Vector2<f32>,69 physics_handle: Option<RigidBodyHandle>,70 colour: Color,71 }7273 impl Default for Ball {74 fn default() -> Ball {75 Ball {76 radius: BALL_RADIUS,77 position: vector![78 WINDOW_WIDTH / (2.0 * PHYSICS_SCALE),79 (2.0 * BALL_RADIUS) - (1.0 * WINDOW_HEIGHT / PHYSICS_SCALE)80 ],81 physics_handle: None,82 colour: BALL_COLOURS[macroquad_rand::gen_range(0, BALL_COLOURS.len())],83 }84 }85 }8687 impl Ball {88 fn physics_handle(&mut self, physics_handle: RigidBodyHandle) -> &mut Ball {89 self.physics_handle = Some(physics_handle);90 self91 }92 }9394 fn conf() -> Conf {95 #[allow(clippy::cast_possible_truncation)]96 Conf {97 window_title: String::from("Macroquad Rapier Bubbles"),98 window_width: WINDOW_WIDTH as i32,99 window_height: WINDOW_HEIGHT as i32,100 high_dpi: true,101 ..Default::default()102 }103 }104105 fn create_physics_for_ball(106 ball: &Ball,107 rigid_body_set: &mut RigidBodySet,108 collider_set: &mut ColliderSet,109 normal_distribution: &mut StdRng,110 ) -> RigidBodyHandle {111 let x_velocity = normal_distribution.sample(Standard);112 let linear_velocity = vector![x_velocity, 1.0];113 let rigid_body = RigidBodyBuilder::dynamic()114 .translation(ball.position)115 .linvel(linear_velocity)116 .build();117 let collider = ColliderBuilder::ball(BALL_RADIUS)118 .restitution(0.0)119 .density(0.001)120 .active_events(ActiveEvents::COLLISION_EVENTS)121 .build();122 let ball_body_handle = rigid_body_set.insert(rigid_body);123 collider_set.insert_with_parent(collider, ball_body_handle, rigid_body_set);124 ball_body_handle125 }126127 fn create_ceiling(ceiling_width: f32, max_balls: u32, collider_set: &mut ColliderSet) {128 let collider_half_thickness = 0.05;129 let nsubdivs: usize = (max_balls * 2)130 .try_into()131 .expect("Expected fewer subdivisions");132 let heights = DVector::from_fn(nsubdivs + 1, |i, _| if i % 2 == 0 { -1.2 } else { 0.0 });133 let collider =134 ColliderBuilder::heightfield(heights, vector![ceiling_width, collider_half_thickness])135 .translation(vector![136 0.5 * WINDOW_WIDTH / PHYSICS_SCALE,137 -1.0 * collider_half_thickness138 ])139 .friction(1.0)140 .restitution(0.0)141 .build();142 collider_set.insert(collider);143 }144145 fn create_ground(collider_set: &mut ColliderSet) {146 let collider_thickness = 0.1;147 let collider = ColliderBuilder::cuboid(100.0, collider_thickness)148 .translation(vector![149 0.0,150 (WINDOW_HEIGHT / -PHYSICS_SCALE) - 0.5 * collider_thickness151 ])152 .sensor(true)153 .build();154 collider_set.insert(collider);155 }156157 fn create_side_walls(gap: f32, collider_set: &mut ColliderSet) {158 // left wall159 let collider_half_thickness = 0.05;160 let collider =161 ColliderBuilder::cuboid(0.5 * WINDOW_HEIGHT / PHYSICS_SCALE, collider_half_thickness)162 .position(Isometry::new(163 vector![164 gap - collider_half_thickness,165 (WINDOW_HEIGHT / (-2.0 * PHYSICS_SCALE))166 ],167 std::f32::consts::FRAC_PI_2,168 ))169 .build();170 collider_set.insert(collider);171172 // right wall173 let collider_half_thickness = 0.05;174 let collider =175 ColliderBuilder::cuboid(0.5 * WINDOW_HEIGHT / PHYSICS_SCALE, collider_half_thickness)176 .position(Isometry::new(177 vector![178 (WINDOW_WIDTH / PHYSICS_SCALE) + collider_half_thickness - gap,179 (WINDOW_HEIGHT / (-2.0 * PHYSICS_SCALE))180 ],181 3.0 * std::f32::consts::FRAC_PI_2,182 ))183 .build();184 collider_set.insert(collider);185 }186187 fn draw_balls(balls: &[Ball]) {188 for ball in balls {189 let Ball {190 colour,191 position,192 radius,193 ..194 } = ball;195 draw_circle(196 PHYSICS_SCALE * position.x,197 -PHYSICS_SCALE * position.y,198 PHYSICS_SCALE * radius,199 *colour,200 );201 }202 }203204 fn update_balls(balls: &mut [Ball], rigid_body_set: &RigidBodySet) {205 for ball in balls {206 if let Some(handle) = ball.physics_handle {207 let ball_body = &rigid_body_set[handle];208 ball.position = *ball_body.translation();209 }210 }211 }212213 #[macroquad::main(conf)]214 async fn main() {215 // seed macroquad random number generator216 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]217 {218 srand(date::now().floor() as u64);219 }220221 let mut rigid_body_set = RigidBodySet::new();222 let mut collider_set = ColliderSet::new();223224 // Create the ground225 create_ground(&mut collider_set);226227 // Create the ceiling228 // maximum number of balls that can fit across the window229 let max_balls: u32;230 #[allow(231 clippy::cast_precision_loss,232 clippy::cast_sign_loss,233 clippy::cast_possible_truncation234 )]235 {236 max_balls = ((WINDOW_WIDTH / PHYSICS_SCALE) / (2.0 * BALL_RADIUS)).floor() as u32;237 }238239 let ceiling_width: f32;240 #[allow(clippy::cast_precision_loss)]241 {242 ceiling_width = max_balls as f32 * BALL_RADIUS * 2.0;243 }244 create_ceiling(ceiling_width, max_balls, &mut collider_set);245246 // gap at left and right edge of window247 let gap = 0.5 * ((WINDOW_WIDTH / PHYSICS_SCALE) - ceiling_width);248249 // Create the left and right wall250 create_side_walls(gap, &mut collider_set);251252 // Create ball253 let mut normal_distribution = StdRng::from_entropy();254 let mut new_ball = Ball::default();255 let ball_body_handle = create_physics_for_ball(256 &new_ball,257 &mut rigid_body_set,258 &mut collider_set,259 &mut normal_distribution,260 );261 new_ball.physics_handle(ball_body_handle);262 let mut balls: Vec<Ball> = Vec::with_capacity(256);263 balls.push(new_ball);264265 // Create other structures necessary for the simulation266 // positive gravity indicates it is applied upwards (reversed)267 let gravity = vector![0.0, 1.0];268 let integration_parameters = IntegrationParameters::default();269 let mut physics_pipeline = PhysicsPipeline::new();270 let mut island_manager = IslandManager::new();271 let mut broad_phase = BroadPhaseMultiSap::new();272 let mut narrow_phase = NarrowPhase::new();273 let mut impulse_joint_set = ImpulseJointSet::new();274 let mut multibody_joint_set = MultibodyJointSet::new();275 let mut ccd_solver = CCDSolver::new();276 let mut query_pipeline = QueryPipeline::new();277 let physics_hooks = ();278 let (collision_send, collision_recv) = crossbeam::channel::unbounded();279 let (contact_force_send, _contact_force_recv) = crossbeam::channel::unbounded();280 let event_handler = ChannelEventCollector::new(collision_send, contact_force_send);281282 let mut paused = false;283284 // run the game loop, stepping the simulation once per frame.285 loop {286 if is_key_released(KeyCode::Escape) {287 break;288 }289290 clear_background(GUNMETAL);291 draw_balls(&balls);292293 if !paused {294 physics_pipeline.step(295 &gravity,296 &integration_parameters,297 &mut island_manager,298 &mut broad_phase,299 &mut narrow_phase,300 &mut rigid_body_set,301 &mut collider_set,302 &mut impulse_joint_set,303 &mut multibody_joint_set,304 &mut ccd_solver,305 Some(&mut query_pipeline),306 &physics_hooks,307 &event_handler,308 );309310 // update ball positions (used for drawing)311 update_balls(&mut balls, &rigid_body_set);312313 // wait for existing balls to settle before spawning a new one314 if island_manager.active_dynamic_bodies().is_empty() {315 let mut new_ball = Ball::default();316 let ball_body_handle = create_physics_for_ball(317 &new_ball,318 &mut rigid_body_set,319 &mut collider_set,320 &mut normal_distribution,321 );322 new_ball.physics_handle(ball_body_handle);323 balls.push(new_ball);324 }325326 // end simulation if a ball touches the ground327 while let Ok(collision_event) = collision_recv.try_recv() {328 if let CollisionEvent::Started(329 _collider_handle_1,330 _collider_handle_2,331 CollisionEventFlags::SENSOR,332 ) = collision_event333 {334 paused = true;335 };336 }337 }338 next_frame().await;339 }340 }
🥊 Placing Colliders #
You can use a translation to place a collider, like we did in the hello world example. You might also use the rotation method on the collider builder, to set the collider’s rotation in radians. If you want to set both, like we do for the left and right walls, then you can pass an isometry to the position method:
ColliderBuilder::cuboid(0.5 * WINDOW_HEIGHT / PHYSICS_SCALE, collider_half_thickness).position(Isometry::new(vector![ translation_x, translation_y ],std::f32::consts::FRAC_PI_2,)).build();
The position
method sets translation and rotation and will override
those values if you wet them individually.
🪚 Height Field #
A height field is a convenient and efficient way of creating a piecewise linear collider, like the saw-tooth-shaped collider at the top of the window. I use it there, as a kind of egg carton, to give the balls a natural resting position.
The height field has vertices at regular intervals along the x-axis, and you pass in the heights of the vertices (from the x-axis) in a vector:
let heights = DVector::from_fn(nsubdivs + 1, |i, _| if i % 2 == 0 { -1.2 } else { 0.0 });let collider =ColliderBuilder::heightfield(heights, vector![ceiling_width, collider_half_thickness]).build();
Here we have a zigzag height field, which has -1.2
height for even-indexed
vertices and 0.0
for odd ones.
🏝️ Island Manager #
The island manager is a register of dynamic bodies, which we can query to get a collection of all active dynamic bodies at each step:
if island_manager.active_dynamic_bodies().is_empty() {// spawn new ball// ... TRUNCATEDballs.push(new_ball);}
In this case, we want all existing balls to settle before spawning a fresh on, so check the collection of active dynamic bodies is empty.
🛫 Ground Collision Sensor & Event Handler #
We set up the event handler in lines 280
– 282
:
let (collision_send, collision_recv) = crossbeam::channel::unbounded();let (contact_force_send, _contact_force_recv) = crossbeam::channel::unbounded();let event_handler = ChannelEventCollector::new(collision_send, contact_force_send);
Then we can loop though collision events to check if any collisions involved a sensor:
while let Ok(collision_event) = collision_recv.try_recv() {if let CollisionEvent::Started(_collider_handle_1,_collider_handle_2,CollisionEventFlags::SENSOR,) = collision_event{paused = true;};}
Since the ground is the only sensor, there is no need to check collision handles, we can just go
ahead and pause the simulation. As well as CollisionEvent::Started
, there is CollisionEvent::Stopped
, which might be useful in
other scenarios.
Running the Bubbles Example #
To run the example, use the following command in the Terminal:
cargo run --example bubbles
Please enable JavaScript to watch the video 📼
🏁 Rapier Physics with Macroquad Simulation: Next Steps #
I was impressed how quickly I could get going using Rapier physics with Macroquad, that said, it is worth spending a little more time to remove some rough corners. I might develop this into a game, like the Bubble Trouble arcade game. For that it would be worth:
- nailing down the physics, so the balls do not roll when they hit the ceiling,
- making the balls “stickier”, so other balls cannot knock settled balls out of position; and
- letting the ceiling drop over time, perhaps by applying an impulse, to make the game a little harder.
I might also preserve a version, as is. I find it so relaxing! It is more satisfying than doom-scrolling social feeds 😄.
Interested to know if you might play around with the demo a little and what improvements you decide on.
🗳 Poll #
🙌🏽 Rapier Physics with Macroquad: Wrapping Up #
In this Rapier Physics with Macroquad post, we got an introduction to working with Macroquad and Rapier. In particular, we saw:
- how to configure Rapier with Macroquad;
- porting the Rapier Getting Started code to Macroquad; and
- more advanced Rapier features such as height fields, event handlers and the Island Manager .
I hope you found this useful. As promised, you can get the full project code on the Rodney Lab GitHub repo . I would love to hear from you, if you are also new to Rust game development. Do you have alternative resources you found useful? How will you use this code in your own projects?
🙏🏽 Rapier Physics with Macroquad: Feedback #
If you have found this post useful, see links below for further related content on this site. Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on X, giving me a mention, so I can see what you did. Finally, be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please consider supporting me through Buy me a Coffee.
Just dropped a new blog post on how you can use Rapier with Macroquad for fast game prototyping.
— Rodney (@askRodney) May 1, 2024
We start with a hello world ball drop, then level up to a floating ball example with a Rapier height field.
Hope you find it useful!
https://t.co/kJ3fHi7Gjo #askRodney
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on X (previously Twitter) and also, join the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Game Dev as well as Rust and C++ (among other topics). Also, subscribe to the newsletter to keep up-to-date with our latest projects.