O'Reilly    
 Published on O'Reilly (http://oreilly.com/)
 See this if you're having trouble printing code examples


Creating Games in Ruby (Part 1)

by Andrea O. K. Wright
12/11/2007

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

Because it is so expressive and flexible, Ruby can make some of the most mundane tasks not only easy, but fun. With game development frameworks handling the low-level details, something like writing game logic, that is inherently fun, can be especially enjoyable with Ruby.

But can playing games written in Ruby be as much fun as writing them, or do they run too slowly, with too many breaks in the action at inopportune times? Is Ruby a legitimate player in the gaming space?

The video clips and screenshots of Ruby-based games and special effects that are embedded within this two-part series provide a partial answer to these questions: you can have a lot of fun playing games written in Ruby!

I understand that Ruby's garbage collection policy (basically, everything stops during gc) has given some would-be Ruby game developers pause, and that others have been deterred by Ruby's absence from the commercial video game market. I will address both of those concerns at the conclusion of the second part in this series. Please consider suspending disbelief and forgetting about those issues until then.

The bulk of this series is a survey of resources for building 2D and 3D games with Ruby. It's not a tutorial, but I hope to give you a sense of what's involved in writing video games. It's an overview of what Ruby has to offer for video game developers.

Ruby/SDL - Lead Developer and Creator: Ohai

Ruby/SDL is a Ruby/C extension for SDL, an open source, cross-platform library that provides multimedia support for applications. SDL stands for Simple Directmedia Layer. Like most of the libraries I'm going to cover, Ruby/SDL offers sound and video integration, but for the most part I'm not going to discuss those facets of game development in this series. I'm going to focus on graphics.

I'm starting with Ruby/SDL because it is the lowest level library on my list, and we can look at concepts that apply to most of the other frameworks right in the source code for the sample applications that are packaged with Ruby/SDL. A number of these ideas are encapsulated deep in the framework code for the other libraries.

SDL was developed in C. Making C library functions available to Ruby developers involves using the Ruby C API and wrapping the original C function in ways that account for the differences between the two languages.

Sometimes the original C doesn't require much in the way of special handling to to be callable from the Ruby side. That's the case for SDL_getTicks, which returns how long the program has been running, in milliseconds. Here's that method's signature from the C-based SDL library:

Uint32 SDL_GetTicks(void);

And here is the Ruby/SDL code that wraps SDL_getTicks:

static VALUE sdl_getTicks(VALUE mod) 
{
  return UINT2NUM(SDL_GetTicks());
}

The UINT2NUM macro is used to convert the C return type to a Ruby Num for consumption by the Ruby caller. Also, notice that even though the C function doesn't take any arguments, the corresponding wrapper takes one parameter. Not being object-oriented, C wouldn't know what to call the function on if it was not passed in.

The wrapper also needs to be referenced in the Ruby C extension's initialization routine in order to be callable from the Ruby side. Here is how that wrapper is referenced in the Ruby/SDL initialization code:

rb_define_module_function(mSDL, "getTicks", sdl_getTicks,0);

The rb_define_module_function method maps the C extension code to a corresponding method name the Ruby programmer can use. In this case the wrapper, sdl_getTicks, is mapped to getTicks. Why go against Ruby naming conventions and use camel-case? There are developers who like to use the naming conventions of a wrapped library when using thin wrappers around that library's functions. For those who prefer to use Ruby-style method names with underscores, get_ticks is defined in the Ruby/SDL library with alias 'get_ticks' 'getTicks'.

Ruby/SDL is not packaged with a tutorial, but it's easy to get started by reading through the code in the sample applications that come with the library. There's one game, a very basic version of Tetris (Figure 1), but the rest of the samples each demonstrate one particular effect or technique. For example, one sample does nothing other than display one small red octagon, the same image that constitutes one of the Tetris blocks, alternately fading in the image, and then fading it back out. To demonstrate collision detection and handling, there's a sample that shows several red octagons moving about randomly and "bouncing" off each other by changing direction when they collide. Another sample features rudimentary event handling: several red octagons move around randomly, except for the one in the middle, which is wired to move up, down, left, or right depending on which of the arrow keys you press. Those three basic samples represent a good chunk of the functionality you would need to create a wide variety of more complex games.

Tetris demo packaged with Ruby/SDL
Figure 1. Tetris demo packaged with Ruby/SDL

I'm going to walk through the some of the code for the simple event handling demo, movesp.rb, and then show how the code for a feature-rich Asteroids-like game built with Ruby/SDL is structured in a similar manner.

