noobtuts

C++ 2D Pong Game

c-plus-plus-2d-pong-game-preview
This tutorial shows how to make a C++ pong game in the easiest way possible. The end result are about 150 lines of code in a single source file without any complicated project-, linker- or compiler settings. It uses OpenGL and it works without any crazy math!

Foreword

This pong game will be really easy to make (just as we like it). We will go through the process of setting up our C++ development environment, creating a new project and then writing function by function until our pong game is finished.

Project Setup

Visual Studio 2008

We will use the Visual Studio 2008 Express IDE to make our game. If the link doesn't work anymore, just Google for "Visual Studio 2008 Express Download". Newer versions might work too, however older versions would require a lot of complicated setup so 2008 is the way to go.

Once installed, we open Visual Studio and select File->New->Project from the top menu:
visual-studio-2008-started

Now we select Win32 as Project type and then Win32 Console Application as Template. The last step is to enter a project name and the location where it should be saved:
visual-studio-2008-new-project

After pressing OK a new window appears where we press Finish. Our project was now created.

Changing _TCHAR* to char**

We see the pong.cpp file right in front of us like this:

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    return 0;
}

Without thinking too much about it, we will change the _TCHAR* thing to char** now:

#include "stdafx.h"

int _tmain(int argc, char** argv) {
    return 0;
}

Note: make sure to also remove the [] brackets behind argv.

If we press F5 (or the green play button), the project should compile fine and a black console window should pop up for a second or so.

OpenGL and GLUT

We want to use the OpenGL graphics library for our project. OpenGL is the way to go when it comes to graphics libraries. It works on all kinds of systems (phones, mac, windows, Linux, ...) and once understood it's a lot of fun to work with.

To make our lives easier we will also use the GLUT library. It just provides us with a few more OpenGL functions that would be a bit harder to implement otherwise (things like drawing text or creating a window).

All we have to do to make OpenGL and GLUT work are two things:

1. Download the needed Files, extract them and place them in our project in the same directory that holds the pong.cpp file (in our example it's C:/pong/pong/). Our project directory should then look like this:
project-directory-with-freeglut

2. Include OpenGL and GLUT (and a few more things) at the top of our source code like this:

#include "stdafx.h"

#include <string>
#include <windows.h>
#include <iostream>
#include <conio.h>
#include <sstream>
#include <math.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include "GL/freeglut.h"
#pragma comment(lib, "OpenGL32.lib")

If we run the project, everything should be fine and the console window should pop up for a second again. Now we can start making the game.

Note: if it says "freeglut.dll not found" then also copy the freeglut.dll file to the Debug directory.

The Main Method

So far the only function in our project is the main function:

int _tmain(int argc, char** argv) {
    return 0;
}

This is the program entry point, which is just the first thing that happens when we run it.

Creating the OpenGL Window

We want to see more than a console popping up for a second, so let's create the OpenGL window. Thanks to the GLUT library, this is incredibly easy:

// window size and update rate (60 fps)
int width = 500;
int height = 200;
int interval = 1000 / 60;

// program entry point
int _tmain(int argc, char** argv) {
    // initialize opengl (via glut)
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(width, height);
    glutCreateWindow("noobtuts.com Pong");

    // start the whole thing
    glutMainLoop();
    return 0;
}

If we save everything and run the game, we can now see a black window already:
opengl-window

Update and Draw

The main parts of any game loop are the update and draw functions. The update function will calculate the ball and racket movement and things like keyhandling. The draw function will just throw everything at the screen so we can actually see something.

All we have to do is create them and tell GLUT that it should use them. It will then call them automatically all the time.

So let's create the update and draw functions first:

void draw() {
    // clear (has to be done at the beginning)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    // ToDo: draw our scene

    // swap buffers (has to be done at the end)
    glutSwapBuffers();
}

void update(int value) {
   // Call update() again in 'interval' milliseconds
   glutTimerFunc(interval, update, 0);

   // Redisplay frame
   glutPostRedisplay();
}

Those functions already contain a few gl and glut function calls. They are just the standard things that we have to do so everything works properly. There is no need to worry about them too much, as they have nothing to do with the gameplay.

So let's tell GLUT to use those functions. We just have to modify our main function again:

// program entry point
int _tmain(int argc, char** argv) {
    // initialize opengl (via glut)
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(width, height);
    glutCreateWindow("noobtuts.com Pong");

    // Register callback functions  
    glutDisplayFunc(draw);
    glutTimerFunc(interval, update, 0);

    // start the whole thing
    glutMainLoop();
    return 0;
}

We just told GLUT to use our draw function for drawing and our update function for updating. The concept of telling something to call a certain function is called Callback.

So when we run the game now, we still only see a black window. But in the background, update and draw are already called all the time (about 60 times per second).

The 2D Mode and the Color

OpenGL can be used to draw in 2D and in 3D. We want to make a 2D game, so obviously we have to tell OpenGL that. We will create a new enable2D function that will do all the OpenGL configurations that are needed in order to let things appear in 2D. We will just take the function as it is, without worrying too much about whatever crazy math is behind it:

void enable2D(int width, int height) {
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0f, width, 0.0f, height, 0.0f, 1.0f);
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity();
}

