Creating Your Own Version of Pong with Ruby

Are you having a hard time figuring out where to start with game development? Don’t worry plenty of people start without any idea of how to get off the ground, but once you realize what you need to do to get started developing, the process becomes quite easy. Once you’ve made a basic 2D game you’ll end up with not just a small academic project but also a bunch of code that you’ll be able to reuse for future projects.

Game Window Class

Before continuing with the program you’ll need to install the Gosu library which aides with game development and download these sound files that’ll be used later on. If you don’t know how to install a ruby library (also called gems) check out Gosu’s helpful installation guide.

Once you’ve imported Gosu you should start off by creating a game window class which is where the bulk of your game will be. This class should inherit from the Gosu class called window so it can properly display and draw 2D graphics like so:

require 'gosu'  # Imports in Gosu library

class GameWindow < Gosu::Window  # Creates Game window class

Next you’ll need to set up some attribute readers for variables that you’ll be creating later on in the class. An attribute reader allows a variable to be read and utilized by other methods without allowing the variable to be overwritten. These variables will be used to represent the current score and the three sound effects that we’ll be placing in the game.

attr_reader :score
attr_reader :blip_sound
attr_reader :boop_sound
attr_reader :score_sound

After this you’ll need to create a method which initializes your game. First you need to create a window to actually display your game in. With base Ruby this would be difficult to do but thankfully Gosu provides us with a helpful method called “super” that’ll do it for us. Super takes the following arguments, height and width of the window in pixels and an optional third argument which determines if the window is fullscreen or not, set to false by default.

You’ll also want to set a title for your window like “Pong” for example. Gosu provides a method that does this called “.caption”, just set it equal to whatever you want the title to be.

def initialize
 super 640, 480 # Creates a 640 by 480 window
 self.caption = "Pong" # Sets the window caption to “Pong”

Next you should define all the variables you’ll need in the program. First create a variable for the margin and set it equal to 20, this is the amount of pixels away from the window edge we want the pong paddles to be. After this define a player and an enemy variable and set them both equal to Paddle.new, this creates an instance of the Paddle class which we’ll be creating later to represent the two paddles on the screen. You’ll need to set the positions of your paddles on the screen, for the left one just use the margin variable for both its x and y. Since your computer draws graphics from left to right you’ll need to set the right paddle’s x position to the whole width of the window minus 20, the y position should still just be 20.

margin = 20
@player = Paddle.new( margin, margin )  # Sets the Position of the left paddle
@enemy = Paddle.new( self.width - Paddle::WIDTH - margin, margin)  #Sets the position of the right paddle

You’ll need to also create a ball variable which is equal to a new ball (Like with the paddles we’ll be creating a class for the ball later on in the program). This ball should be set to appear at the top left area of the screen. You need to create an array for the score which contains two entries, both of which should be set to zero at the start. Then create a variable which uses “Gosu::Font” to contain the size of the font we’ll be using which is 30. Lastly create a counter variable and set it equal to zero and then call the “load_sounds” method which we’ll create later on for the purposes of playing sound effects.

@ball = Ball.new( 100, 100, { 😡 => 4, :y => 4 } )  # Creates a new ball at position 100, 100
@score = [0, 0]  # Sets both sides score to zero
@font = Gosu::Font.new(30)  # Sets Text Font Size to 30
@counter = 0  #Creates a counter variable for loops
load_sounds  #Loads in Sound Effects

Gosu comes with variables for each keyboard key which return true when that specific key is pressed. In your next method you should set the game window to close automatically whenever the player presses the escape button. Close is a Gosu method which simply closes the currently opened window.

def button_down(id)
case id
   when Gosu::KbEscape  # Detects if escape button pressed
   close  # Closes Window
 end
end

Now we need to create the method which will continuously update our program’s state. Thankfully Gosu is once again here to make our lives easier with the “update” method. This method, when written into a program is set to be called 60 times per second so any code inside of it will be continuously checked.

For example in our pong game we need to always be checking if the player is moving, if the AI is moving and where the ball currently is on the screen. Later on we’ll be writing the methods that check these things but for now simply call to them in your update method.

