
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 <= 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 <= 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 < 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