Now let's go back to our main function and call the recently created enable2D function once:

// program entry point
int _tmain(int argc, char** argv) {
    // initialize opengl (via glut)
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(width, height);
    glutCreateWindow("noobtuts.com Pong");

    // Register callback functions  
    glutDisplayFunc(draw);
    glutTimerFunc(interval, update, 0);

    // setup scene to 2d mode and set draw color to white
    enable2D(width, height);
    glColor3f(1.0f, 1.0f, 1.0f);

    // start the whole thing
    glutMainLoop();
    return 0;
}

Besides that, we also want to draw our ball and our rackets in white, hence the glColor3f function call in there. The glColor3f function takes three parameters, which are a red, green and blue factor. The factors are between 0 and 1. So if we would want to draw everything in red, we would call glColor3f(1.0f, 0.0f, 0.0f) instead.

That's it for the OpenGL stuff, now it's time to work on the gameplay!

The Score

Let's add a score like "1:3" to our game so the players know who is currently winning.

At first we will declare two score variables just where we declared our window width and height variables. We will have one score for the left player (the one with the left racket) and one for the right player (the one with the right racket):

// window size and update rate (60 fps)
int width = 500;
int height = 200;
int interval = 1000 / 60;

// score
int score_left = 0;
int score_right = 0;

Now we have a score, but we still have to draw it in order to see something. Again GLUT makes our lives easier here. Drawing a text on the screen works as simple as that:

void drawText(float x, float y, std::string text) {
    glRasterPos2f(x, y);
    glutBitmapString(GLUT_BITMAP_8_BY_13, (const unsigned char*)text.c_str());
}

The code draws the text at the position (x, y). Here is how the coordinates work in OpenGL:
opengl-coordinates
This means that the point (0, 0) (which means that x is 0 and y is 0) is at the bottom left of the window. A point like (500, 0) would be at the bottom right, a point like (0, 500) would be at the top left and a point like (500, 500) would be at the top right of the window.

So let's use our drawText function in our draw function in order to draw our score:

void draw() {
    // clear (has to be done at the beginning)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    // TODO draw our scene

    // draw score
    drawText(width / 2 - 10, height - 15, int2str(score_left) + ":" + int2str(score_right));

    // swap buffers (has to be done at the end)
    glutSwapBuffers();
}