player_move  # Moves Human Player
ai_move  # Moves AI Player
@ball.update  # Moves Ball

Another thing you need to check is if the ball is currently in contact with either the pong paddles or a wall. Later on we’ll create a method which detects collision between two objects but for now just call to it. The method will take one argument which is another object, so create an if statement which checks if it touched either player or enemy objects, if it has increase the speed of the ball and reflect it. Also be sure to play the sound effects of the ball hitting paddles.

if @ball.collide?(@player)  # Checks if ball has collided with player
 @ball.reflect_horizontal  # Reflects ball off player
 increase_speed  # Increases Ball Speed
 @blip_sound.play  # Plays Sound effect
elsif @ball.collide?(@enemy)  # Checks if ball has collided with AI
 @ball.reflect_horizontal  # Reflects Ball off AI
 Increase_speed  # Increases Ball Speed
@bloop_sound.play  # Plays Sound effect

Since the walls aren’t actually objects you can’t use your collide method to detect if the ball has hit them. Instead check the current x position of the ball to see if it’s equal to zero as that would mean it’s currently in contact with the wall. If it is in contact it should reflect off the wall and the game score should increase with a sound effect playing as well. Finally to wrap up the method you need to check if the ball has collided with the ceiling or floor by seeing if its y position equals zero or the height of the window and then reflect it off said surface.

elsif @ball.x &lt;= 0  # Checks if ball hit left wall
 @ball.x = @player.right  # Sets ball x position equal to the human paddles right side
 score[1] += 1  # Increases AI score by one
 @ball.v[:x] = 4
 @score_sound.play  # Plays scoring sound effect
elsif @ball.right >= self.width  #  Checks if ball hit right wall
 @ball.x = @enemy.left  #  Sets ball position equal to AI paddles left side
 score[0] += 1  # Increases player score by one
 @ball.v[:x] = -4
 @score_sound.play  # Play scoring sound
end

@ball.reflect_vertical if @ball.y < 0 || @ball.bottom > self.height  # Reflects ball off ceiling/floor

Now you should create the method for increasing ball speed. To do this just multiply its current movement rate by 1.1 whenever the method is called, in the ball class we’ll be creating later we’ll define how the ball actually moves.

def increase_speed
 @ball.v[:x] = @ball.v[:x] * 1.1  #Increases ball speed by 1.1 times
end

Next you should create the method that’ll allow the human player to move their paddle with the up and down arrow keys. Just check if either the up or down button has been pressed and call the appropriate up or down method (these will be defined in the paddle class later on).

def player_move
 if button_down? Gosu::Button::KbDown  # Checks if down arrow key is pressed
   @player.move_down  # Moves Paddle Down
 end
 if button_down? Gosu::Button::KbUp  # Checks if up arrow is pressed
   @player.move_up  # Moves paddle up
 end

This method also needs to check if the player has reached the edge of the screen and prevent them from moving off the window. This is done by simply checking if the bottom of the paddle has reached a distance greater than the height of the window and if so setting the paddles position to be just at the edge of the window.

@player.bottom = self.height if @player.bottom >= self.height  # Checks if paddle is moved out of bounds

Next you need to define the method that will move the AI paddle. The AI should move at different speeds towards the ball depending on how far away from the ball it is and it should be constantly trying to move towards the ball in the Y direction.

def ai_move  
 pct_move = 0  #  Sets base movement speed of paddle
 distance = @enemy.center_x - @ball.center_x  #  Determines distance between AI and ball
 if distance > self.width / 3  #  Changes Movement Speed based on distance from ball
   pct_move = 0.05
 elsif distance > self.width / 2  #  Changes Movement Speed based on distance from ball
   pct_move = 0.1
 else
   pct_move = 0.14
 end
 diff = @ball.center_y - @enemy.center_y
 @enemy.y += diff * pct_move
 @enemy.top = 0 if @enemy.top &lt;= 0
 @enemy.bottom = self.height if @enemy.bottom >= self.height
