Last updated: 2010-10-20 22:42:11 -0700
What is This?
I don't post the scripts or the code for each episode on the main website until I've completed the video and uploaded it online. For the Making Games With Ruby series, I plan to complete the writing of the entire series before I create any more videos, so the impatient can read the tutorial here before it comes out in video format.
The scripts for episodes 1 through 3 are sub-par, so I haven't included them here, links to the videos are available on this page: http://manwithcode.com/making-games-with-ruby/.
Please note that the episodes here are as they appear on my computer, and are in a format that's helpful for me, not necesarily you the general public (I mean, check out the outline, that probably doesn't mean anything to you, eh? But it's helpful for me, and I included it here just in case someone else found it useful). Everything is subject to change, it's entirely possible that some of what I've written is wrong.
If you see anything I say that's wrong, is confusing, needs more explanation, if there's any code that doesn't work, or if you have any questions, feel free to email me at tyler@manwithcode.com.
Tyler J. Church
Table of Contents
- Outline
- Ep. 4 - Drawing
- Ep. 5 - Input and Movement
- Ep. 6 - The Ball
- Ep. 7 - Keeping Score
- Ep. 8 - Winning and Playing Again
- Ep. 9 - Game States (Title, About, and Pause)
- Ep. 10 - Sound
- Ep. 11 - Options
Outline
Outline
=======
Disclaimer (put an edited version in episode 13):
There are many ways to make a pong game, this is by far not the only
one, and whether or not it's the best or the worst implementation depends
completely on your perspective. The main goal was to make your learning and
my teaching as easy as possible, we'll have to wait and see to find out
whether or not I succeeded. As this tutorial progresses, you'll probably see
me make some mistakes, implement some hacks, or backtrack and add things
that probably should've been there before. This is all part of the
development process, and as much as I'd like to make everything kosher and
neat, it wouldn't reflect real life, or at least that's my excuse for not
spending my time forever reworking this tutorial. And while it might be
difficult to rework this tutorial, it's not hard to rework and refactor
code. Which is why episode 13 exists, all the things that should've or
could've made it into the tutorial, but didn't.
Something I'd like to cover somewhere:
Just 'cus I'm weird this way, I usually like to keep the code for
simple games all in one file, instead of breaking it up, since I find it
easier to CTRL+F to classes and methods, etc. and I tend to lose track of
things when they're spread across files (though for ROG, the code is split
up based on game states and components, and since my attention seems to stay
in one individual area, it's not a problem for the code to be split across
files). I guess this is the real point:
I split code into files according to attention, it works best for
me, it might not for you.
Something else that might be interesting:
I've kind of fallen into a pattern in the way all my games are
structured. Even games created with my own engine have this same structure,
with the engine interalizing some of the repetiveness. It might be
interesting to research and present the different ways people tend to code
their Ruby-based games. Or maybe it'd only be interesting to me, not sure,
but it's worth looking into either way.
Core
----
Ep. 1 - Intro
* What you'll learn
* What we're using
* What I'm assuming about you
* Why I'm teaching this
Ep. 2 - Setup
* On Windows
* On Linux
* On Mac
Ep. 3 - Basics
Ep. 4 - (WRITTEN) Drawing
Ep. 5 - (WRITTEN) Input and Movement
Ep. 6 - (WRITTEN) The Ball
Ep. 7 - (WRITTEN) Keeping Score
Finishing Touches
-----------------
Ep. 8 - (WRITTEN) Win condition, play again?
Ep. 9 - (WRITTEN) Game States (Title, About, and Pause)
* Use pause screen as a simple example of states
Ep. 10 - (WRITTEN) Sound
* [GOT IT] Popping sound for bounce
* [CAN'T FIND IT] 8-bit-ish sound for score
* [GOT IT] light, simple background music
Ep. 11 - (WRITTEN) Options
+ How high the score can go
+ Ball speed
+ Toggle Music
+ Toggle FX
+ Option Reset
Where to Go From Here
---------------------
Ep. 12 - (TODO) Packaging Your Game ('cus you wanna send it to all your friends!)
* Picking a license
* Creating a README
* rubyscript2exe, ocra, etc.
Ep. 13 - (TODO) Tweaking the Game
* There are many different ways to write pong, this isn't necesarily
the only way, or the best way
* Not starting the ball in the same place w/ the same velocity each
time (see rand())
* Better ball bouncing angle (see: gravipong)
* Mouse controls for the menu
* Different colored players
* Using images for players, background, instead of using primitives
* VS computer
* High score table?
* Rubygame 3 compatability
* Different resolutions
* Fullscreen
* State stack
Ep. 14 - (TODO) Resources
* gamedev.net (no forum dedicated to Ruby, but people there know a
heck of a lot about game development, useful articles too)
* Rubygame.org wiki, forum, documentation
* Me, email me at tyler@manwithcode.com
Possible future
----------------
Managing game states
lots of GameObjects
creating an engine
Automated game testing
Back to Top
Ep. 4 - Drawing
Hello Everybody and welcome to Making Games with Ruby episode 4, Drawing. I'm Tyler, and these videos are brought to you by manwithcode.com.
In this episode we are going to be getting some graphics up on the screen, namely the background and the player paddles.
Lets get started!
GameObjects - Our Foundation
Games have many different types of objects, our pong game will have the background, the player paddles, and the ball. I've found, as others have, that it's useful to create a sort-of lowest common denominator class that has the basic functionality and properties you want every object in the game to have.
For our simple pong game, I've decided that every object will have:
- Position
- Width
- Height
- a Rubygame::Surface
for it's properties, and it will:
- Update every frame
- Draw to the screen
- Be able to handle events
We embody this description by creating a simple class:
It's a pretty simple class, and you should notice that the
GameObject
class doesn't do much, this is intentional. The
GameObject class is meant to be inherited from by classes that will
implement real functionality.
The update
and handle_event
methods are left blank
because there's nothing we want every GameObject to do in either of those
cases, and we will add specific functionality to the classes we create later
on.
The draw_screen
method is worth a little explaination:
The Rubygame::Surface#blit
method is used to draw a surface
onto another surface, blit
takes 3 arguments, the third one is
optional. The two we provide here are:
- Which surface we are drawing to, in this case it's the screen
- And where on that surface to draw the surface, we use the GameObject's x, y position for this.
If you remember algebra, graphing, or other similar things from your school math days, x, y coordinates should be familiar to you, but they work a little differently in computer graphics than they did in math class.
X and y are the distance in pixels from the "origin". The origin is the top left corner of your game window. As you get farther to the right of the origin, x goes up, as you get farther down from the origin, y goes up.
When you draw to the screen, surfaces's x and y positions are measured from their top-left corner.
Here's a picture to help you understand this better:
Background
Now that we have the groundwork in place, we can start to do some more interesting things like drawing the background!
Let's start by making a small skeleton class called Background:
And let's initialize it in the Game class:
Do you see that lonely, unfullfilled draw function in the Game class? Well now it's gonna have some code added to it!
There are a couple of things that you need to know to understand this code. The first is how colors work in Rubygame. Colors are represented as an array of three numbers, these three numbers represent the amount of red, green, and blue that will be mixed together to form the final color. The numbers you can provide range from 0 to 255. In this case we're drawing black, so we set everything to zero.
You can mess around with these numbers and see how to get different colors (you'll have to comment out @background.draw to see the different colors). You can google search "RGB colors" (without quotes) for some lists of different colors.
"But Tyler!" you might be thinking, "this is sooooo weird! Why can't we just
call colors by name?" Well, you can. Instead of passing an array of three
colors to @screen.fill
pass
Rubygame::Color[:black]
. You can change the symbol black to a
different color, like green, grey, pink, magenta, etc. But keep in mind that
not all colors may be available.
So if you can call colors by name, why do I call them by number? First, it's because it's a habit I've gotten into, and habits are hard to break. And second, using numbers allow you to have a finer grain of control over the color, since it's possible that the shade of green Rubygame calls green isn't the shade that you want.
It turns out that since our background takes up the whole screen, we don't
need to fill the screen with any color at all, but I do so anyway because
not all games have a background, and you may need to use
Rubygame::Screen#fill
on one of your own games.
The second line is pretty self-explanitory, we call @background's draw method.
The third needs some explaination. Everything we're drawing, isn't actually
being drawn to the screen, it's being drawn on a different surface that's
off-screen. Rubygame::Surface#flip
displays what we've been
drawing to the actual screen.
Why does Rubygame work this way? Why not draw directly to the screen? If we drew directly to the screen, we'd start seeing things flicker because we're watching them get drawn (we'd see the black background get drawn, then our pong background, then our paddles, then we'd see them drawn over again, on and on forever). This way everything gets drawn as a cohesive whole, and eliminates the flickering problem.
If you've run the game, you'll see that we still have just a plain ole black background, let's fix that by adding some drawing code to the Background class:
If you run it now, you should see a beautiful pong background! So how does it work?
We're creating our background using what's called "graphics primitives",
which are really basic things like lines, boxes, and circles.
Rubygame::Surface#draw_box_s
draws a filled-in box (as opposed
to draw_box
, which draws the outline of a box).
The arguments draw_box_s
takes are:
- The upper-left corner
- The bottom-right corner
- The color to draw with
You probably can see the madness I created with all the addtion and subtraction, I know it's hard to understand, but I did it so I the background would look correct no matter the screen size (change the size of the screen, and see the background strech to the same size! Oooo... Ahhh...).
That's it for the background! Here's how the code should look by now:
Paddle - The Player Class
Compared to everything else, getting the players in the game is relatively simple and easy. First we create the Paddle class:
Create the player and the enemy in Game#initialize
:
And add them into the draw
method:
(Be sure to draw the players after the background or else you won't be able to see them!)
And now we have the two players in the game!
Now, personally, I think it'd look a bit nicer if we centered the players about the y-axis, so here's a simple method to do just that:
Add these lines to Game#initialize
:
And you're all set! We now have our players on the screen!
Here's how the code should look now:
Conclusion
So now there's a little more excitement, we have players and a background! But wait, what's this? The paddles don't move!? Next expisode we're going to start accepting more input from the players and add the ability to move the paddles around.
If you have any questions, comments, or suggestions, leave a comment on this page or email me at tyler@manwithcode.com.
Thank you very much for watching! Goodbye!
Back to Top
Ep. 5 - Input and Movement
Hello Everybody and welcome to Making Games with Ruby episode 5, Input and Movement. I'm Tyler, and these videos are brought to you by manwithcode.com
In this episode we're going to be getting those oh-so-beautiful paddles we drew in the last episode to move! So let's get started!
Handling Keypresses
When the user presses a key down Rubygame sends a KeyDownEvent
to our event queue, when that key is released Rubygame sends us a
KeyUpEvent
. Armed with this knowledge, we can start sending the
paddles events:
And we can start handling them:
Most of the code is pretty easy to understand. The case and when statements
are similar to how we handle QuitEvent
's. The meaning of these
expressions though, might not be immeadiately obvious:
When we're handling KeyDownEvents and KeyUpEvents, the event object has a
quite useful attribute called key, which tells us which key we're
interacting with. The value of event.key
is an integer instead
of a string or symbol like you might expect (this will be fixed in Rubygame
3.0). Rubygame has a nice set of constants defined to deal with this (like
K_UP, K_DOWN, K_a, K_5, K_SPACE, etc.). You can see a list of the constants
in the Rubygame module section of the Rubygame
documentation.
So now pressing the up and down arrows moves the paddles! This isn't without some faults, most noteably that both paddles move to the same keys and that they move only once when the key is pressed, not while the key is being held down.
To fix the second problem, we'll need to have two variables, one that keeps track of if the player is moving up, and one for if the player is moving down. Let's initialize these:
Now when a key is pressed down we'll set the corresponding variable to true,
and when it's released we'll set it to false (we're using
KeyUpEvent
now too!):
And we'll fill in our update method now to respond to this:
If we run the game now, nothing will happen because we haven't plugged in the calls to the pladdles's update methods, so let's do that!:
Now we can run it, and... IT'S ALIVE!!! I mean... They move smoothly now! Yay!
Both paddles still respond to the same keys, luckily it's pretty straight
forward to fix this, though it requires a little bit of bouncing around in
the code. What we'll do is create two variables called @up_key
and @down_key
, and when we create the paddles in
Game#initialize
we'll give each paddle separate keys to react
to.
So here's the parameter changes for Paddle#initialize
:
We also have to change Paddle#handle_event
so it uses these new
variables:
Last, but definately not least, add the keys to the arguments we pass in
Game#initialize
:
Now the left paddle moves with W and S, and the right one moves with the up and down arrows. Cool!
Here's how the code should look so far:
Tweaks
Our game is looking pretty good so far, but there are a few small tweaks that I think should be made. These changes aren't essential to the main gameplay, and you don't have to make them if you don't want to, 'cus hey, it's your game!
Limiting Movement
Right now the paddles can go off the screen and keep going and going as far as they want to, this we cannot have! They must stay on the screen!
It's pretty easy to implement, first we add some parameters to
Paddles#initialize
for the top and bottom movement limits:
And then we add some conditionals for the movement in update
:
And then pass these limits in Game#initialize
in a resolution
independant way:
Closing the Window with ESC
You might've noticed that it's kinda annoying to have to hit the "x" in the top-right corner of the window every time you want to close the game. Quite a few programmers (me included) like to hook up the ESC key to close the game, which is really easy to do now that we know how to handle key presses:
Now we can close the game with ESC, awesome!
One new thing in this piece of code: Rubygame::EventQueue#push
.
#push
allows us to send our own events onto the event queue,
thus saving us from duplicating the quitting code, or separating it out into
a method.
Here's how the code should look now:
Conclusion
That brings us to the end of another episode, I hope you've enjoyed it. Next up we're going to get the staple of all pong games moving around on screen: the ball!
If you have any questions, comments, or suggestions, leave a comment on this page or email me at tyler@manwithcode.com.
Thank you very much for watching! Goodbye!
Back to Top
Ep. 6 - The Ball
Hello Everybody and welcome to Making Games With Ruby episode 6, The Ball. I'm Tyler and these videos are brought to you by manwithcode.com.
In this episode we're going to load up a ball image, and get it boucing around on the screen, so let's get started!
Loading an Image
I've created a simple white circle in Inkscape (a vector graphics program)
and rendered it to a .png file. This will be our ball! You can download it
from this link (right click -> Save As): Ball. Be sure to
put this image in the same folder as game.rb
!
First things first we're going to create a new class for our ball:
And create an instance of it in Game#initialize
:
And add it to Game#draw
:
If you run the game now you can see our ball peeking out from the centerline. Woo hoo! We loaded an image from a file!
The only real new code here is Rubygame::Surface#load
, which
if you can't tell, gets an image from a file and turns it into a
Rubygame::Surface
object. According to Rubygame's
documentation, Rubygame supports these image formats:
- BMP: "Windows Bitmap" format.
- GIF: "Graphics Interchange Format."
- JPG: "Independent JPEG Group" format.
- LBM: "Linear Bitmap" format (?)
- PCX: "PC Paintbrush" format
- PNG: "Portable Network Graphics" format.
- PNM: "Portable Any Map" format. (i.e., PPM, PGM, or PBM)
- TGA: "Truevision TARGA" format.
- TIF: "Tagged Image File Format"
- XCF: "eXperimental Computing Facility" (GIMP native format).
- XPM: "XPixMap" format.
I usually use PNG images since they seem to give me the best file size and image quality. I haven't tested this scientifically, so feel free to prove me wrong or use whatever format that makes you happy!
Bouncing Around On Screen
Now a ball just sitting around isn't very interesting, so our first task is
to get it moving. How will we do this exactly? First we're going to give our
Ball
object a velocity along the x and y axes. Each time
Ball#update
is called we'll move the ball with those
velocities. If we want to change the direction the ball goes, all we have to
do is change the velocity, you'll see the power of this in a few minutes.
So let's add that velocity, shall we?:
And don't forget to call Ball#update
in
Game#update
:
Now the ball moves! But it quickly goes off-screen, never to return. Really we'd like to to bounce around the sides of the screen and off of the paddles. Let's take care of the screen sides now.
Since we'll need some information about the screen, we'll pass the
@screen
object to Ball#update
:
And change the Ball#update
method to utilize this:
It's pretty simple, if the ball hits the left or right side, we reverse the x velocity, if it hits the top or bottom, we reverse the y velocity. Now we have a ball bouncing around the screen! Excelent! But the ball completely ignores those paddles, let's fix this.
Bouncing Against the Paddles
First order of business: Letting the ball know when it hits the paddle. There are a bunch of ways to do this, and there's no clear "best way". I'm going to pick something that's worked well for me in the past, it involves using the Game class as the great overseer of all collisions, and a rather simple method of collision detection.
But wait, collision? That's a pretty broad term. What exactly constitutes a "collision"? To me, and to the collision detection code I'm about to show you, a "collision" is when two objects are touching in any way. Two GameObjects sitting next to each other without any space between them is a collision, overlapping is also a collision, and everything in between.
So in the Game
class we'll create the collision?
method that checks to see whether or not two objects are colliding:
It might look complicated, but it's actually pretty simple. It works by seeing if it can rule out the possibility of a collision. The first line, for example, checks to see if the bottom of obj1 has a y position higher up than the top of obj2, if it does there's no chance that they could be colliding, so the method returns false. If nothing can rule out a collision, then the objects must be colliding, and the method returns true.
Let's put this new piece of code into action!:
Hopefully that code explains itself. But it's completely useless without the
Ball#collision
method:
Now that is a lot of code! Thankfully it's not too complicated. First things first we figure out which side of the screen we're on, which should reveal which paddle we're colliding with. Once that's established, we make sure the ball isn't behind or above the paddle when the collision is detected.
After that's been checked, we move the ball one pixel in front of the paddle (so it isn't continuously colliding with the paddle), and we reverse the ball's direction.
Whew! Done! The ball now bounces off the paddles!
Here's how the code should look after all that:
Conclusion
That brings us to the end of another episode! I hope you liked it! Next up we'll be getting the scoring system in place, which means drawing text on the screen! Yay!
If you have any questions, comments, or suggestions, leave a comment on this page or email me at tyler@manwithcode.com
Thank you very much for watching! Goodbye!
Back to Top
Ep. 7 - Keeping Score
Hello Everybody and welcome to Making Games With Ruby episode 7, Keeping Score. I'm Tyler and these videos are brought to you by manwithcode.com.
In this episode we'll be giving players the ability to score on each other, and we'll display this score using text that we render ourselves, so let's get started!
Getting Text on the Screen
Before we can even think about drawing the text, we need to find a font that will be suitable for our game. I found one I like called "Freshman" which you can download here: http://www.dafont.com/freshman.font
The site says the font is free, but nothing more than that, so my apologies to William Boyd if we're actually not allowed to use this.
So download that font, and put it in the same folder as the other files for the game, and we can get started.
To be able to use text in Ruby game, we have to call
Rubygame::TTF.setup
so put that right after you require
rubygame:
With that done, let's create a class called Text
:
It's a simple class, but it's dominated by two things we haven't yet
encountered: Rubygame::TTF.new
and
Rubygame::TTF#render
.
First, TTF.new
. To use a font, first we have to load it up by
creating a new instance of the Rubygame::TTF class. It's important to note
that I've renamed my font file to just "font.ttf". The size argument is like
the font size in any old word processor, 48 is a good default for now.
TTF#render
takes a string and turns it into a
Rubygame::Surface
for us. It takes four arguments:
- The string to be rendered
- Whether or not to anti-alias the text (which makes it look nicer, so I enabled it)
- The color to render the text in
- (optional) The background color, defaults to transparent
Now let's test it! Add this line to Game#initialize
:
And call the draw method:
Then run it and... Yay! "Hello, World!"
But, there's one problem: For the score we'll often need to change the
string that get's rendered, but we can't do this after the Text
class has been initialized, not easily anyway. Let's fix this!
Crack open the Text
class once more and add this code:
Hopefully this code explains itself. In rerender_text
we set
width and height to the correct values, and then render it like we did
before. Since the rendering is the last thing that takes place in the
method, it returns the rendered surface, so we call
rerender_text
as the surface argument for super
.
We also create our own attr_accessor of sorts with the only difference being
that when text=
is called, we rerender.
Now let's test this:
It works, woo hoo! Now remove all that test code and we can get started on what we really came here for.
Giving the Paddles a Score
Now to give the paddles a score:
Small changes, you'll notice that we're now using all of the arguments for
our Text
class, and that we've created our own attr_accessor
for score. I decided to have the drawing for @score_text
be
handled by the Paddle
class, since I felt there was no real
need to add to the long list of objects in Game#draw
.
If we head back up to Game#initialize
we can pass the two new
arguments to Paddle#new
:
I used percentages of the screen width to hopefully keep the scores in a good position no matter what the resolution is, as well as formatting the arguments so they weren't just looooooooooooooooooooooong lines. Other than that there isn't anything new here.
Scoring
We can see the score, but no matter where the ball goes, the score never changes. Instead of the ball bouncing on the left and right sides of the screen, we want it to give the player who scored a point, and have the ball reset somewhere else. Let's add this:
The collision code for the left and right sides is now separated out so that
the score can be applied to the right place, and Ball
now has a
score
method which reverses the direction of the ball and moves
it.
We also can't forget to pass the player and enemy variables in
Game#update
:
Now players can score on each other! Excellent!
Here's how the code should look so far:
Conclusion
That's all for this episode, I hope you've enjoyed watching it. Next episode we'll make it so that one of the players can actually win, so the game won't just go on and on forever.
If you have any questions, comments, or suggestions, leave a comment on this page, or email me at tyler@manwithcode.com.
Thank you very much for watching! Goodbye!
Back to Top
Ep. 8 - Winning and Playing Again
Hello Everybody and welcome to Making Games With Ruby episode 8, Winning and Playing Again. I'm Tyler and as always these videos are brought to you by manwithcode.com.
In this episode we're going to stop the game from going on and on forever by letting one of the players win, and then we'll let the players choose whether they want to play again or quit.
Winning
For a player to win, they have to achieve some goal, for our game it will be a certain number of points, let's go with 3. Not an especially magical number by any means, but it keeps the game short and sweet, which I think is a good thing.
When a player wins we'll display some text stating "Player X Wins", with 'X' being either player one or player two. But before we can do that, we need to be able to detect when a player has won, so let's do that:
When a player wins it prints a message out to the console and exits the game, it works, but it's crude. Let's add some text to celebrate the player (this is gonna involve a lot of jumping around, so please bear with me) :
And a Game#win
method to set this all up right:
Now this code uses the center_y
method that the Paddles use,
but Text
definately isn't a Paddle
, so let's move
that method into GameObject
, and create a center_x
method.
And make sure to draw the text:
When someone wins we hide everything else, so nothing gets in the way of the win screen. And the game is over anyway, no need to show anything else, right? Right, but this is deceptive, because even though we can't see it, the ball is still moving around, we need to stop it from updating so it won't score any more points:
Whew! That was a lot of code... Get through it ok? I barely made made it myself. But now we can see beautiful text displayed when a player wins! Yay! You'll notice that I snuck in the "Play Again" text, which dutifully informs us to press Y or N depending on whether we want to play again, except... it doesn't work, let's fix that!:
Playing Again
Implementing this is a relatively simple handling of a
KeyDownEvent
in Game#update
:
If the game has been won, and someone presses the 'Y' key, we reset the game
to the way it's found when the game first starts. And if someone presses
'N', we call Rubygame.quit
which makes sure Rubygame and SDL
clean up nicely, and then we call Ruby's exit
method,
terminating the game.
Here's how the code looks now:
Conclusion
That about wraps it up for this episode, and most all of the gameplay aspects of this game, next episode we're going to be creating the different screens needed to call this a complete game, namely the title screen, pause screen, about/credits, and help.
If you have any questions, comments, or suggestions, feel free to leave a comment on this page, or email me at tyler@manwithcode.com.
Thank you very much for watching! Goodbye!
Back to Top
Ep. 9 - Game States (Title, About, and Pause)
Hello Everybody and welcome to Making Games With Ruby episode 9, Game States. I'm Tyler and these videos are brought to you by manwithcode.com.
In this episode we're going to be talking about a game programming concept called "game states", which will allow us to easily impliment a pause screen, title screen, and about screen.
Game States
Now what exactly are game states? And why would we want to use them? Game
states (sometimes also called stages, scenes, rooms, and probably other
things too) are a way of separating and managing the different sections of
functionality in a game. By that I mean, a title screen will work
differently than the pause screen, and both will work differently than the
main gameplay state. Managing all that functionality would be a huge hassle
with the current structure of our code, involving any number of extra
variables and huge if
blocks. The solution is to implement game
states.
How do we go about doing this? First we have to separate out our state-specific code, and code that will be shared between states, into their own files.
Create a new folder named lib
and create the files
shared.rb
and ingame.rb
inside of it.
shared.rb
will hold any code that needs to be shared between
game states, which would be the GameObject
class and the
Text
class. So here's the content of
lib/shared.rb
:
Because we're going through a restructuring of the game files already, I've
decided it would be a good idea to keep all our non-code assets (things like
graphics, font files, and later on sounds) in their own folder named
media
. You can see in lib/shared.rb
that the load
paths of ball.png
and font.ttf
have changed to
media/ball.png
and media/font.ttf
, so please move
the files themselves to reflect this.
We also need to move the game specific code into lib/ingame.rb
,
and put it in a class called InGame
:
You'll notice that pretty much everything is the same, except for when we
handle events; the code to handle QuitEvent
's and when the
player presses escape have disappeared. And you'll also see that we get
@game
, @screen
, and @queue
from a
variable called game
, instead of creating them ourselves. That
stuff is still handled by the Game
class, which we'll get to
right now.
game.rb
:
game.rb
warrents some explanation. First we've added two new
require
's for shared.rb
and
ingame.rb
. We now have attr_accessors for screen, queue, clock,
and state. Game#initialize
works basically the same way it used
to, except we now initialize @state
and
@state_buffer
to nil.
The handling of events in Game#update
is the same, except
you'll see that instead of using @queue.each
, we're using
@queue.peek_each
, why? Well each
deletes the
events after we've looped over them, working off of the assumption that
we've successfully handled the events, and won't need to see them again.
peek_each
on the other hand let's us loop over all the events
in the event queue, but doesn't delete them afterwords, this way we can
handle QuitEvent
's in the Game
class, and the
current game state gets to handle the rest of the events.
Game#update
and Game#draw
both call the
corresponding methods for the current game state.
The Game
class now has a Game#switch_state
method,
which allows us to change the game state (and since there is no default game
state, Game#switch_state
has to be called before
Game#run!
). Now, why is it that we have a variable called
@state_buffer
? Why don't we just change the value of
@state
as soon as Game#switch_state
is called? The
reason I did it this way is because Game#switch_state
is almost
garunteed to be called in a game state's update
method, which
probably won't have finished running when it calls
Game#switch_state
. It just seems wrong to me for the value of
@state
to be something different than what the currently
running state actually is. In practice it might not cause any issues, but I
believe it keeps things cleaner and more consistant.
Last, but not least, we create an instance of the Game
class,
and switch states into a new instance of InGame
, which starts
our game running! Woo hoo! Nothing has changed as far as the player is
concerned, but we have now set the stage to easily add more game states.
Oh, and if the previous activities of moving files around got you a little confused, here's how the directory structure looks now:
Pause Screen
With all that code-shuffling out of the way, we can now get around to adding some new game features, namely: the pause screen! The pause screen will be pretty simple, while you're in the game you can get to it by pressing P, and you can exit it by pressing P again.
First let's require in lib/pause.rb
:
All states are classes, so here's the Pause
class (in
lib/pause.rb
):
There's nothing here that's really new or noteworty, except for the fact
that we switch back to a game state that already exists. We don't need to
create a new one, and all the objects and everything in
old_state
are perfectly preserved!
In InGame
we need to hook up the P button to switch to the
pause state:
Again, it's pretty simple, we create a new instance of the
Pause
class, give it @game
, and the current
instance of InGame
as the old state, and the pause screen
works!
Title Screen
With two relatively easy states under our belts, let's try something a little more difficult: the title screen.
First things first, let's create the file lib/title.rb
and load
that up in game.rb
:
And let's make Title
the state that's switched to at startup:
Oh, what's in a name? Err... I mean, what's in a title screen? Well, I
figure having the title of the game, a menu choice to play the game, one to
look at the about/credits screen, and one to quit the game seems good to me,
so let's put all that in our Title
class.
Before we get into any of the menu logic, let's just get the graphics up on screen:
And now we have a pretty good looking menu! Not award winning by any means, but it'll do it's job well.
Now onto the menu logic, which gets a little more complicated:
The most important logic here is that for handle_event
, which
increments @choice
when down is pressed, and decrements it when
up is pressed. @choice
is available to the outside world, so it
can be read by our Title
class to enable the proper state
changes.
The more flashy side of our menu takes up a few more lines of code.
update
isn't too complicated, it's main purpose in life is to
move the two little paddles that visualize our menu selection to the correct
place. I chose to do it this way, instead of the simpler method of instantly
moving the selectors, just because it looks cooler (and I believe that the
initial movement makes the selector's purpose a bit more obvious to the
player). I feel it's important to note the not-so-obvious purpose of the
first if's in update
, which will set the position exactly to
the target position if the if evaluates to true. Now why would we need to do
this? Wouldn't just adding the speed get the selectors to where we want them
to be? Well, as it turns out, it will, but most of the time we'll be off by
a few pixels, causing the selectors to jitter back and forth because they
never get to the exact position they want to be in. Just figured I'd make a
note of that.
Now let's add our menu selector to the game:
If we run it now, we can see our menu selector in action! Cool! Except, we can't actually pick anything... Only move between choices, let's fix that!:
This code isn't very complicated, whenever the player presses Enter (or
Return, if you prefer to call it that, as Rubygame does) we check to see
what choice the player has selected, and either change to the correct state
or quit the game completely. Notice that the second selection (which leads
to the About
state), hasn't actually been implemented, and will
cause the game to crash if you try to choose it. We'll get to the
About
state in the upcoming section.
On the surface, it would appear that we're finished with the Title state,
and for the most part, we are! But there's the issue of quitting the game:
We can currently quit the game from anywhere by pressing escape, by pressing
N when a game ends, as well as by pressing the quit button on the
Title
state, and there's no way to get back to the menu! So
let's make it so the game can only be quit from the title screen, and that
pressing Escape will quit the Pause
and InGame
states.
First, the InGame
state:
Now, the Pause
state:
One could almost be led to believe that this would work, except it doesn't,
the Game
class still intercepts the escape button being
pressed, and quits the game completely no matter what the state is. We can
fix this by adding a simple "and
" to the if statement, though
to me this seems a little hacky, so I added a notice in the comments about
it:
Ok, now we're done with the Title
state! Now we just add in the
About
state, and we're done!
About Screen
The About screen simply shows the credits for the game, there's nothing here that's new or worthy of explaination, so here's the source code:
This is your game too, so feel free to change the credits to whatever you want!
Oh, and we can't forget to load up ./lib/about.rb
in
game.rb
:
And we're done! Yay! :D
A Last Tweak
Or almost done... You see, there's one thing about our current menu setup
that bothers me, no matter which state we leave (About
or
InGame
), the menu choice always goes back to "Play Game", which
I think is a little odd. If you just came back from About, you should still
be on About!
For such a relatively small issue, there is a surprising number of ways we can solve it.
- We can let the game states switching to
Title
choose which menu option to start on. - We can use a class variable in
Title
to keep track of the option that was chosen before the state was switched. - We can implement a "state stack" that would provide an easier way of managing states that we want to be persistent.
Option three, the state stack, is the cleanest and most "proper" way of
solving this problem, but it would take the longest, and this episode has
gone on for long enough. Option one necesitates that other game states have
knowledge of the Title
state, which they shouldn't have to deal
with.
That leaves option two, giving Title
a class variable:
Now when the player presses Enter, the current choice is saved into
@@choice
. This same variable gets passed in to
Selector#initialize
's new choice argument. Maybe it's a little hacky, but
it's quick, simple, and it works!
Now we're done! :D
Here's how the directory structure should look after all this:
And the following is the full source for all the files (excluding
lib/about.rb
, which was listed above):
game.rb
:
lib/shared.rb
:
lib/ingame.rb
:
lib/pause.rb
:
lib/title.rb
:
Conclusion
That brings us to the end of another episode, next up we'll be adding sound to our game, which should be lots of fun!
If you have any questions, comments, or suggestions, feel free to leave a comment on this page, or email me at tyler@manwithcode.com.
Thank you very much for watching! Goodbye!
Back to Top
Ep. 10 - Sound
Hello Everybody and welcome to Making Games With Ruby episode 10, Sound. I'm Tyler and as always these videos are brought to you by manwithcode.com
In this episode we're going to be adding some sound to our thus far silent game. Sound is essential to any good game, and adding it is drop dead simple, so let's get started!
The Sound Files
First things first, if we're going to be playing some sound in our game, we'll need some sound files to play. According to Rubygame's documentation, Rubygame supports the following audio formats:
- WAVE
- MOD
- MIDI
- OGG
- MP3
Because it's the easiest for me, relatively ubiquitous, and has a decent compression rate, we'll be using the ogg vorbis format.
Our Pong game will have two different sounds, a piece of background music, and a popping noise sound effect for when the ball hits the paddles or the sides of the screen. I made the sound effect myself, but not the background music. The background music is RoboWizard [EC] by the user EmperorCharlemagne on newgrounds.com, and is released under the Creative Commons Attribution-Noncommercial-ShareAlike license. I'm releasing my sound effect into the public domain.
And with all that out of the way, here's a direct link to both files, save
these into your game's media
folder (right click->Save As):
Pop!
Alright, let's start with the easier of the two, our sound effect. Basically
we want the ball to play the popping sound whenever it hits a paddle or the
top or bottom sides of the screen. Before we can do that, we need to load
the sound, so crack open lib/ingame.rb
and add the
@hit_sound
variable to the Ball
:
Yep, it's that's all it takes to load up a sound file! And playing it is as
easy as calling @hit_sound.play
:
And that's it! Now our ball plays a lovely popping noise whenever it hits something!
Our Background Musician
Our background music will work in almost exactly the same way that our sound
effect did, execpt for some small differences. The first difference that
you'll notice is that we'll be using the Rubygame::Music
class
instead of the Rubygame::Sound
class. The Music
class allows us to manipulate the sound file in a few extra ways that the
Sound
class didn't allow, and it also loads the sound file more
efficiently than the Sound
class does (by keeping only part of
the file in memory at a time), because music files are often larger than
their sound effect counterparts.
The music
variable will be a class variable so we don't keep
reloading it each time we enter the InGame
state, and so we can
keep track of where the music left off. Ideally we would create a music
manager of somekind, since class variables are kind of hackish, but this'll
work for now:
It works! But not quite perfectly. First, the music keeps playing after we
leave the InGame
state, it should pause when we leave, and then
resume when we come back. Second, the background music is a loop, meaning
that once we reach the end of the song, we're supposed to seamlessly return
to the beginning, but this doesn't happen.
To fix the first one, we'll have to take a brief tangent, we'll get back to
the second problem in a minute. Remove the call to @@music.play
and then we'll get going.
Design Flaw: There is No State Class
To properly manage the pausing and unpausing of the music, we need to have
the Game
class tell the InGame
state when the
state is changing. Since this could be useful in other contexts, we'll want
the Game
state to inform all relevant states when a state
change takes place, not just our InGame
class.
To do this, the Game
class will call a
state_change
method. Not all states will need to respond to the
state changing, and it seems kind of ridiculous and sloppy to be defining a
blank state_change
method in all the states other than
InGame
. Let's take a step back for a second, and think about
why we're being forced to do this, and if there's a better way. We're having
to do this because our states need to respond to all the methods that we've
decided they should, at this point it's update
,
draw
, and state_change
. There's no formal
definition of this, it only exists within our minds, and it's reflected in
the game states we currently have implemented. Having no formal definition,
and having to implement blank methods are design flaws, but luckily the fix
is pretty easy, we just need to implement a State
class for all
our other states to inherit from.
Load up lib/shared.rb
, and add the following class definition:
And change all the game states to inherit from this class:
lib/pause.rb
:
lib/title.rb
:
lib/about.rb
:
lib/ingame.rb
:
There! Now we can add new functionality to all State
's easily
and relatively effortlessly. To be honest, the State
class
probably should've existed as soon as we added game states, but I didn't
think of it. 'Tis the joy of programming, mistakes are made, but it's
usually easy to recover.
Changing State? I'll Alert the Media
With that out of the way, the Game
class can now inform the
states when a state change is taking place. Open up game.rb
,
and edit Game#switch_state
so it looks like this:
The changes are pretty simple, with a call to state_change
on
the state that we're changing out of, and another call to the state we're
going into, as indicated by the argument we pass. This works, but it doesn't
actually do anything from the player's perspective, let's change that by
implementing InGame#state_change
:
This one's another easy one, we pause the music when we're changing out of
the InGame
state. If we're changing into the
InGame
state, we check to see if the music is paused (and thus
had started playing previously), if it is paused, we unpause it, if it's
not, we start playing it from the beginning. Remember how we had the issue
with the background music not looping? The :repeats
option on
@@music.play
fixes this. :repeats
sets the number
of times the music will repeat, -1
makes it repeat
indefinitely.
And, woah, what's this? The music plays and pauses? That must mean... We're done! Not that difficult, eh?
Notes on Sound
While we might be done for now, I still want to urge you to check out the
documentation on Rubygame::Sound
and
Rubygame::Music
, and see all the available options. Two things
you're probably be most interested in are adding a fade effect to sounds,
and changing the volume level that they play at.
Also, keep in mind that while you can load and play as many
Rubygame::Sound
's as you want, you can only ever have one
Rubygame::Music
playing at a time.
Source
Here's the full source for all the files thus far:
Directory Structure:
game.rb:
lib/shared.rb:
lib/ingame.rb:
lib/about.rb:
lib/pause.rb:
lib/title.rb:
Conclusion
And that's it for this episode, are those sounds annoying you yet? Are you starting to wish you had a convenient way to toggle them on or off in the game? We'll you're in luck! Next episode we'll be adding options to our game.
If you have any questions, comments, or suggestions, feel free to leave a comment on this page, or email me at tyler@manwithcode.com.
Thank you very much for watching! Goodbye!
Back to Top
Ep. 11 - Options
Hello Everybody and welcome to Making Games With Ruby episode 11, Options. I'm Tyler and these videos are brought to you by manwithcode.com.
Not everyone will want to play our pong game in the same way. Some might want the ball to move faster or slower, some might want the winning score to be higher, some might want to disable the sound effects or background music. Today, our goal is to make all this possible, so let's get started!
Creating the Conf File
So how does one go about making a game configurable? On the surface, it's pretty easy, we just add some variables and allow the player to change them. The only problem is that as soon as the game is closed, we lose any customizations that the player made. Not good.
The solution is to keep the configuration inside a file. We could create our own custom configuration file reader and writer, but that's a hassle we don't need to deal with. YAML to the rescue! What's YAML, you ask? YAML is two things: First and foremost, it's a data format that's readable and writable by both humans and computers. Second, it's a library in Ruby's standard library that makes it really easy to store the data our programs use in the YAML format, and then later reload that data into our program, which is exactly what we need!
Crack open irb
and we'll create our configuration file:
It's pretty easy, the only thing that's really new here is
YAML.dump
, which turns our conf hash into the text of the YAML
file, which we then save to lib/conf.yml
.
Here's what the file looks like:
We can load up the file just as easily:
Which gives us exactly the data we saved! Excellent!
Using the Conf File
Now that we have a conf file, the ways we could go about making it useful to
us are many, but I figure that the easiest way is to have a constant named
Conf
that holds our settings, and a method that saves out the
settings to a file.
Create the file lib/conf.rb
, and add these lines:
And be sure to require lib/conf.rb
in game.rb
:
Now that the settings have been loaded, we can use them! Let's start with
the music, open up lib/ingame.rb
, goto
InGame#state_change
, and wrap it in this if
statement:
Next up, the ball's sound effect:
The winning score:
And lastly, the ball speed:
And that's all there is to it! Try changing the configuration values in
lib/conf.yml
and see what effect it has. Pretty cool, eh?
Building the Options Screen
Sure, it's easy enough for us to edit lib/conf.yml
, but our
players sure as hell won't want to have to do it, not to mention that we
have to restart the game to see the changes in the configuration. What we
need is an Options screen, so let's start building one!
First up, we need to add an "Options" entry to the Title
screen:
Basically the choices just got shuffled around, so there isn't much here that's worthy of explanation. One thing I would like to point out is this:
This is one of the lesser-known features of Ruby, basically it takes the items in an array and places them into another array (such as an argument list, or a literal array). So this:
Becomes this:
Alright, so now we have the entry on the Title
screen, now
let's start building the Options
screen. Create
lib/options.rb
and add this skeleton for the
Options
state:
And require in options.rb
in game.rb
:
Now we need to think about how our options screen is going to look. In
essence it'll be a list of the four configuration settings we have, that the
player should be able to scroll through and change. We could tool ourselves
a new mechanism to let the player select among items from a list, but if
you'll remember, we've already written one for the Title
screen! So take the code for Selector
in
lib/title.rb
and move it over to lib/shared.rb
so
it's clear that it's meant to be shared among game states, and we can get
started using it!
We really have three different types of options the player can alter. One is a simple true/false or on/off, the music is either playing or it's not. Another is a list or a range of choices, like how high the winning score goes, or how fast the ball goes. The third is just a reset, which restores the options to what they originally were.
Just because it's the simplest, let's start with the OnOff
class:
It's not scary at all, see? OnOff#initialize
just takes it's
position, text size, the prefix (i.e. the name of the option), and the
option in Conf
that it'll be altering. OnOff#cycle
changes flips the right option and displays the change. If the
self.text=
part looks a little odd to you, don't worry, it's
just like changing the text for any other instance of the Text
class, we have to call that method so we're sure the text gets rerendered
correctly. Hopefully everything else is self-explanatory.
Now let's start using our new OnOff
class!:
There we go! Not too difficult! Quite like our Title
class,
really.
Next up is the control for options that have a list of choices:
Strikingly similar to our OnOff
class, except it cycles through
a list of choices, not too difficult.
Now let's use the new List
class:
Excellent! Now we can manipulate all the options! You might be wondering why
excatly one of the options for @winning_score
is
-1
, how could the score ever get to -1
? Well,
that's the point, it can't, so when the winning score is set to
-1
, the game will never end, and players can play for as long
as they want to!
Now that the players can change every option that we've made available, it
stands to reason that we should also implement a way to reset them to their
defaults. First things first, we should add a reset
method to
Conf
in lib/conf.rb
:
And a Reset
control in the Options screen to take advantage of
this:
This Reset
class takes advantage of reset
methods
in OnOff
and List
that haven't been implemented
yet, so let's implement them!:
Basically all this does is regenerate the text for the controls, as well as
resetting the internal @choice
variable in List
.
You'll notice that Reset
itself has a blank reset
method, since it'll be included in the list of options, even though it isn't
technically an option itself.
Now we can't forget to actually instantiate Reset
:
And we're done! We can now alter all the options, and reset them back to their defaults! That's all there is to the Options screen!
Source
Here's the full source for the files so far:
Directory Structure:
game.rb:
lib/conf.rb:
lib/conf.yml:
lib/ingame.rb:
lib/shared.rb:
lib/title.rb:
lib/options.rb:
Conclusion
I hope you enjoyed this episode! The end of this episode actually marks the end of the core development on our Pong game, next episode we'll be talking about packaging the game so you can distribute it to friends, family, paying customers, or whoever else you want to!
If you have any questions, comments, or suggestions, feel free to leave a comment on this page, or email me at tyler@manwithcode.com.
Thank you very much for watching! Goodbye!
Back to Top