advertisement

Print

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.

Pages: 1, 2, 3

Next Pagearrow