Here's the code that creates all the red octagons:

sprites = []
for i in 1..5
  sprites << Sprite.new
end
sprites << MovableSp.new

Sprite is the class that is responsible for octagons that move randomly and MovableSp (short for "Movable Sprite") is the class responsible for the controllable octagon in the middle. A sprites array is created to hold all of the sprites. When I first started getting interested in video games, I thought a sprite was a stock woodland elf in a fantasy game. I'd see it used in contexts like this SDL game and wonder if the Sprites weren't supposed to represent elves dancing around. It turns out that "sprite" is a standard computer graphics term for a 2D object whose position typically changes between frames.

Here's the class definition for the random motion sprites, with ellipses where I removed most of the "y code" that mirrors the "x code:"

class Sprite
  def initialize
    @x=rand(640)
    ...
    @dx=rand(11)-5
  end
  
  def move
    @x += @dx
    if @x >= 640 then
      @dx *= -1
      @x = 639
    end
    if @x < 0 then
      @dx *= -1
      @x = 0
    end
    ...
  end
  
  def draw(screen)
    SDL.blitSurface($image,0,0,32,32,screen,@x,@y)
  end
end

In initialize, the Sprite's initial x and y coordinates as well as @dx and @dy, which represent how much the Sprite will move along the x axis and the y axis for each frame, are set randomly. The move method assigns values to the x and y coordinates by adding the @dx and @dy values. When the Sprite reaches the edge of the screen (when the x coordinate is >= 640) , @dx is multiplied by -1 to make the Sprite change direction. SDL.blitSurface, called in draw, copies the image of the red octagon to the display surface.

Here's the class definition for the sprite in the middle, the MovableSp:

class MovableSp
  def initialize()
    @ud=@lr=0;
  end
  
  def move()
    @ud=@lr=0;
        @lr=-1 if SDL::Key.press?(SDL::Key::H)
          or SDL::Key.press?(SDL::Key::LEFT)
    @lr=1  if SDL::Key.press?(SDL::Key::L)
      or SDL::Key.press?(SDL::Key::RIGHT)
    @ud=1  if SDL::Key.press?(SDL::Key::J)
      or SDL::Key.press?(SDL::Key::DOWN)
    @ud=-1 if SDL::Key.press?(SDL::Key::K)
      or SDL::Key.press?(SDL::Key::UP)
  end
  
  def draw(screen)
    SDL.blitSurface($image, 0, 0, 32, 32, screen,
      300+@lr*50,200+@ud*50)
  end
end

When move is called, it initializes both @ud (the up/down factor) and @lr (left/right factor) to 0 and then sets the values of @ud and @lr depending on which arrow key was pressed, if any. In draw, the last two parameters represent the new x and y coordinates for the MovableSp. When both factors are zero, the sprite is drawn at the center of the screen. Otherwise the MoveableSp moves 50 pixels or negative 50 pixels from the center in the specified direction.

Here's the code that sets up the game loop and event queue processor:

while true
  while event = SDL::Event2.poll
    case event
    when SDL::Event2::Quit
      exit
    when SDL::Event2::KeyDown
      exit if event.sym == SDL::Key::ESCAPE
    end
  end
  
  screen.fillRect(0,0,640,480,0)
  SDL::Key.scan
  
  sprites.each {|i|
    i.move
    i.draw(screen)
  }
  screen.updateRect(0,0,0,0)
end

This event loop closes the app if the user hits the escape key or clicks the "x" (an SDL::Event2::Quit event is generated when the user clicks on "x" button on the game window title bar). In each frame, move and draw are called on each member of sprites. Then the screen is updated by virtue of the updateRect call. This is a pattern you will see over and over in the rest of this series: sprites determine if and how they need to move or change their appearance, they are copied to the display surface, and then the display gets updated to reflect the changes.

Here's a video clip of that Asteroids-like game I mentioned, Steven Davidovitz's Nebular Gauntlet:

Nebular Gauntlet is a great resource for learning about SDL techniques, like scrolling the background and using a particle engine to simulate rocket fire. It's still a work in progress, but there are already a lot of features in place. You can save games, and it comes with a map editor that lets you specify where the shields and bots should be placed in point and click fashion. Bots are programmed to chase the spaceship; it's a bot that's pursuing the player's spaceship in the clip. There is code to determine if you have completed your mission (passed through certain points on the screen), and code to advance you through multiple levels. As you watch the video, notice the little radar area in the upper left-hand corner that tracks the action on the screen.