end

After this you should create a draw method. This method’s purpose is to simply call on all the other methods which draw the various parts of the game that we’ll be creating after this. These methods are “draw_background, draw_center_line, draw_score, @player.draw, @enemy.draw,” and “@ball.draw”.

def draw
 draw_background
 draw_center_line
 draw_score
 @player.draw
 @enemy.draw
 @ball.draw
end

Now let’s start with the “draw_background” method. This should simply consist of a call to “draw_rect”, a gosu method which allows for the drawing of rectangles. This rectangle should be the size of the screen and black.

def draw_background
 Gosu.draw_rect 0, 0, self.width, self.height, Gosu::Color::BLACK
end

Now lets create the “draw_center_line” method. Using the “draw_line” method from Gosu this should draw a segmented white line with each segment being ten pixels tall.

def draw_center_line
 center_x = self.width / 2
 segment_length = 10
 gap = 10
 color = Gosu::Color::WHITE
 y = 0
 begin
   draw_line center_x, y, color,
             center_x, y + segment_length, color
   y += segment_length + gap
 end while y &lt; self.height
end

We also need a method that will draw the current score at the top of the screen. Use the @font variable from earlier to set the text size to 30 pixels.

def draw_score
 center_x = self.width / 2
 offset = 25
 char_width = 10
 z_order = 100
 @font.draw score[0].to_s, center_x - offset - char_width, offset, z_order
 @font.draw score[1].to_s, center_x + offset, offset, z_order
end

Finally we need to load in our sound effect files. The Sample class from Gosu allows us to easily load in sound files from our projects folder.

def load_sounds
 path = File.expand_path(File.dirname(__FILE__))
 @blip_sound = Gosu::Sample.new(File.join(path, "blip.wav"))
 @boop_sound = Gosu::Sample.new(File.join(path, "boop.wav"))
 @score_sound = Gosu::Sample.new(File.join(path, "score.wav"))
end

Now we can move on to creating our game object class.

Game Object Class

Once you’ve completed Game Window you need to create a class that will be used to represent the game objects. This class should start with attribute accessors for some variables that will be used in it. Attribute accessors allow a variable to be both read and modified by other methods. These variables will represent the x and y coordinates and the height and width of each game object.

attr_accessor 😡
attr_accessor :y
attr_accessor :w
attr_accessor :h

Once you’ve done this you now need to create an initialize method which creates the variables you just made accessors for.

def initialize(x, y, w, h)
 @x = x
 @y = y
 @w = w
 @h = h
end

You need to have a reference point for each side of a game object so you can tell where every part of it is. For the purposes of making pong you’ll need to know where an object’s left and right sides are, its top and bottom, and its vertical and horizontal centres. Each of these can easily be determined with a one line method using the above variables you just defined.

def left
 x
end

def right
 x + w
end

def right=(r)
 self.x = r - w
end

def top
 y
end

def top=(t)
 self.y = t
end

def bottom
 y + h
end

def center_y
 y + h/2
end

def center_x
 x + x/2
end

def bottom=(b)
 self.y = b - h
end

Lastly you need to define that collision method mentioned in the previous section. This will be how we know if the ball has come into contact with the paddles. This method should take one argument which is another game object. It needs to detect if there is any overlap between the two objects either horizontally or vertically and should return true if the overlap does not equal zero.

def collide?(other)
x_overlap = [0, [right, other.right].min - [left, other.left].max].max
y_overlap = [0, [bottom, other.bottom].min - [top, other.top].max].max
x_overlap * y_overlap != 0
end

While the first class we made is pretty specific to Pong this one can be reused in any other 2D game you might create. It has the basics of moving objects, detecting their current screen position, and detecting collision and can easily be added to making it a useful class that you should hang on to for future projects.

Final Thoughts

That wraps up the first part of creating pong with Ruby. In part two we’ll be wrapping up with creating our basic version of the game and then look at some different ways that we can expand on the game and make it more complex.

Good Luck!

Download the source code here.

If you have any questions or comments email me at nick@crumbsofcode.com