A tutorial contributed by Dirk Johnson.
This tutorial covers integrating the Chipmunk Physics Engine's Ruby bindings with Gosu's Ruby bindings. This tutorial assumes that both Gosu and Chipmunk have been properly installed. See http://wiki.slembcke.net/main/published/Chipmunk. The source code for this tutorial can be found in Gosu's 'examples' folder, but to run it, you'll need to copy the chipmunk extension there too if you haven't installed it system-wide.
New, better documented bindings to Chipmunk 5.3.4 are available at http://github.com/beoran/chipmunk. Check there too for the documentation. --Beoran.
Pre-compiled Chipmunk libraries are available in (this forum thread)[http://www.libgosu.org/cgi-bin/mwf/topic_show.pl?tid=82]. Thanks, bentglasstube!
Lately, I have been writing a game I have been threatening to write on-and-off for a long time. I am, of course, using Ruby as the implementation language and Gosu as the animation layer. One thing I faced early on was implementing the physics of the game; following one of my coding mantras, I took the approach of "get it to work first, worry about optimizing later". While the performance I saw was basically adequate for the scope of version 0.1, I could see it would start to bog down fairly quickly going forward. As it happens, I showed Julian an early copy of my work and he pointed me to Chipmunk to handle the physics.
Can I just say, Chipmunk is awesome: it is awesome because it is fast and it is simple to use. And it is really awesome because, like Gosu, it supports both Mac OS X and Ruby! Not to put too fine a point on it, in its current iteration (as of this writing, version 3), Chipmunk is a 2D, Euler space, rigid body, physics engine. This is quite a mouthful, and, for the most part, all y'all need worry about is "2D" which means it will mesh quite well with Gosu.
After looking at the Chipmunk framework, one thing I noticed was that it basically assumes that you are working with a profile oriented environment where down (the direction of gravity) is towards the bottom of the screen. What this means is that you can exploit all Chipmunk offers (e.g., gravity, friction, collision detection, etc.) for your side-scrolling games, however, top-down games, where down is out the back of the display, might limit what Chipmunk can do for you, or, at least, render it more difficult to use.
Of course, my game is top-down.
So, that left me with discovering just what Chipmunk v3 could do for me. I could have started the discovery process by ripping apart my code and attempting to integrate Chipmunk back in, but I decided to take a much simpler first step... take the top-down Tutorial for Gosu and integrate Chipmunk into that. I even thought I would do it one better and document the conversion as a sort-of tutorial; thus what you are reading now.
Let us begin...
Lets look at the Tutorial's Player class. Notice it has instance variables for tracking what I call the universal attributes of a game object:
With Chipmunk, your game objects will no longer track your universal attributes, instead, your game objects will have a Chipmunk Body (sometimes called a rigid body) instance (CP::Body), which will track your universal attributes for you. This is a good thing. Now your game objects can focus on what makes them unique.
In the Tutorial, collision analysis was done by the Player object, however, when you really think about it, determining whether one object collides with another object is really the purview of the universe in which your game objects live. Luckily, Chipmunk provides us a "universe" object called Space (CP::Space). Space is responsible for defining gravity and damping as well as keeping track of the Bodies and Shapes that exist within. (Note: you can have none, one, or many Spaces in your game, easily allowing variable forces on your game objects, however, this tutorial assumes one Space.)
The astute student will have noticed I snuck in a new term in the last paragraph: Shape. While the Body is used to track universal attributes, the Shape (CP::Shape) is used to define the physical boundaries of those attributes. Since a Shape is defined as being physical, it is primarily used for collision detection. As one might guess, a Shape is pretty useless without a Body, therefore, each Shape has a Body. This means that our game objects need only have a single reference to a Shape and they automatically get a Body, too! It should be noted that your Shapes need to be convex shapes, which means all internal angles are less than or equal to 180 degrees. Visually, this means no dimples in the shape. If the game object being defined is more complex than a single convex shape can define, then a game object can have more than one Shape, that, when pieced together, form the overall shape of the object. In this case, each Shape should point to the same Body. (To me it is a bit awkward to have the Shape own the Body, considering that a game object could have more than one Shape but only need a single Body - rather, I would have the Body own the Shape(s). This is, however, a minor detail and easily worked around.)
In order to detect "collisions", the Tutorial cycled through every Star every update and judged the distance between the Player and the Stars; if the distance was less than a threshold (in this case, 35), then there was a collision and the Star was removed and the score increased by 10. Now that game objects have Shapes, we can let Space do this work for us. One might ask, "How do we tell Space to increment the score and remove the Star when there is a collision?" I'm glad you asked, and the answer is "Ruby blocks!" Space allows us to define blocks of code (sometimes called closures) that will be called when a Player collides with a Star (or, more generically, when one collision type collides with another collision type). This allows us to increment the score and remove the Star from Space automagically! Pretty cool.
Go ahead and run the ChipmunkIntegration.rb; it is located at the end of this tutorial. The ship (Player) performs like I like the ship to perform. You may prefer a different feel. Notice I added a "reverse" (the down button). When you are through getting a feel for where we are heading, lets get our hands dirty.
(Note: The code at the end of this tutorial is commented and explains things this tutorial does not mention so be sure to read the comments in the code as well.)
Now that we understand the Chipmunk concepts that we need to apply to our game model, lets update the Player class by removing the following:
Now that we have ripped out the undesirable characteristics of our Player, lets rebuild it in our new, Chipmunkified image. Following along in the code may help:
Lets next update the Star class. This is actually quite trivial:
See, that wasn't so bad, was it? And, we are half way there!
Initializing the environment properly is key to the whole physics engine working well. Lets take this one step at a time by starting from scratch. Referring to the code at the end of this tutorial will make things a lot clearer as you read:
We are now ready to set up the rules for a collision between a :ship and a :star. There is one critical thing to be aware of. Within the block of code representing the rules for a collision, you are not allowed to modify the contents of Space. This means you must keep track of what you want to modify and then modify away after the block of code has completed executing. For this purpose, as we want to remove Stars from Space, we will create a @remove_shapes instance variable.
What may not be obvious is that for things to work properly in our game, we need to define a second collision block in the case of one :star hitting :another. You might ask how this could happen. I'm glad you asked. The way it could happen is that between the time that a :ship collides with a :star and the time the Star is removed from Space, the Star travels and may collide with another Star, thus moving that Star. Since our Stars are intended to be stationary, we need to tell Space to ignore collisions between two :stars. We do this by passing a nil block (&nil).
Wow, we are done initializing our physics environment. We are almost through with the conversion! This is pretty fun, huh? Onward.
The next step is to integrate stepping Space with the Gosu::Window update. One thing to note about the Chipmunk engine is you can lose collisions, meaning Shapes that should have been reported as a collide can go unreported when one or both of those Shapes have a large momentum. The reason is that between steps the Shapes can travel so far as to pass each-other. The trick, then, is to step Space with a small enough increment that you make such a miss highly unlikely. In the Tutorial below, I have added a SUBSTEPS constant which lists how many times Space should be stepped per Gosu::Window update. This may be adjusted as needed. I personally like the feel of the game when SUBSTEPS is 6. In the Gosu::Window update method, do the following:
Congratulations! You have converted the Gosu Tutorial to using Chipmunk! And, along the way, you should have been armed with most of what you need to do the same to your games. I hope you enjoyed this as much as I did, and I hope you make some awesome stuff with Gosu and Chipmunk (ChipSu?).
As of this writing, Chipmunk's Ruby bindings documentation is out of date. On the Chipmunk forums, the author, Scott Lembcke, has said he will be updating the docs and will "soon" release version 4. In the meantime, scour the C documentation, the code, and the forums. I will do my best to update this tutorial for version 4 when it is released.