Creating Games in Ruby (Part 1)
Pages: 1, 2, 3

Rects can be used to move sprites. You can position a sprite by setting any of the attributes like midleft or bottom (represented by circles or dashed lines in the diagram below) on the sprite's Rect. When the Punch the Chimp game starts, @rect.topleft = 10,10 is used to position the chimp. In order to make the fist follow the mouse, the fist's Rect's midtop value is set to match the mouse coordinates every frame.

Rect attributes
Figure 5. Rect attributes

The Rect class also provides collision detection services and a lot of utility methods, like clamp, which puts a Rect right inside another Rect, and inflate, which can make a Rect grow or shrink depending on whether you pass in positive or negative numbers. Here is the code that determines whether the fist has made contact. The test is made with a smaller version of the fist's Rect, made by deflating it (or inflating it with negative parameters), to ensure that a punch only registers if the fist hits its target squarely.

# Attempt to punch a target. 
# Returns true if it hit or false if not.
def punch(target)
  @punching = true
  return @rect.inflate(-5,-5).collide_rect?(target.rect)

Another concept that is central to Pygame programming is the idea of a sprite group. In Rubygame, the Sprites::Group class is based on Pygame's sprite.Group.

Sprite groups handle bulk actions for their constituent sprites, including drawing, updating, and collision detection. In Punch the Chimp, the fist and the chimp both belong to the same sprite group, but in real games, the sprites in a scene usually don't all belong to the same sprite group. Games can be organized around sprite groups. For example, there can be different sprite groups for different teams. SpriteGroups are easily extensible.

When we looked at the Ruby/SDL sample code, we saw that simple group updates can be achieved by putting all the sprites in an array and looping through the array to redraw each sprite every frame. So what makes sprite groups so special? One example of a useful feature, which is available if you mix in Rubygame's UpdateGroup module or use Pygame's RenderUpdate module, is that they can keep track of the Rects that were repositioned since the last update and only redisplay those.

Now we'll look at some of what's going on in Rubygame's development branch: Edge Rubygame. One of the major changes is the new scene management system with tight OpenGL integration, a new event handling mechanism, and a positioning system that is very different from the Pygame-like Rect-based one.

Here are a couple of screenshots of a demo from the Rubygame 3.0 development branch. The big panda follows the mouse. When you click on the screen both the panda and the ruby jump to the cursor. When the panda and the ruby collide, they turn red. A miniature version of the scene plays out in the picture-within-a-picture in the upper right-hand corner.

Rubygame demo
Figure 6. Demo packaged with Rubygame's 3.0.0 development branch: no collision detected

Rubygame demo
Figure 7. Demo packaged with Rubygame's 3.0.0 development branch: collision detected

Below is code that shows how to display sprites in the new system. It's the code the displays the picture of a ruby in the demo. Behind the scenes it's using the same sort of bulky sequence of OpenGL API calls we looked at in the RUDL samples, but here they are wrapped with a single setup_texture call. The position is set using two coordinates, but behind the scenes, the framework is using OpenGL's 3D positioning system with the z coordinate set to 0. There's tight OpenGL integration in the Rubygame 3.0 branch, but John is committed to making the new system work without requiring a 3D graphics card. There will be an alternative implementation of the new scene management framework that will not require OpenGL.

ruby = {
  @surface = Surface.load_image('ruby.png')
  @pos = Vector2[100,300]
  @depth = -0.1
  @angle = -0.2

The Rubygame picture-in-a-picture may remind you of the little radar screen we looked at in Nebular Gauntlet but the implementations are entirely different.

Rubygame's picture-in-a-picture and the Nebular Gauntlet's radar screen
Figure 8. Nebular Gauntlet's radar screen

In Nebular Gauntlet the scaled-down version is achieved by drawing a small shape to represent each spaceship or shield. The code that's responsible for the radar area loops through all the ships, bots and shields and scales a proportional model of the action by dividing the x and y coordinate of each object by a size modifier, which is set as 15 in the application initialization code. Then it draws a white circle to represent each one.

In the Rubygame 3.0 demo app, the window in the upper right is showing another view of the scene by virtue of a virtual camera with a perspective that differs from the scene manager's default perspective.

You can think of a virtual software camera as being similar to a cell-phone camera in that both involve focusing on a region of the world and projecting it onto a two-dimensional screen. If the world region is defined to match the default camera's world region, but its screen region is smaller, as it is in this case, the figures appear diminished. There's no application code that draws a second panda or ruby, like the white circles that have to be drawn on the radar screen in Nebular Gauntlet. The second camera just had to be added using add_camera.

A release date for Rubygame 3.0 has not been scheduled yet. There are still a lot of ideas that John would like to incorporate into it.

He has ideas that push the envelope of game development that you can read about in the comments in the Rubygame code and also in his blog. He recently wrote about why the RGB color model is inadequate for rendering a scene in the middle of a tunnel lit with yellow. He suggests that many developers would just tint everything in the scene yellow, and considers what might be involved in making the scene more true-to-life. In real life, the limited spectrum emitted by the lights in the tunnel would make a red car appear to be a dark yellow-orange or a blue car appear nearly black.

Building Games with Ruby (Part Two) Preview

The second part of this series will cover Gosu, a high-level 2D game development framework. I will detail the techniques used to make the landscape dynamic in this game that is packaged with Gosu (click the image to view a video clip):

Part Two will also feature Shattered Ruby, a 3D game development framework inspired by Ruby on Rails, and the GGZ Gaming Zone project, which promotes networked gaming and has recently bolstered its support for Ruby.


GGZ Gaming Zone
Nebular Gauntlet
Neon Helium OpenGL Tutorials
Shattered Ruby

Editor's Note: Read more about creating games in Ruby in Part 2 of this article.

Andrea O. K. Wright enjoys organizing weekly Ruby Tuesday tech lunch-and-learns for her colleagues at Chariot Solutions, a consulting firm based in Fort Washington, PA. She has given presentations about developing games with Ruby at several conferences, including RubyConf 2007.

Return to O'Reilly Ruby.