What's new? | Help | Directory | Sign in
Google
gosu
Cross-platform 2D game development library
  
  
  
  
    
Search
for
Updated Dec 10, 2007 by julianraschke
RubyTutorial  
Tutorial for a small game using Ruby/Gosu.

Source code

The code for the complete game, together with the required media files, can be found in the Gosu distribution of your choice ('examples/Tutorial.rb').

If you have installed Gosu via RubyGems, the examples are in your 'gems' folder together with the rest of the library. For example, on OS X 10.5, the examples can be found in /Library/Ruby/Gems/1.8/gems/gosu-<version>/examples.

If you have not installed Gosu via RubyGems, you have to copy gosu.so (or gosu.bundle, respectively) into the examples directory, then run Tutorial.rb.

If you don't have an editor that supports direct execution (TextMate, SciTE…), cd into the directory and run it via ruby Tutorial.rb.

1. Overriding Window's callbacks

The easiest way to create a complete Gosu application is to write a new class that derives from Gosu::Window (see the reference for a complete description of its interface). Here's how a minimal GameWindow class might look like:

require 'gosu'

class GameWindow < Gosu::Window
  def initialize
    super(640, 480, false)
    self.caption = "Gosu Tutorial Game"
  end

  def update
  end

  def draw
  end
end

window = GameWindow.new
window.show

The constructor initializes the Gosu::Window base class. The parameters shown here create a 640x480 pixels large, non-fullscreen—that's what the "false" stands for—window. Then it changes the window's caption, which is empty until then.

update() and draw() are overrides of Gosu::Window's member functions. update() is called 60 times per second (by default) and should contain the main game logic: move objects, handle collisions, etc.

draw() is called afterwards and whenever the window needs redrawing for other reasons, and may also be skipped every other time if the FPS go too low. It should contain the code to redraw the whole screen, and no logic whatsoever.

Then follows the main program. A window is created and its show() member function is called, which does not return until the window has been closed by the user or its own code. Tada - now you have a small black window with a title of your choice!

2. Using Images

class GameWindow < Gosu::Window
  def initialize
    super(640, 480, false)
    self.caption = "Gosu Tutorial Game"
    
    @background_image = Gosu::Image.new(self, "media/Space.png", true)
  end

  def update
  end

  def draw
    @background_image.draw(0, 0, 0);
  end
end

Gosu::Image#initialize takes three arguments. First, like all media resources, it is tied to a window (self). All of Gosu's resources need a Window for initialization and will hold an internal reference to that window. Second, the file name of the image file is given. The third argument specifies whether the image is to be created with hard borders. See BasicConcepts for an explanation.

As mentioned in the last lesson, the window's draw() member function is the place to draw everything, so this is the place for us to draw our background image.

The arguments are almost obvious. The image is drawn at (0;0) - the third image is the Z position; again, see BasicConcepts.

Player & movement

Here comes a simple player class:

class Player
  def initialize(window)
    @image = Gosu::Image.new(window, "media/Starfighter.bmp", false)
    @x = @y = @vel_x = @vel_y = @angle = 0.0
  end

  def warp(x, y)
    @x, @y = x, y
  end
  
  def turn_left
    @angle -= 4.5
  end
  
  def turn_right
    @angle += 4.5
  end
  
  def accelerate
    @vel_x += Gosu::offset_x(@angle, 0.5)
    @vel_y += Gosu::offset_y(@angle, 0.5)
  end
  
  def move
    @x += @vel_x
    @y += @vel_y
    @x %= 640
    @y %= 480
    
    @vel_x *= 0.95
    @vel_y *= 0.95
  end

  def draw
    @image.draw_rot(@x, @y, 1, @angle)
  end
end

There are a couple of things to say about this:

Integrating Player with the Window

