advertisement

Print

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

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

Pages: 1, 2, 3

Next Pagearrow