The main event loop that drives Nebular Gauntlet is not too different from the loop that controls movesp.rb. It evaluates input, determines if the ship or other entities need to change position and repositions them, and then updates the display on the screen. Below is the main event loop for Nebular Gauntlet, followed by a few of the methods it calls. I did not show the code for do_think and do_render in favor of showing the code for think and render, which are called by do_think and do_render respectively. It's do_render that takes care of updating the entire screen, after calling render.

def start
  ...
  # Main loop; Loop until quit
  while !@@quit
    handle_input() # Handle keyboard input
    do_think() # Do calculations/thinking
    do_render() # Render everything
    ...
  end
end

def think(elapsedtime)
  return if elapsedtime > 100
  $map.check_objs
  ...
  $entities.move(elapsedtime)
  $entities.collide_with($fires)
  $entities.collide_with($ship)
  $entities.collide_with($entities)
  $entities.collide_with($map)
  ...
end

def render      
  $map.draw
  $ship.draw
  ...
  $camera.draw
  $fires.draw
  $entities.draw
  @console.draw
  @interface.draw
  ...
end

RUDL
Creator: Danny Van Bruggen

There has been no active development on the RUDL project for a few years, but it's a good source of ideas, and it's packaged with some interesting resources.

RUDL was created to provide a way to use SDL with more conventional Ruby syntax and style than you will find in Ruby/SDL and also to minimize the amount of boilerplate code the developer has to write, like initialization code.

The RUDL wrapper for SDL's SDL_setVideoMode exemplifies RUDL's approach to meeting those goals. The SDL_setVideoMode method must be called in every program that uses SDL for graphics. It creates the display surface that the images will appear on. You can pass it a flag to specify which rendering system to use: SWSURFACE or software surface, HWSURFACE or hardware surface or OPENGL. It's usually best to avoid HWSURFACE. Video drivers are not all optimized in a way that complements SDL games, and hardware acceleration is not an option on all platforms. As for OpenGL, it's probably the fastest option, but it's not very well integrated with either Ruby/SDL or RUDL. If you select it as your rendering engine, you need to use the OpenGL API for drawing, which is not a hard API to understand, but can be somewhat cumbersome. We will look at the OpenGL API a little later, after we look at how SDL_setVideoMode is encapsulated in RUDL.

Here's the signature for SDL_setVideoMode in the original SDL library:

SDL_Surface *SDL_SetVideoMode(int width, int height,
                              int bpp,Uint32 flags);

RUDL's wrapper for SDL_setVideoMode is DisplaySurface.new. RUDL creator Danny Van Bruggen felt that it violated the principle of least surprise for a method that creates a new display surface and returns it to be called SDL_setVideoMode. He thought you would expect a DisplaySurface to be returned if you create new DisplaySurface. He also didn't think it was necessary to pass 4 parameters to it, when the third parameter represents bits per pixel and is likely to be set at 16, and the last parameter represents the rendering system, which is likely to be SWSURFACE.

Below is a sample call to RUDL's SDL_setVideoMode wrapper (DisplaySurface.new), as compared to a sample call to Ruby/SDL's much more literal SDL_setVideoMode wrapper (setVideoMode):

# sample call to RUDL's setVideoMode wrapper
display = DisplaySurface.new([640,480])
# sample call to Ruby/SDL's setVideoMode wrapper
display = SDL::setVideoMode(640,480,16,SDL::SWSURFACE)

Below are excerpts from the RUDL code that wraps SDL_SetVideoMode, and the code that maps DisplaySurface.new to that wrapper when RUDL is initialized. The ellipsis is a place holder for the many things that happen in that wrapper, like evaluating which of the optional parameters were passed in and the pyrotechnics necessary to make the C extension wrapper behave like a constructor for the DisplaySurface class. Sometimes instead of putting a lot of code in the C extension files, developers create thin wrappers for the C library functions and then write a pure Ruby library to make the API more elegant and add functionality.

static VALUE displaySurface_new(int argc, VALUE* argv, VALUE self)
{
  ...
  surf = SDL_SetVideoMode(w, h, depth, flags);
  currentDisplaySurface =
    Data_Wrap_Struct (classDisplaySurface, 0, 0, surf);
  return currentDisplaySurface;
})
rb_define_singleton_method(classDisplaySurface,
  "new", displaySurface_new, -1);

Some of the most useful resources packaged with RUDL are Martin Stannard's C to Ruby translations of the popular Neon Helium OpenGL tutorials. These ports use OpenGL as the rendering system, but use RUDL for everything else, like event handling. Since we're talking about RUDL, it makes sense to show what the OpenGL API looks like at this point.