This draws a score like "1:3" at the top center of the window (that's why the x coordinate is roughly width/2 and the y coordinate is roughly height). Since we want to make a game in C++, we have to convert our integer score values to a string in order to use it with our drawText function. We did this with int2str(score). Here is the int2str function, feel free to use it any time you want to convert a integer value to a string:

std::string int2str(int x) {
    // converts int to string
    std::stringstream ss;
    ss << x;
    return ss.str( );
}

Note: if this throws an error when compiling, make sure you included sstream at the top.

We really want to see something now, so let's run the program. Here is how it looks:
pong-score

Awesome, seems like all the OpenGL and GLUT stuff was worth it.

The Rackets

Each player needs a racket. One at the left area of the window and one at the right area. Let's implement it!

Racket Variables

We need a few variables again. We have to save the position and the size of each racket. But to save us some work, we will only save the size once instead of saving it for each player:

// rackets in general
int racket_width = 10;
int racket_height = 80;
int racket_speed = 3;

// left racket position
float racket_left_x = 10.0f;
float racket_left_y = 50.0f;

// right racket position
float racket_right_x = width - racket_width - 10;
float racket_right_y = 50;

Note: add this to our variables area again at the top, where we also stored the window size and the score.

As we can see, the left racket should be at (10, 50) which is at the left of the screen and the right racket should be at (width - racket_width - 10, 50) which is at the right of the screen. We used width - racket_width in order to always have it at the right, independent of the window width (If we would change the window width now, the racket would still be perfectly at the right).

Drawing the Rackets

Again, we actually want to see something. The rackets are just simple rectangles. We can draw a rectangle in OpenGL like this:

void drawRect(float x, float y, float width, float height) {
    glBegin(GL_QUADS);
        glVertex2f(x, y);
        glVertex2f(x + width, y);
        glVertex2f(x + width, y + height);
        glVertex2f(x, y + height);
    glEnd();
}

The function just tells OpenGL to begin drawing a quad, then it tells OpenGL the four points of it via glVertex2f(x, y) and then it tells OpenGL that we are done by calling glEnd().

That's all there is too it. Lines, triangles and all kinds of other shapes can be drawn almost the same way.

So let's go back to our draw function and use the recently created drawRect function in order to draw our two rackets:

void draw() {
    // clear (has to be done at the beginning)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    // draw rackets
    drawRect(racket_left_x, racket_left_y, racket_width, racket_height);
    drawRect(racket_right_x, racket_right_y, racket_width, racket_height);

    // draw score
    drawText(width / 2 - 10, height - 15,
             int2str(score_left) + ":" + int2str(score_right));

    // swap buffers (has to be done at the end)
    glutSwapBuffers();
}

There really isn't anything complicated to it, just two calls to drawRect with the racket variables. If we press run, we can already see where the whole thing is going:
pong-rackets

Racket Controls

It's kinda boring to play pong without the ability to move the rackets up and down, so let's add some keyboard handling.

We will use the function GetAsyncKeyState to find out if a key is pressed or not. We will check the W / S keys (for left racket controls) and UpArrow / DownArrow keys (for right racket controls). If they were pressed, we want to increase / decrease the racket's y position.

We declared a racket_speed variable before, this will be the factor by which the racket's y position is increased or decreased. It's really simple, we just add a keyboard function that checks if the keys are down and then call it once every time the game gets updated (in our already existing update function):

void keyboard() {
    // left racket
    if (GetAsyncKeyState(VK_W)) racket_left_y += racket_speed;
    if (GetAsyncKeyState(VK_S)) racket_left_y -= racket_speed;
   
    // right racket
    if (GetAsyncKeyState(VK_UP)) racket_right_y += racket_speed;
    if (GetAsyncKeyState(VK_DOWN)) racket_right_y -= racket_speed;
}

void update(int value) {
   // input handling
   keyboard();

   // Call update() again in 'interval' milliseconds
   glutTimerFunc(interval, update, 0);

   // Redisplay frame
   glutPostRedisplay();
}

Note: something like a += b is just a shorter version of a = a + b.

If we try to run this code we get errors like "VK_W: undeclared identifier". For some reason we have to define the values of the W and S key manually, it's really easy though. We just copy them from the Virtual Keycodes site and add this code to the top of our source code (for example where we defined our window size):

// keycodes
#define VK_W 0x57
#define VK_S 0x53

If we run it now, we can move the rackets up and down perfectly!

The Ball

Ball Variables

As usual we need a few variables to represent the ball. It needs a position (x and y as usual) which is centered in the screen, a fly direction (dir_x and dir_y), a size and a certain fly speed:

// ball
float ball_pos_x = width / 2;
float ball_pos_y = height / 2;
float ball_dir_x = -1.0f;
float ball_dir_y = 0.0f;
int ball_size = 8;
int ball_speed = 2;

Let's talk about the ball_dir for a second. The ball_dir_x and ball_dir_y variables describe in which direction the ball is currently flying. For example, if x is -1 and y is 0, this means that the ball is flying straight towards the left. If x and y would be 1 this would mean that the ball is flying towards the top-right. Please take a look at the OpenGL Coordinates picture above if there is anything unclear about this.

Drawing the Ball

We already have a drawRect function so we might as well use it to draw the ball. All we have to do is add the following code to our draw function (where we already draw the rackets and the score):

// draw ball
drawRect(ball_pos_x - ball_size / 2, ball_pos_y - ball_size / 2, ball_size, ball_size);

This draws the ball centered at its position with its size. If you find that code to complicated, feel free to use the following less exact but simpler one:

// draw ball (easy version, but not entirely centered)
drawRect(ball_pos_x, ball_pos_y, ball_size, ball_size);

If we press run, we can see the ball already:
pong-ball

Let the Ball fly

Since we already have the ball's fly direction all set up in our ball_dir_x and ball_dir_y variables, we don't have to do much in order to make it fly to the current direction.

To keep everything nice and clear we will add a new function: updateBall which moves the ball a bit into its direction in each update call:

void updateBall() {
    // fly a bit
    ball_pos_x += ball_dir_x * ball_speed;
    ball_pos_y += ball_dir_y * ball_speed;
}

void update(int value) {
   // input handling
   keyboard();

   // update ball
   updateBall();

   // Call update() again in 'interval' milliseconds
   glutTimerFunc(interval, update, 0);

   // Redisplay frame
   glutPostRedisplay();
}

It simply increases the ball's position by its direction multiplied with the speed. No crazy math to it, just one multiplication and one addition.

Reminder: the update function is called about 60 times per second because we set it up as a callback in the GLUT library previously.

Note: as mentioned before, a += b is just a fancier way of doing a = a + b.

Ball Collisions

Alright, the last part of our game takes a slightly bit more math, but still nothing that we didn't learn in school already.

We have to find out if the ball did hit the left or right racket, left or right wall and top or bottom wall. Here is what we do in each case:

Let's just jump into it all at once and get through it. Here is our modified updateBall function:

void updateBall() {
    // fly a bit
    ball_pos_x += ball_dir_x * ball_speed;
    ball_pos_y += ball_dir_y * ball_speed;
   
    // hit by left racket?
    if (ball_pos_x < racket_left_x + racket_width &&
        ball_pos_x > racket_left_x &&
        ball_pos_y < racket_left_y + racket_height &&
        ball_pos_y > racket_left_y) {
        // set fly direction depending on where it hit the racket
        // (t is 0.5 if hit at top, 0 at center, -0.5 at bottom)
        float t = ((ball_pos_y - racket_left_y) / racket_height) - 0.5f;
        ball_dir_x = fabs(ball_dir_x); // force it to be positive
        ball_dir_y = t;
    }
   
    // hit by right racket?
    if (ball_pos_x > racket_right_x &&
        ball_pos_x < racket_right_x + racket_width &&
        ball_pos_y < racket_right_y + racket_height &&
        ball_pos_y > racket_right_y) {
        // set fly direction depending on where it hit the racket
        // (t is 0.5 if hit at top, 0 at center, -0.5 at bottom)
        float t = ((ball_pos_y - racket_right_y) / racket_height) - 0.5f;
        ball_dir_x = -fabs(ball_dir_x); // force it to be negative
        ball_dir_y = t;
    }

    // hit left wall?
    if (ball_pos_x < 0) {
        ++score_right;
        ball_pos_x = width / 2;
        ball_pos_y = height / 2;
        ball_dir_x = fabs(ball_dir_x); // force it to be positive
        ball_dir_y = 0;
    }

    // hit right wall?
    if (ball_pos_x > width) {
        ++score_left;
        ball_pos_x = width / 2;
        ball_pos_y = height / 2;
        ball_dir_x = -fabs(ball_dir_x); // force it to be negative
        ball_dir_y = 0;
    }

    // hit top wall?
    if (ball_pos_y > height) {
        ball_dir_y = -fabs(ball_dir_y); // force it to be negative
    }

    // hit bottom wall?
    if (ball_pos_y < 0) {
        ball_dir_y = fabs(ball_dir_y); // force it to be positive
    }

    // make sure that length of dir stays at 1
    vec2_norm(ball_dir_x, ball_dir_y);
}

It's really simple actually. Every time we just check the ball's x and y values to see if it's inside the left racket, inside the right racket, above the top wall, below the bottom wall, left of the left wall or right of the right wall. Then as explained above, we change directions, increase scores and so on.

We also used a t variable at the racket collision parts. This one just specifies where exactly the racket was hit, so we can change the ball's outgoing direction depending on where the racket was hit.

Then there is the vec2_norm function call at the bottom. We need it because we modified the ball_dir_x and ball_dir_y variables before. But in order to have the ball flying at the same speed all the time, the sum of both of those variables should always be exactly 1. That's pretty much what vec2_norm does, it just sets the length of a vector to one.

For example, if we would have something like:

The sum of those two would be 20. Hence the ball would fly pretty fast. After calling vec2_norm, they would be:

Now the sum of those two is exactly 1, while the ratio (or in other words, the fly direction) remains the same.

Here is how our vec2_norm function looks like:

void vec2_norm(float& x, float &y) {
        // sets a vectors length to 1 (which means that x + y == 1)
        float length = sqrt((x * x) + (y * y));
        if (length != 0.0f) {
            length = 1.0f / length;
            x *= length;
            y *= length;
        }
    }

Its not too hard to understand each line of it. It just calculates the squareroot of the sum of the squares of x and y, then divides and multiplies things a bit.

Now don't worry, this is not some crazy magic that we just invented, it's just a formula that can be found in any math book that covers vector math. Make sure to give the whole vector math thing a read one day, because there are a lot of cool things that are used in games every now and then.

If we launch the game, we can now see the ball colliding with the walls and the rackets just as we planned it.

Summary

That's how to make a game in C++. A rather long tutorial, but in the end it's just about 150 lines of code. As usual, our goal was to keep things as easy as possible. There are a lot of improvements that can be done to this game. Examples are sounds, a vector2 class or some nice particle effects and shaders. Maybe even AI for a computer controlled enemy.


Download Source Code & Project Files

The C++ 2D Pong Game source code & project files can be downloaded by Premium members.

All Tutorials. All Source Codes & Project Files. One time Payment.
Get Premium today!