Continue with Game development with Ruby

In this post we will continue with game development with Ruby. This time we will focus on UI design with Gosu, a library for game development. Let's go step by step and implement our sokoban game.

Game.rb

We create a file game.rb with the following content:

require 'gosu'  # add gosu gem

class GameWindow < Gosu::Window
  def initialize
    super 500, 500
    self.caption = "Sokoban with Gosu"
  end
end

window = GameWindow.new
window.show

we initialize a window for game with width and height of 500 pixels. The window has title Sokoban with Gosu. Then we create an instance of the window and display it. we get a black screen like this.

We need some ways to record the map of the game and then we translate it to build the UI of the game. Now let's create a file called levels.rb.

Levels.rb

module Levels
    L1 = [	
			{x: 0, y: 0, type: :wall},
			{x: 50, y: 0, type: :wall},
            {x: 100, y: 0, type: :wall},
            {x: 150, y: 0, type: :wall},
            {x: 200, y: 0, type: :wall},
            {x: 0, y: 50, type: :wall},
            {x: 200, y: 50, type: :wall},
            {x: 250, y: 50, type: :wall},
            {x: 300, y: 50, type: :wall},
            {x: 0, y: 100, type: :wall},
            {x: 300, y: 100, type: :wall},
            {x: 350, y: 100, type: :wall},
            {x: 400, y: 100, type: :wall},
            {x: 450, y: 100, type: :wall},
            {x: 0, y: 150, type: :wall},
            {x: 50, y: 150, type: :wall},
            {x: 150, y: 150, type: :wall},
            {x: 200, y: 150, type: :wall},
            {x: 450, y: 150, type: :wall},
            {x: 0, y: 200, type: :wall},
            {x: 450, y: 200, type: :wall},
            {x: 0, y: 250, type: :wall},
            {x: 450, y: 250, type: :wall},
            {x: 0, y: 300, type: :wall},
            {x: 50, y: 300, type: :wall},
            {x: 100, y: 300, type: :wall},
            {x: 250, y: 300, type: :wall},
            {x: 300, y: 300, type: :wall},
            {x: 350, y: 300, type: :wall},
            {x: 400, y: 300, type: :wall},
            {x: 450, y: 300, type: :wall},
            {x: 100, y: 350, type: :wall},
            {x: 250, y: 350, type: :wall},
     		{x: 100, y: 400, type: :wall},
     		{x: 150, y: 400, type: :wall},
     		{x: 200, y: 400, type: :wall},
     		{x: 250, y: 400, type: :wall},
     		{x: 100, y: 200, type: :dest},
            {x: 150, y: 200, type: :dest},
            {x: 200, y: 200, type: :dest},
            {x: 250, y: 200, type: :dest},
            {x: 150, y: 250, type: :dest},
            {x: 150, y: 300, type: :dest},
            {x: 100, y: 150, type: :box},
            {x: 250, y: 150, type: :box},
            {x: 300, y: 150, type: :box},
            {x: 200, y: 250, type: :box},
            {x: 250, y: 250, type: :box},
            {x: 350, y: 250, type: :box},
            {x: 100, y: 100, type: :player}
  	]
    Stage = [L1]
end

Plan.rb

Here we create a level which is an array of hashes, each of which has x, y coordinate and types of object as :box, :wall, :player, :dest (destination). We will create another file called plan.rb to translate each level to reality. But before translation can be realized we need some images. We create an image folder with image of size 50x50 with the name: background.jpg, box.png, player.png, wall.png and destination.png. Now let's create plan.rb to do the translation and draw to the screen.

class Plan
  attr_accessor :moves #number of moves

  def initialize
    @level = @score = @moves = 0 #initialize level, score and moves to 0
    
    #instantiate object for player, background, wall, destination and box
    @player = Gosu::Image.new("img/player.png")
    @bg = Gosu::Image.new("img/bg.jpg", :tileable => true)
    @wall = Gosu::Image.new("img/wall.jpg")
    @dest = Gosu::Image.new("img/destination.png")
    @box = Gosu::Image.new("img/box.png")

    @info = Gosu::Font.new(20)  #intantiate object for writing
  end
   #load levels in file `levels.rb` into @map object using Marshal load and dump
   #increment level if we have to go to next level else do nothing, and reset moves number
   
  def load_level(next_level = false)
    @level += 1 if next_level
    @map = Marshal.load( Marshal.dump(Levels::Stages[@level]) ) # deep copy
    @moves = 0
  end
  #draw is Gosu method for drawing image and characters to screen
  #draw has parameters text, x coordinate, y coordinate, z value, x scale, y scale and color
  def draw
    @bg.draw(0, 0, 0)
    @info.draw("Level: #{@level} | Score: #{@score} | Moves: #{@moves}", 10, 480, 1, 1.0, 1.0, 0xff_ff0000)
    @info.draw("Esc- exit | Space - reset", 280, 480, 1, 1.0, 1.0, 0xff_808080)
    
    #The actual translation happens here
    @map.each do |cell|
      case cell[:type]
      when :wall
        @wall.draw(cell[:x], cell[:y], 0)
      when :dest
        @dest.draw(cell[:x], cell[:y], 1)
      when :box
        @box.draw(cell[:x], cell[:y], 0)
      when :player
        @player.draw(cell[:x], cell[:y], 2)
      end
    end
  end
end

We need to require some files in game.rb

require_relative 'levels'
require_relative 'plan'

Let run the game again: ruby game.rb we get something like this:

This is quite a lot now. I think we can add some movement in the next post. Hope you enjoy.

Reference

http://dreamingechoes.github.io/game/gosu/ruby/become-a-videogame-developer-master-with-gosu-and-ruby/ https://www.libgosu.org/