Let's make Nyan Cat using the 'gosu' gem
Apr 30, 2015
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
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!
Buy me a coffee @hola_soy_milk