[ LiB ] | ![]() ![]() |
SDL has been a common thread throughout the book, first in Chapter 4 with the Pygame SDL wrapper for Python, and then in Chapter 7 with LuaSDL. It would stand to reason that SDL, being the progressive library that it is, also has its fingers in Ruby.
To use SDL with Ruby, you first need to install the SDL library, which can be found in its entirety at its home page, http://www.libsdl.org.
Once SDL is installed, Ruby needs an interface into SDL; there are several different interfaces to choose from. Most of the interfaces can be found in the Ruby application archive at http://raa.ruby-lang.org/.
To make it a bit easier for Windows users, a bundled SDL package is contained in a nifty executable included on this book's CD; it is called pack-rubysdl.exe, and you can find it on the CD in the Chapter 10 folder. The pack-rubysdl.exe package is distributed under the GNU Public License and, for Win32, includes the following:
Ruby 1.6.4. The Ruby version.
Rubysdl-0.6. The actual SDL package.
Rubywin-0.0.3.2. An IDE for Ruby on Windows platforms.
Rb2exe-0.2. A program for converting Ruby scripts into executable files.
Opengl-0.32. The version of OpenGL.
The package is built with Cygwin and comes with a few SDL samples, including those for using the keyboard and joystick, loading sound files from disk, and manipulating a CD. The package also includes one fairly complete sample game by Ohbayashi Ippei.
The caveat to this bundle is that the documentation and installation are in Japanese. You will not be able to read the install files without the proper Japanese character set installed. This is inconvenient for English speakers, as the install files may look like Figure 10.1, depending on the platform used.
Whether your platform displays the characters correctly or not, choosing the left-hand confirmation button means you agree to install the RubySDL folder and files on your C:\ drive (see Figure 10.2).
RubyWin is one of the big bonuses in this package. A GUI developed by Masaki Suketa that bundles Ruby 1.6.4 and Scintilla 1.38 (by Neil Hodgsen), RubyWin creates an environment for running Ruby SDL scripts without having to change or manipulate local environment variables. Launching the executable brings up the RubyWin GUI (see Figure 10.3) and the Run File command, accessible via the Ctrl-R shortcut or through the Ruby menu, can be used to launch and test Ruby SDL applications
The common Ruby SDL modules and classes are listed in Table 10.1.
Component | Module or Class | Description |
---|---|---|
SDL::CD | Class | Represents the CD-ROM drive |
SDL::Error | Class | Error class; handles Ruby/SDL errors |
SDL::Event | Class | Handles events |
SDL::Event2 | Class | Handles events |
SDL::Joystick | Class | Represents a joystick |
SDL::Key | Module | Defines key constants and gets the key state |
SDL::Mixer | Module | Holds sound functions and constants |
SDL::Mixer::Wave | Class | Handles WAV files |
SDL::MPEG | Class | Handles MPEG streams |
SDL::Mouse | Module | Contains mouse constants and functions |
SDL::PixelFormat | Class | Parent to SDL::Surface (obsolete) |
SDL::Screen | Class | Displays the screen image |
SDL::SKK | Class | Handles Japanese input |
SDL::Surface | Class | Contains methods for creating SDL surfaces (images) |
SDL::TTF | Class | Handles TrueType fonts |
SDL::WM | Module | Handles windows |
Ruby SDL includes all sorts of classes for supporting window management, MPEG streaming, joysticks, CD-ROMs, and different fonts. More commonly used are the tools for initializing and SDL environments, creating SDL surfaces, handling events, audio, time, and Japanese character support.
The init module is used to initiate SDL. A flag that triggers which portion of SDL needs to be initialized is included when initializing:
SDL::INIT_AUDIO. Initialize system audio.
SDL::INIT_VIDEO. Initialize system video.
SDL::INIT_CDROM. Initialize the CD-ROM.
SDL::INIT_JOYSTICK. Initialize a joystick device.
The line of code that will initialize video looks like the following:
SDL.init(SDL::INIT_VIDEO)
A particular game's video mode is set with SDL.set_video_mode(), which takes as arguments the width and height of the screen, bits-per-pixel (0=s current or local display), and any necessary flags:
SDL.set_video_mode(640, 480, 0, SDL_FLAG)
Possible flags for SDL.set_video_mode include the following:
SDL::SWSURFACE. Creates video surface in system memory.
SDL::HWSURFACE. Creates video surface in video memory.
SDL::FULLSCREEN. Attempts to use the full screen.
SDL::SDL_DOUBLEBUF. Enables double buffering.
To find out if a particular video mode is supported, there is also an SDL.checkVideoMode() command that uses the same syntax.
After setting up a video mode, SDL::Surface.new will create an empty SDL surface. Its new method also keeps an eye out for several flags:
SDL::SWSURFACE. Creates the surface in system memory.
SDL::HWSURFACE. Creates the surface in video memory.
SDL::SRCALPHA. Chooses the location with the best hardware alpha support.
SDL::SRCOLORKEY. SDL chooses the location with the best hardware colorkey blitting.
Surface.new also needs width, height, and format. The format must be the instance of SDL::Surface and have the same bits per pixel as the specified surface.
There are dozens of methods that can be used on SDL surfaces. Some of the more commonly used ones are listed in Table 10.2.
Method | Equivalent To | Purpose |
---|---|---|
alpha | Returns surface alpha | |
bpp | Return bits per pixel | |
colorkey | Returns surface colorkey | |
drawCircle | draw_circle | Draws a circle |
drawEllipse | draw_ellipse | Draws an ellipse |
DrawFilledCircle | draw_filled_circle | Draws a circle filled with specified color |
drawFilledEllipse | draw_filled_ellipse | Draws an ellipse filled with specified color |
drawLine | draw_line | Draws a line between the given coordinates |
drawRect | draw_rect | Draws a rectangle |
displayFormat | display_format | Makes a copy of itself on a new surface; used for fast blitting |
displayFormatAlpha | display_format_alpha | As displayFormat wtih alpha value per pixel |
fillRect | fill_rect | Fills given rectangle with specified color |
flags | Returns surface flags | |
format | Returns pixel format | |
getClipRect | get_clip_rect | Returns clipping rectangle for the given surface |
getPalette | get_palette | Returns the palette of the specified surface |
GetPixel | get_pixel | Gets color of the specified pixel |
GetRGBget_rgb | Returns RGB component values of specified pixel in an array | |
getRGBA | get_rgba | Like getRGB, but includes an alpha value |
h | Return height | |
load | Loads image (such as a BMP) and returns instance of SDL::Screen | |
loadBMP | load_bmp | Loads given bitmap |
lock | Sets up a surface for directly accessing pixels | |
makeCollisionMap | Creates a collision map | |
mapRGB | map_rgb | Maps the RGB color value to the pixel format of specified surface and returns the pixel value as an integer |
mapRGBA | map_rgba | Same as MapRGB but also includes an alpha value |
mustLock? | must_lock? | Returns true if surface must be locked to directly access pixels |
put | Draw given image in self | |
PutPixel | put_pixel | Writes pixel to the specified position |
rotateScaled Surface | rotate_ scaled_surface | Rotates surface instance with given angle and scale. Note: method is considered obsolete; it's been superceded by transformSurface. |
rotateSurface | rotate_surface | As rotateScaledSurface but scale is set to 1.0 |
saveBMP | save_bmp | Saves file in BMP format |
setAlpha | set_alpha | |
setColorKey | set_color_key | Sets the colorkey of a blit-able surface |
setColors | set_colors | Same as setPalette but with different flags |
setPalette | set_palette | Sets a portion of the palette for the given 8-bit surface |
transformSurface | transform_surface | Creates a rotated and scaled image of given surface |
unlock | Unlocks a surface | |
w | Returns width |
Ruby SDL has two event classes, eventand event2, for handling events. Each has a number of methods; these methods are outlined in Tables 10.3 and 10.4.
Method | Equivalent To | Purpose |
---|---|---|
appState | Event.app_state | Returns current stat.enableUNICODE |
Event.enable_unicode | Enable UNICODE | Keyboard translation (disabled by default) |
Event.disableUNICODE | Event.disable_unicode | Disables Unicode keyboard translation |
Event.enableUNICODE? | Event.enable_unicode? | Returns whether Unicode keyboard translation is enabled |
gain? | Returns true when gaining focus | |
info | Returns event information in an array | |
keyMod | key_mod | Returns the current key modifiers |
keyPress? | key_press? | Returns true when a key is pressed down in a key event |
keySym | key_sym | Returns SDL virtual keysym |
mouseButton | mouse_button | Returns the mouse button index |
mousePress? | mouse_press? | |
mouseX | mouse_x | Returns the x coordinate of the mouse |
mouseXrel | mouse_xrel | Returns the relative mouse motion on the x-axis |
mouseY | mouse_y | Returns the y coordinate of the mouse |
mouseYrel | mouse_yrel | Returns the relative mouse motion on the y-axis |
new | Creates a new SDL::Event object | |
poll | Polls for currently pending events | |
type | Returns the type of a given stored event | |
wait | Waits for the next available event | |
appState | app_state | Returns the kind of ActiveEven |
Method | Equivalent To | Purpose |
---|---|---|
Active | Event that occurs when mouse/keyboard focus gains/loss | |
appState | Event2.app_state | Same as Event.appState |
enableUNICODE | enable_unicode | Same as Event.enableUNICODE |
enableUNICODE? | Event2.enable_unicode? | Same as Event.enableUNICODE? |
disableUNICODE | disable_unicode | Same as Event.disableUNICODE |
JoyAxis | Event that occurs when axis of joystick is moved | |
JoyBall | Event that occurs when a joystick trackball moves | |
JoyButtonDown | Event that occurs when joystick button is pressed | |
JoyButtonUp | Event that occurs when joystick button is released | |
JoyHat | Event that occurs when joystick hat moves | |
KeyDown | Event that occurs when a key is pressed | |
KeyUp | Event that occurs when a key is released | |
MouseButtonDown | Event that occurs when a mouse button is pressed | |
MouseButtonUp | Event that occurs when a mouse button is pressed | |
MouseMotion | Event that occurs when the mouse is moved | |
poll | Same as Event.poll | |
quit | Event that occurs when a quit or exit is requested | |
SysWM | Event that occurs when plaform-dependent window manager occurs | |
VideoResize | Event that occurs when windows are resized | |
wait | Same as Event.wait |
Ruby SDL also has mouse and key classes and methods for mouse and keyboard events; these are outlined in Table 10.5.
Method | Equivalent To | Purpose |
---|---|---|
Key.disableKeyRepeat | Key.disable_key_repeat | Disables key repeat |
Key.enableKeyRepeat | Key.enable_key_repeat | Sets keyboard repeat rate |
Key.getKeyName | Key.get_key_name | Returns the string of key name |
Key.modState | Key.mod_state | Returns the current of the modifier keys |
Key.press? | Return true if given key is pressed | |
Key.scan | Scans key state | |
Mouse.hide | Hides mouse cursor | |
Mouse.setCursor | Mouse.set_cursor | Used to change the mouse cursor |
Mouse.show | Shows a mouse cursor | |
Mouse.state | Returns mouse state in array | |
Mouse.warp | Sets the position of the mouse cursor |
Ruby's SDL has a Mixermodule that is used to serve up music files, change volume, and set up sound effects like fading. Mixerhas a class for handling WAV files, SDL::Mixer::Wave, and a class for loading music, SDL::Mixer::Music. Wave handles standard WAV files, while Music can load mod, S3M, it, XM, MID, and MP3 file formats. Mixer's methods are outlined in Table 10.6.
Method | Equivalent To | Purpose |
---|---|---|
allocateChannels | allocate_channels | Dynamically change the number of channels managed by the mixer |
fadeInMusic | fade_in_music | Fade in the given music in milliseconds |
fadeOutMusic | fade_out_music | Fade out the given music in milliseconds |
halt N/A | Halt playing of a particular channel | |
haltMusic | halt_music | Halt music |
load | Load a music file and return the object of Mixer::Music | |
open | Initialize SDL_mixer | |
play? | Return whether specific channel is playing or not | |
playChannel | play_channel | Play a WAV on a specific channel |
playMusic | play_music | Play music |
playMusic? | play_music? | Return whether the music is playing |
pause | Pause on a particular channel | |
pause? | Return whether a particular channel is paused | |
pauseMusic | pause_music | Pause music |
pauseMusic? | pause_music? | Return whether the music is paused |
resume | Resume a particular channel | |
resumeMusic | resume_music | Resume music |
rewindMusic | rewind_music | Rewind music |
setVolume | set_volume | Set the volume |
setVolumeMusic | set_volume_music | Set volume |
spec | Return the audio spec in array |
SDL uses the notion of ticks to keep 9-track of time. The getTicks/get_ticks method will get the number of milliseconds that have passed since SDL was initialized. There is also a delay method that will wait a given number of milliseconds before returning; it is used to process scheduled jobs and events.
Ruby's SDL comes equipped with an SSK module for encoding the Japanese character set. This module relies on the SDLSSK library, and can set the encoding to the Japanese character system (EUCJP), the ASCII-preserving Unicode system (UTF8), or the Shift-JIS Japanese system (SJIS). SSK has a handful of methods; these are outlined in Table 10.7.
Method | Purpose |
---|---|
Context | Super class that represents the state of input |
Dictionary | Super class for manipulating user dictionaries |
encoding | Returns encoding |
EUCJP | Sets encoding to EUCJP |
Keybind | Represents the keybind in SDLSKK input system |
RomKanaRuleTable | Represents the rule of conversion from alphabet to Japanese kana |
SJIS | Sets encoding to SJIS |
UTF8 | Sets encoding to UTF8 |
All of the tables given in this chapter aren't enoughyou need to try an example of using SDL and Ruby together. In the Chapter 10 section of the accompanying CD is a sample RubyBounce folder with five Ruby files. They are as follows:
CONST.RB
PLAYER.RB
RUBYBOUNCE.RB
STATE.RB
SYSTEM.RB
These five files are explained in the next few subsections. Each has a part to play in setting up a quick SDL Ruby environment where a player manipulates a small bouncing ruby (see Figure 10.4).
This program can be run from the RubyWin application. open up rubywin.exe in your new C:\RUBYSDL\BIN folder, choose the Ruby menu, select Run, and then choose the RUBY-BOUNCE.RB file.
The simplest of the five Ruby files, CONST.RB is used to hold any specific game constants that need to be defined (see Figure 10.5). In this example, the file holds four constants, each of which defines a wall in the playing surface. Changing these values later on changes where the player can travel onscreen:
LWALL_X=40 RWALL_X=600 FLOOR_Y=440 CEIL_Y=60
These values are x- and y-set pixel ranges that define the edges of the playing surface in pixels (see Figure 10.6).
The functions set up in the SYSTEM.RB file should look familiar, as they are similar to the functions you used in earlier chapters. The only difference between the first define, setup_bmp, and earlier endeavors to load bitmaps is Ruby's own unique twist:
def setup_bmp(filename) graph=SDL::Surface.loadBMP(filename) graph.setColorKey SDL::SRCCOLORKEY, graph[0,0] graph=graph.displayFormat end
Here SDL::Surface.loadBMP is used to grab a .BMP file, the colorkey is set with the setColorKey method, and finally, displayFormat is used to display the surface.
Also included in this file are two functions for keeping 9-track of where an object travels in the two-dimensional screen. The x_out function and the send_loc function help determine if the object tries to travel past the LWALL and RWALL constants set in CONST.RB:
def x_out?(x,w) x+w+10<LWALL_X || x-10>RWALL_X end def send_loc?(x,w) return true if LWALL_X+SEND_FIELD_WIDTH>x+w return true if RWALL_X-SEND_FIELD_WIDTH<x false end
Then you define the system class with the initialize and continue_game methods. In a full version game, this would be a good place to set important variables like player score and number of lives, but in this case just one instance variable is set; @life:
class System def initialize @life=3 end def continue_game? @life > 0 end end
There are three classes defined in STATE.RB: State, StateInitializer, and StateDriver. Each is used to keep 9-track of the game state, and each is stored within the jt (just in time) module. The State class has two methods, initialize and move_state. State.initialize is probably the most important method in this script. It first calls the constructor and sets three important instance variables: state_hash, state_driver, and state. Using each variable, State.initialize then sets a loop that iterates over each entry in state_hash:
class State def initialize(first_state) state_initializer = StateInitializer.new yield state_initializer @state_hash = state_initializer.state_hash @state_driver = StateDriver.new(self) @state = first_state @state_hash.each do |key,val| self.instance_eval <<-EOS def self.#{key.id2name}(*arg) if @state_hash[:#{key.id2name}][@state] then @state_hash[:#{key.id2name}][@state].call(@state_driver,*arg) end end EOS end end
The move_state method is used to create new states and assign them to @state:
def move_state(new_state) @state=new_state end
The StateInitializer class defines both initializewhich creates the state_hash instance variableand add_event:
class StateInitializer def initialize @state_hash={} end attr_reader :state_hash def add_event(state,event,&block) if not @state_hash[event] then @state_hash[event]={} end @state_hash[event][state]=block end end
Finally, define the class StateDriver with two methods, initialize and move_state:
class StateDriver def initialize(state_obj) @state_obj=state_obj end def move_state(new_state) @state_obj.move_state(new_state) end end
Now the fun stuffthe player must be defined with a constructor method (initialize). You need methods to display the player onscreen (w, h, and draw) and move around the screen (act and move_lr). But first, the PLAYER.RB file needs help from SYSTEM.RB and STATE.RB:
require 'system.rb' require 'state.rb'
Next, designate the class Playerand define a few player constants:
class Player INIT_DY=-50 DX=20 H=32;W=32 G=20 GRAPH_P1 = setup_bmp 'ruby.bmp'
These constants initialize the height and width and name of the bit-map image of the player piece. After that, call the initialize method. This method not only calls the SYSTEM.RB code but it also establishes keyboard events for moving the player's ruby piece around the screen, including moving left and right and jumping the piece up:
def initialize(system) @system=system @x=320;@y=200 @dy=0 @state=JT::State.new(:jumping) do |i| i.add_event(:walking,:act) do |d,key,dt| move_lr(key,dt) if key.jump then @dy= INIT_DY d.move_state :jumping end end
The player pieces must also 9-track the constants set in CONST.RB so that the piece cannot leave the playing field:
i.add_event(:jumping,:act) do |d,key,dt| move_lr(key,dt) @y += @dy*dt/100 @dy += G*dt/100 if @y > FLOOR_Y - H then @y = FLOOR_Y - H d.move_state :walking end end
Included in the Player.initialize method are sample event handlers to 9-track the player piece in case it collides with any other sprites/rects on the playing surface:
@damage_state = JT::State.new(:normal) do |i| i.add_event(:normal,:act) { } i.add_event(:normal,:collision_enemy) do |d| @system.collision_enemy @damage_time=0 d.move_state(:damaged) end i.add_event(:damaged,:act) do |d,dt| @damage_time+=dt d.move_state(:normal) if @damage_time > DAMAGE_TIME end i.add_event(:damaged,:collision_enemy) { } end end
After Player.initialize come two quick methods that define the width and height of the player piece:
def w ;W;end; def h ;H;end;
You need a draw method to put the previously defined bit-map (in GRAPH_P1) onto the screen. Drawing the bit-map is accomplished with the put method:
def draw(screen) screen.put(GRAPH_P1,@x,@y) end
The act method is a worker method that checks with STATE.RB and establishes the state.act and damage_state.act instance variables so that the player piece has the functionality from STATE.RB:
def act(key,dt) @state.act(key,dt) @damage_state.act(dt) end
Finally, define the player's movement within a move_lr method. Move_lr checks whether the player's key presses move the actual game piece off of the predefined playing surface:
def move_lr(key,dt) @x-=DX*dt/100 if key.left @x+=DX*dt/100 if key.right @x = LWALL_X if @x< LWALL_X @x = RWALL_X-W if @x > RWALL_X-W end
It's in RUBYBOUNCE.RB that SDL is opened and initialized and the actual game loop runs. First, SDL and the other defined files are required:
require 'sdl' require 'system.rb' require 'state.rb' require 'const.rb' require 'player.rb'
Initialize SDL with its init method, define the video mode, and establish the surface area with the following two lines:
SDL.init( SDL::INIT_VIDEO ) screen = SDL::setVideoMode(640,480,16,SDL::SWSURFACE)
A new structure is established that holds each keypress available to the player:
Key = Struct.new("Key",:left,:right,:jump,:send)
The new method constructor is called for each object that must be initialized:
system=System.new player=Player.new(system) event=SDL::Event.new key=Key.new
Now that every object you need is established, the game loop is created. First, use tick to establish the time:
before=now=SDL::getTicks-1
Then establish a while loop that uses the poll method to check for events from the keyboard:
while system.continue_game? if event.poll != 0 then if event.type==SDL::Event::QUIT then break end if event.type==SDL::Event::KEYDOWN then exit if event.keySym==SDL::Key::ESCAPE end end
Each possible key press is queried for by Key::press?:
SDL::Key::scan key.left = SDL::Key::press?(SDL::Key::LEFT) key.right = SDL::Key::press?(SDL::Key::RIGHT) key.jump = SDL::Key::press?(SDL::Key::UP) key.send = SDL::Key::press?(SDL::Key::DOWN)
The SDL ticks are checked for in the loop as time moves forward:
before=now now=SDL::getTicks dt=now-before
Any actions are fulfilled by calling player.act:
player.act(key,dt)
The screen is filled, and the player redrawn with each iteration of the loop:
screen.fillRect(0,0,640,480,0) player.draw(screen)
All that is left to do is make sure the SDL screen is flipped and that any garbage is collected:
ObjectSpace.garbage_collect screen.flip
[ LiB ] | ![]() ![]() |