codemonkey like you

Month: July, 2011

My First Game (using Ray, a Ruby DSL)

Ray is a Ruby DSL for creating games that looks totally sweet. This blog post will walk you through how to make a game with it. I just discovered Ray about half an hour ago, so we’ll see how this goes.

First, install the gem’s external dependencies. I’m on OSX, so I do this:

brew install glew libsndfile

Then, I typed in an example program. I find that it’s much more helpful to type stuff in than to copy and paste. It sticks in your mind more. For example, the first time I ran the following program, I left out the require 'rubygems' line and it choked, saying it couldn’t find ray.

require 'rubygems'
require 'ray'

Ray.game 'my game', :size => [800, 800] do
  register { add_hook(:quit, method(:exit!)) }

  scene :some_scene do
    # Exit when q is pressed
    on :key_press, key(:q){ exit! }

    @text = text "Hello world!", :angle => 30, :at => [100, 100], :size => 30
    render do |win|
      # You ask the target to draw each object.
      win.draw @text
    end
  end

I couldn’t think of a game to make, so I turned to Whitacker Blackall’s wonderful and funny post about his first steps in programming and game creation. And then: of course, pong!

These are my initial thoughts on the design, stream-of-consciousness style.

OK, looking at Whitaker’s screenshot, the main visual elements are:

  • 2 paddles (maybe I can make a paddle method so I don’t have to type the same code twice)
  • a ball
  • A score
  • The ability to pause the game, with a status indicator (my first thought is a global hook for the space bar, which I’m 99% sure is possible, since I did it with q)
  • A dashed line going down the middle of the screen
  • A black background with white objects, except for the dashed line, which is gray

Interactivity / Event bindings:

  • Key bindings for the left paddle that move it up & down
  • Key bindings for the right paddle that move it up & down
  • Need to update score when the ball goes past a paddl
  • The ball moves (!) and bounces off paddles (!!!)
  • Need to check for when ball hits the paddle (and rebound it at the correct angle!)
  • Pausing is optional, I’ll do it if I feel like it

First Steps

Let’s make a black background. Yep, that simple. I’m keeping the skeleton I created above, especially the press-q-to-quit binding, because otherwise it’s hard to quit. Turns out Ray has a black background by default, so that’s easy.

Progress so far: https://gist.github.com/1087755

Making the Paddle

Let’s do something a bit harder: make a white oval pong paddle. Looks like Ray::Polygon.ellipse is what I want. Since I know I’m going to have more than one paddle, I’m going to make a paddle method. It’s kind of unclear what the first 3 arguments to ellipse do, but right now I think that the initial array is the size ([height, width] in pixels) and the other two numbers are y-pos and x-pos. Let’s try it…and I get a weird blob. Fiddling with the numbers doesn’t tell my anything, so let’s go into the source:

git clone https://github.com/Mon-Ouie/ray.git

(Side note: This project’s documentation is a bit lacking. Right now the non-github homepage is good at getting you excited (clearly :)) and gives a broad overview, but it’s light on details. It’s version 0.1.1, though, so this level of documentation is OK – for such an early version, getting people excited enough to, say, blog about it is a huge step.) Anyway, `git grep Ellipse` gives…nothing? What the heck? Oh – it’s lowercase `ellipse`. Oops. Anywyay, here’s what we want, in ext/polygon.c:

    @param [Ray::Vector2] center The center of the ellipse
    @param [Float] rx Horizontal radius
    @param [Float] ry Vertical radius
    @param [Ray::Color] color The color of the ellipse

So the first param is a Ray::Vector2 (in lib/vector.rb) which is the center of the ellipse, and the other two number params are the horizontal and vertical radius…lets’s try it. Here’s what I tried:

Ray::Polygon.ellipse([400, 400], 400, 40, Ray::Color.white)

No dice. It still gives me a weird blob in the upper left corner, even though

Ray::Polygon.circle([400, 400], 100, Ray::Color.white)

does what I think it should do: gives a circle with radius 100 centered at (400, 400). The weird scaling factor of the ellipse is throwing me off, I think.

This is taking too long. I’m going to use a rectangle instead – rounded corners aren’t essential right now. Thankfully, the rectangle initialization parameters are much easier to figure out, and I quickly have two paddles on screen:

Paddles on screen

We have paddles!

Gist of what I’ve got so far: https://gist.github.com/1087793
You’ll notice that I use descriptive variables instead of comments – this is directly attributable to reading Martin Fowler’s Refactoring at work. Also, it turns out I was right, I could make a re-usable paddle method.

Let’s make a ball

This is not going to be a moving ball, just a white circle in the center of the screen. This went much faster (maybe 30 seconds):

def ball
  Ray::Polygon.circle([WIDTH/2, HEIGHT/2], BALL_RADIUS, Ray::Color.white)
end
Paddles and a ball

We now have a ball.

Here’s the gist: https://gist.github.com/1087812

A Score to Settle

Now we draw the score. This is just to get the text in place.

Pong has a score now

You’ll note that I’m using “left” and “right” as the respective scores – this was so I could be sure that the text was placed correctly, since using “0” for both wouldn’t show a difference. I expect the scores to be tracked by variables later, maybe with a redraw_scores method. I also expect to change @left_score to @left_score_text, but we’ll get to that later.
Here’s the gist: https://gist.github.com/1087824.

That Dotted Line

I like the dotted line, and it’s the last static element remaining, so let’s do it. I expect it to be pretty difficult to do a dotted line, so I’ll do a regular line. First, I’ll try this:

def middle_line
  Ray::Polygon.line([WIDTH/2, HEIGHT/2], [WIDTH, HEIGHT], HEIGHT, Ray::Color.white)
end

I thought that the parameters were ([initial_x, initial_y] [final_x, final_y], length, color) but it looks like I was wrong, since I got this:

Misshapen pong line

A misshapen line

But then I realized that I was right about the parameters, but wrong about which ones to put in. This worked:

def middle_line
  line_width = 5
  line_x_pos = center_x_pos
  Ray::Polygon.line([line_x_pos, 0], [line_x_pos, HEIGHT], line_width, Ray::Color.white)
end
Pong has a line down the middle now

Pong has a line down the middle now

And here’s the gist: https://gist.github.com/1087851. You’ll notice that I refactored pixels_[left,right]_of_center a bit.

Action!

Let’s try adding our first interactive piece: moving a paddle up and down with the keyboard. I know that I want the methods to be Lispy, like this:

def move_paddle_up(paddle)
  # ...
end

So let’s try that. It should work, since we can access polygon attributes directly. I ran into some problems trying to call a method defined outside the Ray.game block inside the on :key_press block (something about calling NilClass#arity), but using holding? worked fine. Plus, you can pass :up directly to holding?, while on :key_press requires you to pass in key(:up).

Here’s  the gist: https://gist.github.com/1087881.

Until next time

Well, that’s it for this post. In the next post: boundary detection, and (hopefully) finishing the game. I hope you learned something!

Tumblarity++

I just (re)started my Tumblr here: http://gabebw.tumblr.com/. I’ll be using it for short “today I learned”-style tips. I’ll use this blog for long-form posts.