class GameWindow < Gosu::Window
  def initialize
    super(640, 480, false)
    self.caption = "Gosu Tutorial Game"
    
    @background_image = Gosu::Image.new(self, "media/Space.png", true)

    @player = Player.new(self)
    @player.warp(320, 240)
  end

  def update
    if button_down? Gosu::Button::KbLeft or button_down? Gosu::Button::GpLeft then
      @player.turn_left
    end
    if button_down? Gosu::Button::KbRight or button_down? Gosu::Button::GpRight then
      @player.turn_right
    end
    if button_down? Gosu::Button::KbUp or button_down? Gosu::Button::GpButton0 then
      @player.accelerate
    end
    @player.move
  end

  def draw
    @player.draw
    @background_image.draw(0, 0, 0);
  end

  def button_down(id)
    if id == Gosu::Button::KbEscape
      close
    end
  end
end

As you can see, we have introduced keyboard and gamepad input! Similar to update() and draw(), Gosu::Window provides two member functions button_down(id) and button_up(id) which can be overriden, and do nothing by default. We do this here to close the window when the user presses ESC. (For a list of predefined button constants, see RubyReference). While getting feedback on pushed buttons is suitable for one-time events such as UI interaction, jumping or typing, it is rather useless for actions that span several frames - for example, moving by holding buttons down. This is where the update() member function comes into play, which only calls the player's movement methods. If you run this lesson's code, you should be able to fly around!

3, Simple animations

First, we are going to get rid of the magic numbers for Z positions from now on by replacing them with the following constants:

module ZOrder
  Background, Stars, Player, UI = *0..3
end

What is an animation? A sequence of images - so we'll use Ruby's built in Arrays to store them. (For a real game, there is no way around writing some classes that fit the game's individual needs, but we'll get away with this simple solution for now.)

Let's introduce the stars which are the central object of this lesson. Stars appear out of nowhere at a random place on the screen and live their animated lives until the player collects them. The definition of the Star class is rather simple:

class Star
  attr_reader :x, :y
  
  def initialize(animation)
    @animation = animation
    @color = Gosu::Color.new(0xff000000)
    @color.red = rand(255 - 40) + 40
    @color.green = rand(255 - 40) + 40
    @color.blue = rand(255 - 40) + 40
    @x = rand * 640
    @y = rand * 480
  end

  def draw  
    img = @animation[Gosu::milliseconds / 100 % @animation.size];
    img.draw(@x - img.width / 2.0, @y - img.height / 2.0,
        ZOrder::Stars, 1, 1, @color, :additive)
  end
end

Since we don't want each and every star to load the animation again, we can't do that in its constructor, but rather pass it in from somewhere else. (The Window will load the animation in about three paragraphs.)

To show a different frame of the stars' animation every 100 milliseconds, the time returned by Gosu::milliseconds is divided by 100 and then modulo-ed down to the number of frames. This very image is then additively drawn, centered at the star's position and modulated by a random colour we generated in the constructor.

Now let's add easy code to the player to collect away stars from an array:

class Player
…
  def collect_stars(stars)
    stars.reject! do |star|
      Gosu::distance(@x, @y, star.x, star.y) < 35
    end
  end
end

Now let's extend Window to load the animation, spawn new stars, have the player collect them and draw the remaining ones:

class Window < Gosu::Window
  def initialize
    super(640, 480, false)
    self.caption = "Gosu Tutorial Game"
    
    @background_image = Gosu::Image.new(self, "media/Space.png", true)
    
    @player = Player.new(self)
    @player.warp(320, 240)

    @star_anim = Gosu::Image::load_tiles(self, "media/Star.png", 25, 25, false)
    @stars = Array.new
  end

  def update
    ...
    @player.move
    @player.collect_stars(@stars)
    
    if rand(100) < 4 and @stars.size < 25 then
      @stars.push(Star.new(@star_anim))
    end
  end

  def draw
    @background_image.draw(0, 0, ZOrder::Background)
    @player.draw
    @stars.each { |star| star.draw }
  end
…

Done! You can now collect stars.

4. Text and sound

Finally, we want to draw the current score using a bitmap font and play a 'beep' sound every time the player collects a star. The Window will handle the text part, loading a font 20 pixels high:

