Ruby's So Classy

Posted: 08 Nov 2015
Classes and game design

I've spent a lot of time developing small video games in my spare time ( and I have two unfinished games to show for it ). Up until now, I've only ever used C++ and some extension libraries to make that happen, but in Ruby as in C++ classes are key to many types of games. I'm going to create an Actor class to demonstrate how Ruby classes work, and how they can be used in a larger program. We use the term "actor" to describe any thing in the game space that moves and interacts with the environment.

class Actor

  def initialize( x = 0, y = 0 )

    @posX = x
    @posY = y
    @width = 16
    @height = 16

  end

end

Here we begin our class definition. There are a lot of things going on here, so I'll break them down into steps:

  1. We define our class "Actor"
  2. We define the "initialize" method with parameters
  3. We declare several instance variables

Defining our class is simple, we just write "class" to let Ruby know we're about to name a new class, then write the name we would like to use. In Ruby, common practice is to begin the class name with a captial letter, and to begin subsequent words with a captial and without a space like this: CamelCase.

Every class needs a class constructor, the method which creates a new object using our class; a class isn't any good if we don't make an object to use it! It's always best to start with this method, because it's easier to read and this is where we create any variables we need for the rest of the class. ALWAYS name your constructor "initialize", this is just how Ruby works; when we call "my_actor = Actor.new" later, Ruby will look for the method called "initialize" in the class "Actor" in order to create the new object.

Now we fill in our constructor with some things we want. The constructor is only called once, so we only include things that we don't need to repeat later. Our Actor needs at least four variables: x and y coordinates, width, and height. We declare each of these variables by preceding each name with an "@" to specify that the entire class needs access to these variables; were we to omit the "@", these variables would be local to our constructor, and unavailable to any other method in the code.

You'll notice that our "initialize" method has some parentheses and variables following it. These are our parameters; when we create a new "Actor" object, we want to be able to place it in a different spot in the game space, rather than piling up all of our Actors on top of each other ( well, maybe you want to do that, but I'm not making that type of game here ). I've written "x = 0, y = 0" so that if the new Actor is created with no arguments, we pass 0 to our posX and posY variables instead. For the sake of simplicity, our Actors are always 16 by 16 pixels in size.

This is a good start, but our class doesn't do anything. So far, we can make an object that stores four variables, inaccessible to anything outside of that object. So, we write more methods.

def collide?

  if @xPos < 0
    true
  elsif @yPos < 0
    true
  elsif @xPos + width > 640
    true
  elsif @yPos + height > 640
    true
  else
    false
  end

end

def move

  xMove = rand( 2 ) - 1
  yMove = rand( 2 ) - 1

  @xPos += xMove

  if collide?
    @xPos -= xMove
  end

  @yPos += yMove

  if collide?
    @yPos -= yMove
  end

end

A fully realized Actor class would look more complicated than this, and may accept player input, but for now, we're just defining random movement. We've got two new methods here that will move our Actor: "collide?" and "move". We use "collide?" to check whether our Actor has moved outside of the game space, if it has "collided" with the boundaries. For our purposes, our game space is 640 by 640 pixels in size; to check whether the actor goes out of bounds, we have to add it's width to its x position, and height to its height coordinate, otherwise, our Actor will only stop once it goes all the way off the edge of the game space.

Now for our "move" method. Here we generate two random numbers between -1 and 1 ( actually get 0, 1, or 2, and subtract 1 ). Then, we pass those numbers to our Actor's coordinates, @xPos and @yPos. Immediately after we change our Actor's coordinates, we check to see whether it moved outside the game space. If it did, we subtract the same number we just added to get our Actor back within the boundaries. This is a very simple way to do what we call "collision detection"; this all happens within the method, and before we even draw our Actor to the screen, so the end user doesn't actually see the Actor move out of bounds and jump back, even though that's what the program does.

Notice that I used both instance and local variables in the "move" method. I called our Actor's x and y coordinates, which are instance variables, and created two local variables within the method to store the random numbers. We only need those random numbers within the "move" method, so it's best to make them local, but we need the coordinates we declared earlier in order to execute the method, so we needed to be sure we declared them as instance variables.

I won't go into all the details here, because we still need to draw our Actor and give it some other things to do besides moving. Instead, I'll show you what the rest of our code would look like to generate the game.

pac = Actor.new( 304, 480 )
blinky = Actor.new( 304, 282 )
pinky = Actor.new( 282, 304 )
inky = Actor.new( 304, 304 )
clyde = Actor.new( 320, 304 )

quit = false

while( quit == false )

  pac.move
  blinky.move
  pinky.move
  inky.move
  clyde.move

  pac.draw
  blinky.draw
  pinky.draw
  inky.draw
  clyde.draw

end

First we create our five Actors: pac, blinky, pinky, inky, and clyde. Then, we set up a game loop ( which should provide a way to break in practice ) by creating a boolean "quit". Finally, each time our loop executes, we call the "move" method on each of our Actors once. I've added a "draw" method to illustrate how we would "move" each actor first, then "draw" them on the screen.

This is a totally simplified, inaccurate recreation of Pac-Man, especially since the player's movements should be controlled, not random, but this should give you an idea of how video games can use classes, and how best to use a class in your own programs.

Previous: An Enumerable method: cycle | Next: Ruby vs JavaScript