Forum: Ruby-Gnome 2 Writing a game in Ruby

Posted by Jeff Wright (rubygamer)
on 2008-07-11 08:01
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.height*scale)
                        @scale = scale
                end
                cr.set_source_pixbuf(@pixbuf, @x*scale, @y*scale)
                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
#############################################################################
Posted by Joachim Glauche (joaz)
on 2008-07-11 12:48
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.

Posted by André Wagner (andre_nho)
on 2008-07-11 13:09
(Received via mailing list)
> 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)
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.