Writing a game in Ruby

Hi there,

I’m in the process of writing what will eventually be a card game using
ruby. However, first things first I’m trying to set up the graphics
functions. Currently I’m using a Gtk window with a sigle widget, a
DrawingArea inside which I use cairo to draw the graphics.

However I’ve run into a few problems. First of all, if I don’t manually
call the garbage collector frequently, the program grinds to a halt –
it seems like when i grab a new Cairo context, the old one isn’t deleted
and hangs around in memory? Am I doing something wrong?

Secondly, even after adding the garbage collector, the frame rate is
depressingly low. With no background and drawing a minimal number of
objects per frame, it only gets ~20 FPS.

I feel like I must be doing something wrong.

My question is twofold:

  1. Is there a better way to do this (other than cairo/GTK) that I don’t
    know about?
  2. If not, is there any way to speed up cairo’s drawing?

I’ve included a stripped-down version of my code below.

Thanks so much!

P.S. I’m somewhat new to this forum, so if this post is in the wrong
place, please tell me.
###############################################################################

require ‘gtk2’
require ‘cairo’

def min(x, y)
if x<y
return x
else
return y
end
end

def max(x, y)
if x>y
return x
else
return y
end
end

class Entity
attr_accessor :image, :x, :y, :scale

    def initialize(img, x, y)
            @x, @y = x, y
            @image = Gdk::Pixbuf.new(img)
            @scale = 0
    end

    def draw(cr, scale)
            cr.save
            if scale != @scale
                    @pixbuf = @image.scale(@image.width*scale,

@image.heightscale)
@scale = scale
end
cr.set_source_pixbuf(@pixbuf, @x
scale, @yscale)
cr.rectangle(@x
scale, @y*scale, @pixbuf.width,
@pixbuf.height)
cr.clip
cr.paint
cr.restore
end
end

class Field
attr_accessor :entities, :scale, :height, :width

    def initialize(w, h)
            @width, @height = w, h
            @entities = []
    end

    def draw(cr, x, y, scale)
            return nil if scale <= 0
            cr.save
            cr.rectangle(x, y, scale*@width, scale*@height)
            cr.clip
            cr.translate(x, y)
            cr.scale(scale, scale)
            entities.each do |e|
                    e.draw(cr, 1)
                    e.x += rand()*10-5
                    e.y += rand()*10-5
            end
            cr.restore
    end

end

class JCanvas < Gtk::DrawingArea
attr_accessor :myfield, :yourfield, :frames, :sc

    def initialize(w, h)
            @myfield = Field.new(1280, 800)
            @yourfield = Field.new(1280, 800)
            @frames = 0
            @sc = 0.5
            super()
    end

    def get_cr()
            #puts "Getting new CR"
            cr = window.create_cairo_context()
    end

    def redraw
            cr = get_cr
            cr.push_group()

            black = [0.0,0.0,0.0]
            cr.set_source_rgb(black)
            cr.paint

            sc = @sc

            myfield.draw(cr, 640-640*min(sc,1), 0, min(sc, 1))
            yourfield.draw(cr, 640-640*max(1-sc, 0), 800*sc,

max(1-sc, 0))

            cr.pop_group_to_source()
            cr.paint

            @frames += 1
            return 1
    end

end

def main

    win = Gtk::Window.new;
    win.set_title("Magickz!");

    win.fullscreen
    w, h = win.default_size()
    canvas = JCanvas.new(w, h);
    10.times { canvas.myfield.entities.push(Entity.new("test.jpg",

rand()*500, rand()*500)) }
10.times {
canvas.yourfield.entities.push(Entity.new(“skull.jpg”, rand()*500,
rand()*500)) }

    canvas.add_events(Gdk::Event::BUTTON_PRESS_MASK)
    canvas.signal_connect("button_press_event") { puts "got MOUSE!!"

}

    canvas.add_events(Gdk::Event::KEY_PRESS_MASK)
    canvas.signal_connect("key_press_event") { |ev, k| puts "got

KYBD (#{k.keyval})"; if k.keyval==113; exit(); end; if k.keyval==65362;
canvas.sc +=0.05; else; canvas.sc -= 0
.05; end }

    canvas.set_flags(Gtk::Widget::CAN_FOCUS)

    win.add(canvas);
    canvas.show();
    win.show()

    Thread.new("gameloop") {
            while true
                    canvas.redraw
                    sleep(0.0100)
            end
            true
    }
    Gtk.timeout_add(2000) {puts "starting GC"; GC.start; true}
    Gtk.timeout_add(1000) {puts "FPS = #{canvas.frames}";

canvas.frames = 0; true}

    Gtk.main

    return 0;

end

main
#############################################################################

Hi Jeff,

I’m in the process of writing what will eventually be a card game using
ruby. However, first things first I’m trying to set up the graphics
functions. Currently I’m using a Gtk window with a sigle widget, a
DrawingArea inside which I use cairo to draw the graphics.

I looked at your code but i’m not sure what you plan to to exactly. I
created a picture test.jpg and commented out the skull.jpg thingy. I
just see a window, nothing more. Maybe you can attach the full code &
pictures.

However I’ve run into a few problems. First of all, if I don’t manually
call the garbage collector frequently, the program grinds to a halt –
it seems like when i grab a new Cairo context, the old one isn’t deleted
and hangs around in memory? Am I doing something wrong?

Ok, first be sure to use the newest ruby-gnome version.
Second, the GC is automatically started. With GC.start you trigger a
run, but you never stop it.
What you can do is avoid destroying/recreating/reloading stuff you can
still use. For example your images. Load them into a pixbuf once at
start and use whenever you need them. Other example is what you display.
Do you need to update everything or just one area?

Secondly, even after adding the garbage collector, the frame rate is
depressingly low. With no background and drawing a minimal number of
objects per frame, it only gets ~20 FPS.

Well despite GTK is not a game engine, have a look at your “game main
loop”. I guess canvas.redraw redraws everything - and you call this 100
times a second. Plus, this operation may be blocked by other operations
of the GTK main loop which want to do something at the same time. You
may consider putting this “game main loop” into the GTK main loop.

oh and:

def min(x, y) …
def max(x, y) …

You could just use [1,2].min or [1,2].max in ruby.

Secondly, even after adding the garbage collector, the frame rate is
depressingly low. With no background and drawing a minimal number of
objects per frame, it only gets ~20 FPS.

Hello Jeff,

not to be saying anything against Gtk, but have you ever heard of gosu?
It’s a very nice new game library for ruby.

http://code.google.com/p/gosu/

Regards,

André


Lamentação sem gratidão é murmuração. (Howard Hendricks)