Below is a screenshot of a sample that creates a couple of two-dimensional primitives followed by the code that renders the triangle. Since OpenGL is a 3D rendering system, points are represented by vertexes, which have an x coordinate, a y coordinate, and a z coordinate. The z axis goes from front to back. 0 is on the screen surface. If you're sitting in front of a terminal, front pops out of the screen towards you and back is going back into virtual space behind the screen. To make an object appear to go into the screen you decrease its z value.

A 2D scene, like this, can be created with OpenGL by always setting the z coordinate to 0. Setting the triangle's three vertexes requires three GL.Vertex calls, and setting a color for each vertex requires three GL.Color calls. Drawing the triangle requires GL.Begin and GL.End to group those vertexes.

Creating a 3D pyramid would require three times as many GL::Vertex calls to define the three surfaces. To display an image instead of coloring the shape, you would use GL.BindTexture and then you would use GL.TexCoord calls in lieu of the GL.Color calls.

RUDL port of Neon Helium OpenGL tutorial
Figure 2. A Neon Helium OpenGL tutorial ported to RUDL by Martin Stannard

display = Proc.new {
  GL.Clear(GL::COLOR_BUFFER_BIT|  
           GL::DEPTH_BUFFER_BIT)        
  GL.Color(1.0, 1.0, 1.0)
  GL.LoadIdentity()
  GL.Translate(-1.5, 0.0, -6.0)         
  # draw a triangle
  GL.Begin(GL::POLYGON)                    
  GL.Color3f(1.0, 0.0, 0.0)             
  GL.Vertex3f(0.0, 1.0, 0.0)            
  GL.Color3f(0.0, 1.0, 0.0)             
  GL.Vertex3f(1.0,-1.0, 0.0)            
  GL.Color3f(0.0, 0.0, 1.0)             
  GL.Vertex3f(-1.0,-1.0, 0.0)           
  GL.End()                                              
  GL.Translate(3.0,0.0,0.0)             
}

Rubygame
Creator and Lead Developer: John Croisant

Rubygame is a full-featured, high-level game development library that also exposes its lower-level wrappers around SDL's C API. You can jump start your project using Rubygame's convenience methods and helper classes, and you can access SDL functions through Rubygame on an a la carte basis to tweak your code.

Rubygame was initially modeled after the popular Python-based framework, Pygame, and its name reflects that. According to John Croisant, the creator of Rubygame, he chose Pygame because it was the best game development framework he knew about at the time. Over time, even the features that come closest to being direct ports from Pygame have become more distinctly Ruby-tinged in the way they are implemented.

Rubygame 3.0, the next major release of the framework, will be a radical departure from Pygame. John is working to ensure that older games will run with the new system, though some adjustments may be required.

Before I go into some of what's in store with Rubygame 3.0, I want to give you a feel for what it means for a game engine to be Pygame-like by looking at one of the samples packaged with Rubygame 2.1.0, the current version of Rubygame.

This Punch the Chimp game, which resembles a banner ad, is a direct port from a Pygame tutorial. The Rubygame and Pygame APIs are similar enough that it's very easy to follow along in the Rubygame code while going through the step-by-step Pygame tutorial. You move the fist with the cursor and throw your punch by clicking the mouse button. If you make contact, the chimp spins.

Punch the Chimp, packaged with Rubygame
Figure 3. Punch the Chimp game packaged with Rubygame

Rect, as in Rectangle, is one of the main classes in Pygame, and likewise one of the main classes in Rubygame 2.x. Rects are typically paired with sprites and are usually based on the dimensions of the image that represents the sprite. Below is the bitmap that represents the chimp in the game. The red background color does not show up when the game runs. Like most game frameworks, Rubygame allows the user to specify a color that should not be rendered when a particular image is displayed. Without the background, the chimp cuts a fine figure on the screen.

Chimp bitmap used for Punch the Chimp
Figure 4. Chimp bitmap used for Punch the Chimp

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)
end

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 = GLImageSprite.new {
  @surface = Surface.load_image('ruby.png')
  setup_texture()
  @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):

Video thumbnail. Click to play this video clip.
Click to play this 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.

Resources

GGZ Gaming Zone
Gosu
Nebular Gauntlet
Neon Helium OpenGL Tutorials
Rubygame
Ruby/SDL
RUDL
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.

Copyright © 2009 O'Reilly Media, Inc.