class Window < Gosu::Window
  def initialize
    …
    @font = Gosu::Font.new(self, Gosu::default_font_name, 20)
  end

  …

  def draw
    @background_image.draw(0, 0, ZOrder::Background)
    @player.draw
    @stars.each { |star| star.draw }
    @font.draw("Score: #{@player.score}", 10, 10, ZOrder::UI, 1.0, 1.0, 0xffffff00)
  end
end

What's left for the player? Right: A counter for the score, loading the sound and playing it.

class Player
  attr_reader :score

  def initialize(window)
    @image = Gosu::Image.new(window, "media/Starfighter.bmp", false)
    @beep = Gosu::Sample.new(window, "media/Beep.wav")
    @x = @y = @vel_x = @vel_y = @angle = 0.0
    @score = 0
  end

  …

  def collect_stars(stars)
    stars.reject! do |star|
      if Gosu::distance(@x, @y, star.x, star.y) < 35 then
        @score += 10
        @beep.play
        true
      else
        false
      end
    end
  end
end

As you can see, loading and playing sound effects couldn't be easier! See the RubyReference for more powerful ways of playing back sounds - fiddle around with volume, position and pitch.

That's it! Everything else is up to your imagination. If you can't imagine how this is enough to create games, see if you can find useful source code from the GosuUsers page.


Comment by hendrik.richert, Jul 15, 2007

You rule.

Comment by singleto...@yahoo.com, Sep 15, 2007

Incredible. This is a beautiful DSL for 2D games!

Comment by kaerigan, Nov 05, 2007

Wow, this looks great.

Comment by kintovision, Dec 09, 2007

the lack of documentation (and errors in what is provided) is starting to piss me off.

I"m calling draw_quad like this in my Tilemap class: draw_quad(x1, y1, Gosu::green, x2, y2, Gosu::green, x3, y3, Gosu::green, x4, y4, Gosu::green, 1)

Why the hell is that getting a method undefined error? I have require 'gosu' at the top of the file...grrrrrr

Comment by kintovision, Dec 09, 2007

omg, ok, so it works when I move the routine out of a function and into game-window class. wtf?

Comment by julianraschke, Dec 09, 2007

Calm down :) draw_quad is a method of Window, so if you'd like to call it from another class, it needs a reference to the window.

Comment by kintovision, Dec 15, 2007

sorry. thanks for being nice and not telling me to RTFM

Comment by demetriusnunes, Dec 19, 2007

Hey Julian, I spent a few hours tonight learning Gosu and I am blown away. Thanks for a great library! Anyway, I've improved the CptnRuby? example a little bit: added background music, several sound effects, a mini-map at the top right and a gem count and timer at the top left, and a conclusion when you collect all gems. How can I post it here? It's 1.1Mb.

Comment by julianraschke, Dec 20, 2007

No problem, just mail it to me at julian@raschke.de and I'll post the URL here :) A mini-map sounds interesting.

Comment by di3go.bernardes, Jan 04, 2008

julian i have a question, there is a way to make animations without using a single file with all the tiles? like this example with stars? like, a monster atk have like 6 dif files, monster walk more 5,...

Comment by julianraschke, Jan 05, 2008

Sure. Load all the files using Image.load_tiles and then just append all the arrays together using the + operator. If that is what you want to achieve :)

Comment by ladybenko, Mar 20, 2008

Hi, I was wondering if I can translate this tutorial to Spanish and post it in my blog. Thanks :)

Comment by julianraschke, Mar 20, 2008

Sure! That would be very cool, I could link it on the DocsOverview page when it's done :)

Comment by ladybenko, Mar 22, 2008

Translation's done :) What's only left is to state clearly the doc license since I don't know whether the wiki is under the same MIT license as Gosu or (c) all rights reserved by you. So just tell me and I'll upload it!

Comment by julianraschke, Mar 22, 2008

The whole Google Code page (except for the comments) counts as Gosu's documentation, and is covered by the MIT license.

Comment by ladybenko, Mar 22, 2008

Thanks! Here's the link http://cafeina.ladybenko.net/?p=485

Comment by julianraschke, Mar 24, 2008

Hah, a stylish PDF even :) It's now linked in the DocsOverview.


Sign in to add a comment