When I’m not fighting crime and going about my daily coding life, I’m teaching kids to code computer games.

We’ve recently been using the gosu gem, which is a ton of fun to use and makes creating games really simple.

When making an introductory game, I want to avoid using the ‘space shooter’ example.

I therefore decided that it was time to make an adorable game where you have Nyan Cat pick up sweets!

Nyan Cat Game Tutorial

Nyan Cat Game

If on OS X, you’ll need to first install some libraries. I’d recommend using Homebrew:

brew install sdl2 libogg libvorbis

After that, it’s as simple as running gem install gosu.

To start, clone the following repository:

https://github.com/RubyHabits/nyan-cat

First, we’ll need a Window. Gosu provides a Window class for us to use:

require 'gosu'
Gosu::Window.new(900, 550, false).show

The Window’s constructor takes three parameters: The width, the height, and whether it should be in full screen mode.

Go ahead — run this! You’ll see a lovely, albeit empty, window.

Let’s extract a class out of this:

class Window < Gosu::Window
  def initialize
    super(900, 550, false)
  end
end

Window.new.show

The above does the same, but now we can start adding things to our window. Let’s give it a title.

def initialize
  super(900, 550, false)
  self.caption = 'Nyan Cat!'
end

If we run it, we’ll immediately see the title. Awesome!

Let’s start with adding our protagonist, the Nyan Cat! I’ve added some images to the repository that we can use to display our feline friend:

First we’ll create a NyanCat object in our Window class, and then draw it:

def initialize
  super(900, 550, false)
  self.caption = 'Nyan Cat!'
  @cat = NyanCat.new(self)
end

def draw
  @cat.draw
end

In the background, the Window class calls two methods on itself — draw and update. We will subclass these to add our game logic!

But first, let’s add our NyanCat class.

class NyanCat
  def initialize(window)
    @sprites = Gosu::Image::load_tiles(window, 'images/cat.png', 847/6, 87, false)
    @x = 10
    @y = 200
    @width = @sprites.first.width
    @height = @sprites.first.height
  end
end

This initialize method comes pretty loaded:

  • First, we load the image into the given and split it up into six sprites for animation, specifying its width and height and declaring it non-tileable.
  • We set its initial coordinates to 10, 200
  • We extract its width and height from the images

Next, we’ll need to have a function that draws the cat on the window:

  def draw
    sprite = @sprites[Gosu::milliseconds / 75 % @sprites.size]
    sprite.draw(@x, @y, 1)
  end

The coordinates represent the top left corner of the sprite. This is important to remember!

The third parameter of the draw method is the z coordinate. Basically, the higher the value, the further in front it’s shown.

Good to go! If we run the game, the cat should appear and animate beautifully.

Next, let’s add some movement. In our game, the cat will be able to move up and down.

def update
  @cat.move_up if button_down? Gosu::KbUp
  @cat.move_down if button_down? Gosu::KbDown
end

As mentioned before, the Window class automatically calls update on itself. When this happens, we check to see if the player is holding the up or down keys and move the cat accordingly. Let’s add those!

def move_up
  @y = @y - 5
end

def move_down
  @y = @y + 5
end

See how easy that was?! Run it! Your kitty will happily fly through space!

What we’re doing here is that each time the window updates itself and the key is being pressed, the cat’s y coordinate will go up or down by 5, so that the next time the window is drawn, the cat will appear to have moved 5 pixels.

The bigger the y value, the lower down the screen we go.

And now, a yummy sweet for the cat to collect! We’ll make it simple. There’s only one sweet onscreen at a time.

def initialize
  super(900, 550, false)
  self.caption = 'Nyan cat!'
  @background = Background.new(self)
  @cat = NyanCat.new(self)
  @sweet = Sweet.new(self)
  @score = 0
end

def draw
  @cat.draw
  @sweet.draw
end

def update
  @cat.move_up if button_down? Gosu::KbUp
  @cat.move_down if button_down? Gosu::KbDown
  @sweet.move
  @sweet.reset(self) if @sweet.x < 0
  if @cat.bumped_into? @sweet
    @score = @score + 1
    @sweet.reset(self)
  end
end

We added a few important things here. Let’s go over them:

  • We added a Sweet object. We need to write its class!
  • We also added a score counter
  • The sweet is drawn, just like the cat
  • The sweet moves independently
  • The sweet will reset (more on this later) when it leaves the screen
  • It will also reset if the cat bumps into it.
  • When the cat bumps into the sweet, a point will be added to our score

