Friday, August 6, 2010

A simple menu for a Gosu game in ruby

For The Lost Battle, I wrote a very simple menu system that would allow me to add in-game menus. This allowed me to instantiate a menu and then add items to it and let the menu take care of the drawing.

The menu also takes care of notifying me when an item has been clicked via a callback, and also offsets the position of the item on hover.

The following is the basic gist of how it can be used:

@menu = Menu.new(self) #instantiate the menu, passing the Window in the constructor

@menu.add_item(Gosu::Image.new(self, "item.png", false), 100, 200, 1, lambda { self.close }, Gosu::Image.new(self, "item_hover.png", false))
@menu.add_item(Gosu::Image.new(self, "item2.png", false), 100, 250, 1, lambda { puts "something" }, Gosu::Image.new(self, "item2_hover.png", false))

The arguments of add_item are the image showed when the item is in its normal state, the x position, y position, z-order, the callback that will be invoked when that item is clicked and finally an optional parameter of an image that is used when the item is hovered upon.

Then hook to the button_down event of your Window, and inform the menu that the mouse has been clicked:

def button_down (id)
    if id == Gosu::MsLeft then
        @menu.clicked
    end
end

Finally, just draw and update the menu in the hooked methods:

def update
    @menu.update
end

def draw
    @menu.draw
end

I made a simple demo of this and you can download it from here.


The following is the source of the two aforementioned classes:

class Menu
    def initialize (window)
        @window = window
        @items = Array.new
    end

    def add_item (image, x, y, z, callback, hover_image = nil)
        item = MenuItem.new(@window, image, x, y, z, callback, hover_image)
        @items << item
        self
    end

    def draw
        @items.each do |i|
            i.draw
        end
    end

    def update
        @items.each do |i|
            i.update
        end
    end

    def clicked
        @items.each do |i|
            i.clicked
        end
    end
end

class MenuItem
    HOVER_OFFSET = 3
    def initialize (window, image, x, y, z, callback, hover_image = nil)
        @window = window
        @main_image = image
        @hover_image = hover_image
        @original_x = @x = x
        @original_y = @y = y
        @z = z
        @callback = callback
        @active_image = @main_image
    end

    def draw
        @active_image.draw(@x, @y, @z)
    end

    def update
        if is_mouse_hovering then
            if !@hover_image.nil? then
                @active_image = @hover_image
            end

            @x = @original_x + HOVER_OFFSET
            @y = @original_y + HOVER_OFFSET
        else 
            @active_image = @main_image
            @x = @original_x
            @y = @original_y
        end
    end

    def is_mouse_hovering
        mx = @window.mouse_x
        my = @window.mouse_y

        (mx >= @x and my >= @y) and (mx <= @x + @active_image.width) and (my <= @y + @active_image.height)
    end

    def clicked
        if is_mouse_hovering then
            @callback.call
        end
    end
end

Note that both the code and the method in general can be greatly improved and refactored. Just take a look at the code and modify it as you see fit :)