Let’s add said Sweet class:

class Sweet
  attr_accessor :x, :y, :width, :height
  def initialize(window)
    @sprite = Gosu::Image.new(window, 'images/candy.png')
    @width = @sprite.width
    @height = @sprite.height
    reset(window)
  end

  def draw
    @sprite.draw(@x, @y, 1)
  end
end

As you can see, the code is very similar to that of the cat. It’s not animated, so we just declare a single sprite to be the image representation of the sweet.

We’ve declared the coordinates and size as accessible attributes. This is in order for the cat to check if it bumped into the sweet. Also for the window to check if the sweet left.

Two methods missing: reset and move. These are as follows:

def reset(window)
  @y = Random.rand(window.height - @height)
  @x = window.width
end

def move
  @x = @x - 15
end

Resetting the sweet will move it to the right hand side of the screen. Naturally, the right hand side is the width. The maximum value that the x coordinate can take.

Next, the sweet moves to the left, so the x coordinate moves lower, to approach 0, the left hand side. You probably noticed that the sweet moves three times as fast as the cat. This adds challenge!

Finally, let’s add a method to check if the cat bumped into something. This will require some geometric calculations, but they’re easy to follow!

  def bumped_into?(object)
    self_top = @y
    self_bottom = @y + @height
    self_left = @x
    self_right = @x + @width

    object_top = object.y
    object_bottom = object.y + object.height
    object_left = object.x
    object_right = object.x + object.width

    if self_top > object_bottom
      false
    elsif self_bottom < object_top
      false
    elsif self_left > object_right
      false
    elsif self_right < object_left
      false
    else
      true
    end
  end

It looks nasty, but the code is fairly straight forward. We first need to calculate the values for the top, bottom, left and right edges of both the cat and the object it bumps into.

Next, some logic to figure out if they collided. It’s quite clever! Basically, if the cat is not above, not below, not to the left AND not to the right of the object, then they naturally bumped!

If you run the game, you can actually play it now! It’s fully functional, but there are some bells and whistles to add.

First off, the lovely Nyan Cat tune.

In the initialize method of the Window class, add the following two lines:

@song = Gosu::Song.new(self, 'sounds/nyan.mp3')
@song.play

Run it! Nyanyanyanyanaynaynaynayan (sorry, got excited.)

Next up, let’s display the score! This is easy as well:


  def initialize
    super(900, 550, false)
    self.caption = 'Nyan cat!'
    @background = Background.new(self)
    @cat = NyanCat.new(self)
    @sweet = Sweet.new(self)
    @score = 0
    @score_text = Gosu::Font.new(self, 'Arial', 72)
    @song = Gosu::Song.new(self, 'sounds/nyan.mp3')
    @song.play
  end

  def draw
    @background.draw
    @cat.draw
    @sweet.draw
    @score_text.draw("#{@score}", 0, 0, 1)
  end

The Gosu::Font class is also provided for you, and we draw it like an image, passing to it the text we want to display.

Finally, you’ll notice mention of a Background class.

To give it the illusion of infinite movement, we’ll add the same background image twice and moving these past the player. The illusion of movement comes to life!

The code below will be added to the same file, where we’ll have our Window class.

class Background
	def initialize(window)
    @first_image = Gosu::Image.new(window, "images/background.jpg")
    @width = @first_image.width
    @second_image = Gosu::Image.new(window, "images/background.jpg")

		@first_x = 0
    @second_x = @first_x + @width
    @scroll_speed = 2
	end

	def draw
    @first_image.draw(@first_x, 0, 0)
    @second_image.draw(@second_x, 0, 0)
	end

	def scroll
    @first_x = @first_x - @scroll_speed
    @second_x = @second_x - @scroll_speed
    if (@first_x < -@width)
        @first_x = @width
        @second_x = 0
    elsif (@second_x < -@width)
        @second_x = @width
        @first_x = 0
    end
	end
end

It’s a little complex, but basically, if one of the background images leaves the screen completely, it’ll be reset to be to the right of the other.

However, the background doesn’t scroll yet. I challenge YOU to figure out where the method call should go.

And that wraps it up! If you made it this far, you’ll be happy to know that you can check out the finished game in the ‘finished-game’ branch.

Happy nyan-ing!