In this first playable game project we will build a very simple Pong clone. If you don’t know what Pong is then you are much younger than me and you should take a look at its appearance and history before continuing. We will use everything we learnt about C++ Functions and OOP to create a class for each of the objects of the game (a bat and a ball) as well as functions within these classes to make our bat and ball behave as we expect.[widgets_on_pages id=”udemy_advert_cpp_1″][widgets_on_pages id=”udemy_code_details”]

To get started we need to create a new project, complete with all the required properties to work with SFML. If you haven’t completed the Building your first SFML game project you will need to do that first. In that project, we created a Visual Studio project template, so that starting a new SFML project is now a two-minute job instead of a half an hour long chore.

The next steps set up our new SFML C++ project.

  1. Open Visual Studio and from the main menu choose File | New Project. In the left-hand menu click C++. Select the HelloSFML template and name your project Pong.
  2. Now click OK.
  3. Right-click the HelloSFML.cpp file under the Source Files heading from the right-hand Solution Explorer window. Choose Rename and rename the file to Main. This is a more appropriate name as this will indeed be our “main” source file.
  4. Open Main.cpp by double-clicking it. Now delete all of its contents as we will be starting fresh and discussing all of the code as well.
  5. Copy & paste the SFML .dll files in the YOUR_DRIVE:\SFML\bin to YOUR_DRIVE:\Visual Studio Stuff\Projects\Pong\Pong.

Now we can get coding.

[widgets_on_pages id=”bcgp_cfgp_gpp”]

Building the Bat class

In the tutorial on coding a C++ class, we mentioned that we should separate the declaration from the definition. As we create the Bat class we will see exactly how to do this. Perhaps unsurprisingly the Bat class will represent the player’s bat.

Right-click on Header Files in the Solution Explorer window. And select ADD | New Item. This step is shown in the next image.

create-new-header-file

 

Now select C++ Header File (.h) (1) and enter bat.h in the Name: field (2) as shown next.

create-new-header-file-part-2

Click the Add button (3) and our new file will be created for us.

Coding bat.h

Now we will code the class declaration. This will look a bit different to how it did in the Introduction to OOP in C++ tutorial but when we move on to the definition which will go in a separate file (Bat.cpp) things will look more familiar and will make more sense.

Take a look at the next block of code. This will be the entire contents of our bat.h file. In it, we #include the SFML library that we need, declare some member variables that are private and then declare some functions which are public.

We will discuss the variables and functions in more detail when we use and implement them in Bat.cpp in a minute. For now just note their names, parameters and return types. We have a Vector2f called position which will indeed hold the horizontal and vertical position of the player’s bat. We have an SFML RectangleShape which will be the actual bat that appears on the screen and we have a float called batSpeed that is the number of pixels per frame that the bat can move at.

The functions are as follows. A function called Bat which we learnt is the constructor because it has the same name as the class. Notice it takes two float parameters. This is perfect for initializing the position on the screen when we first create an  Bat object. Next is the getPosition function which returns a FloatRect, the four points which define a rectangle. Then we have getShape which returns a RectangleShape. This will be used to return to the main game loop  batShape, so it can be drawn.

Finally, we have the moveLeft, moveRight and update functions which take no parameters and return no values. We will see how these are useful to us very soon.

Add all this code we have just discussed to the bat.h file.

#pragma once
#include <SFML/Graphics.hpp>

using namespace sf;

class Bat
{
private:
	Vector2f position;

	// A RectangleShape object
	RectangleShape batShape;

	float batSpeed = .3f;

public:
	Bat(float startX, float startY);

	FloatRect getPosition();

	RectangleShape getShape();

	void moveLeft();

	void moveRight();

	void update();

};

Now we can get to the real functionality of the Bat class.

Coding bat.cpp

Let’s create the file then we can start discussing the code. Right-click the Source Files folder in the Solution Explorer window. Now select C++ File (.cpp) and enter Bat.cpp in the Name: field. Click the Add button and our new file will be created for us.

We will divide the code for this file into two to make discussion of it simpler. Take a look at part one of the code and explanation below.

bat .cpp part one

In this first part, we can see that we #include bat.h. This makes all the functions and variables that were declared previously in bat.h available to us. Then we implement the constructor. Notice we use the syntax Bat::Bat as the function name to make it clear we are using the Bat function from the Bat class.

This constructor receives two float values, startX and startY. The next thing that happens is we assign these values to position.x and position.y. The Vector2f named position now holds the values that were passed in and because position is a member variable, these values are accessible throughout the class. Note however that position was declared as private and will not accessible in our Main.cpp file. Not directly anyway. We will see how we resolve this issue soon.

Finally, in the constructor, we initialize our RectangleShape called batShape by setting its size and position.

Add the code we have just discussed to the Bat.cpp file.

#include "stdafx.h"
#include "Bat.h"

// This the constructor and it is called when we create an object
Bat::Bat(float startX, float startY)
{
	position.x = startX;
	position.y = startY;

	batShape.setSize(sf::Vector2f(50, 5));
	batShape.setPosition(position);
}

Now for the rest of the functions.

bat.cpp part two

We have five more functions that we need to code. Let’s go through them one at a time. Refer to the next block of code during this discussion.

First, we have the getPosition function. All it does is return a FloatRect to the code that called it. The batShape.getGlobalBounds, itself returns a FloatRect that is initialized with the coordinates of the four corners of the RectangleShape, batShape. We will use this function in Main.cpp when we are determining whether the ball has hit the bat.

Next, we have getShape. All this function does is pass a copy of batShape to the calling code. This is necessary so we can draw the bat in Main.cpp. When we code a public function with the sole purpose of passing back private data from a class we call it a getter function.

Now we can look at the moveLeft and moveRight functions. All they do is add or subtract batSpeed from position.x. This means that every time we call either moveLeft or moveRight we will tweak the horizontal position of the player’s bat. This is just what we need.

The last function in the Bat class is update. We will call this function one per frame of the game. The update function will grow in complexity as our game objects get more complicated. For now, all we need to do is set the position of batShape with whatever the current values held in position happen to be.

Add the code we have just discussed to the Bat.cpp file right after the previous code we added.

FloatRect Bat::getPosition()
{
	return batShape.getGlobalBounds();
}

RectangleShape Bat::getShape()
{
	return batShape;
}

void Bat::moveLeft()
{
	position.x -= batSpeed;
}

void Bat::moveRight()
{
	position.x += batSpeed;
}

void Bat::update()
{
	batShape.setPosition(position);
}

That’s it for the Bat class. What we have effectively done is this. We have a RectangleShape that is initialized to a position on the screen that will be determined when we create a Bat. We have the ability to change the position variable to the left or right at any time we choose by calling moveLeft or moveRight. Once per frame the RectangleShape will be moved to whatever position is contained within the position variable. The code in Main.cpp also has the ability to get a copy of this RectangleShape by calling getShape, this is ideal for when we need to draw it. The code in Main.cpp can also call getPosition to get the coordinates that define the four points of the bat which we will use for detecting collisions with the ball.

Let’s make a ball.

Building the Ball class

As we did for the Bat class we will create a new .h file to hold the class declaration for our Ball class. Right-click on Header Files in the Solution Explorer window. And select ADD | New Item. Now select C++ Header File (.h) and enter ball.h in the Name: field. Click the Add button and our new file will be created for us.

Coding ball.h

Take a look at the next block of code. We can see we have another Vector2f for holding the position of the ball. We have another RectangleShape which will be used to draw the ball and we have two float variables xVelocity and yVelocity to represent the horizontal and vertical speeds of the ball. These variables are all private so not accessible directly from outside the code of the class.

We also have a selection of public functions. The one named Ball will obviously be our constructor. Just like our Bat constructor, it takes two float parameters that will set the starting position. Just as we did with the Bat class we have a getPosition and getShape functions that return a FloatRect and RectangleShape respectively.

Now we have the  getXVelocity getter function which will return the value of some private data to the calling code.

Now we have something a little bit different that won’t make complete sense until we actually use them. When we make a quick and simple version of a game like Pong it is possible to foresee a couple of problems we will encounter:

  1. What happens when the ball hits the side of the screen?
  2. What happens when the ball hits the bat or the top of the screen?
  3. What happens when the ball hits the bottom of the screen?

The three functions reboundSides, reboundBatOrTop and hitBottom will deal with the above the events. We will see exactly how in a minute.

Finally, just like in the Bat class we have a  update function that will be called once per frame of the game to move the ball around.

Add the code we have just discussed to the ball.h file.

#pragma once
#include <SFML/Graphics.hpp>

using namespace sf;

class Ball
{
private:
	Vector2f position;

	// A RectangleShape object called ref
	RectangleShape ballShape;

	float xVelocity = .2f;
	float yVelocity = .2f;

public:
	Ball(float startX, float startY);

	FloatRect getPosition();

	RectangleShape getShape();

	float getXVelocity();

	void reboundSides();

	void reboundBatOrTop();

	void hitBottom();

	void update();

};

Next for the implementation.

Coding ball.cpp

Let’s create the file then we can start discussing the code. Right-click the Source Files folder in the Solution Explorer window. Now select C++ File (.cpp) and enter Ball.cpp in the Name: field. Click the Add button and our new file will be created for us.

We will divide the code for this file into two to make discussion of it simpler. Take a look at part one of the code and explanation below.

ball.cpp part one

Here we have the constructor and it does exactly the same thing that the Bat constructor did. It captures the required starting position passed in as parameters, sets the size and position of the RectangleShape that will represent the ball on the screen. Add the code below to Ball.cpp.

#include "stdafx.h"
#include "Ball.h"

// This the constructor and it is called when we create an object
Ball::Ball(float startX, float startY)
{
	position.x = startX;
	position.y = startY;

	ballShape.setSize(sf::Vector2f(10, 10));
	ballShape.setPosition(position);
}

The next part of the code is much longer but not complicated in any way.

ball.cpp part two

[widgets_on_pages id=”udemy_advert_cpp_2″][widgets_on_pages id=”udemy_code_details”]
There are seven more functions in Ball. Let’s run through them one at a time.

First getPosition is a getter which returns a FloatRect to the calling code. We will soon see how we use the getPosition functions from Bat and Ball to do collision detection in our Main.cpp code.

Next we have getShape which does exactly the same job that the getShape function in the Bat class did. It returns a copy of the RectangleShape to be drawn on the screen.

The getXVelocity function is a getter for the xVelocity member variable and does it’s one simple job of returning the value stored in xVelocity.

The reboundSides function will be called every time the ball hits the sides of the screen. Look at the code inside it. It simply makes xVelocity the negative of whatever it currently is. So if it is positive (moving right) it will become negative and move left, and vice versa as well.

The ReboundBatOrTop function is a tiny bit more in-depth, but not much. The first line of code in this function  position.y -= (yVelocity * 30) directly alters the position.y value by the equivalent of 30 frames of movement and prevents the ball from getting stuck on the bat. This is a common bug when using simplistic physics like we are here. The second line of code reverses the direction of the ball.

The hitBottom function is very straight forward as well. First, it sets the position.y value to 1, effectively placing the ball back at the top of the screen and the position.x to 500, placing it around about the middle horizontally.

Finally, for the Ball class, we have the update function which is a bit more in-depth than the update function in the Bat class. Unlike a bat, a ball is always moving. So the first thing that happens each frame is we update position.x and position.y by xVelocity and yVelocity. Then, just as we did for the Bat class, we update the position of the RectangleShape with the new values stored in position.

Add the code we have just discussed directly after the previous code, into the Ball.cpp file.

FloatRect Ball::getPosition()
{
	return ballShape.getGlobalBounds();
}

RectangleShape Ball::getShape()
{
	return ballShape;
}

float Ball::getXVelocity()
{
	return xVelocity;
}

void Ball::reboundSides()
{
	xVelocity = -xVelocity;
}

void Ball::reboundBatOrTop()
{
	position.y -= (yVelocity * 30);
	yVelocity = -yVelocity;

}

void Ball::hitBottom()
{
	position.y = 1;
	position.x = 500;
}

void Ball::update()
{
	// Update the ball position variables
	position.y += yVelocity;
	position.x += xVelocity;

	// Move the ball and the bat
	ballShape.setPosition(position);
}

Coding Main.cpp

At last, we are in a position to put our new classes to work and make the Pong game come to life. Switch to the Main.cpp file in Visual Studio and we can discuss the first part of six parts that go to make up the code for this file.

We will be displaying some text known as a HUD (Heads Up Display) in this game which will show the player’s score and remaining lives. For this, we need a font.

  1. Download this free-for-personal-use font from http://www.dafont.com/theme.php?cat=302.
  2. Click the Download button.
  3. Unzip the download.
  4. Add the DS-DIGIT.ttf file into the YOUR_DRIVE:\Visual Studio Stuff\Projects\Pong\Pong folder.

Main.cpp part one

Add all the following #include directives and the using statement to the top of the file. Notice that we are including our two new classes by the code #include "bat.h" and #include "ball.h".

// These "include" code from the C++ library and SFML too
#include "stdafx.h"
#include "bat.h"
#include "ball.h"
#include <sstream>
#include <cstdlib>
#include <SFML/Graphics.hpp>

// Avoid having to put sf in front of all the SFML classes and functions
using namespace sf;

Onwards to part two.

Main.cpp part two

In the next block of code, we create a window that is 1024 x 768 pixels in size. As this is going to be a working game we declare two int variables to keep track of the player’s lives and score. Then we create an instance of a Bat and a Ball. We pass in appropriate starting positions for each, as required by their constructors which we coded previously.

All the rest of the code in part two deals with displaying the HUD (score and lives) on the screen. Some of this we have seen previously in the Hello SFML project but some of it is new.  We create an SFML Text object called text and an SFML Font object called font. We then load the font that we placed in the project folder a few moments ago. Next, we use setFont to assign our new font to our hud Text object. Next, we set the size followed by the color of the font using setCharacterSize and setFillColor functions.

Add the code we have just discussed to Main.cpp.

// This is where our game starts from
int main()
{
	int windowWidth = 1024;
	int windowHeight = 768;
	// Make a window that is 1024 by 768 pixels
	// And has the title "Pong"
	RenderWindow window(VideoMode(windowWidth, windowHeight), "Pong");

	int score = 0;
	int lives = 3;

    // create a bat
	Bat bat (windowWidth / 2, windowHeight - 20);

	// create a ball
	Ball ball(windowWidth / 2, 1);

 // Create a "Text" object called "message". Weird but we will learn about objects soon
Text hud;

 // We need to choose a font
 Font font;
 // http://www.dafont.com/theme.php?cat=302
 font.loadFromFile("DS-DIGIT.ttf");

 // Set the font to our message
 hud.setFont(font);

 // Make it really big
 hud.setCharacterSize(75);

 // Choose a color
 hud.setFillColor(sf::Color::White);

We are ready to enter the game loop!

Main.cpp part three

In this third part of the Main.cpp code we will handle the player’s input.  There are two ways to handle player input in SFML and I encourage you to research them both further(event handling and keyboard input). Here we use both. First, in the while loop  that has the condition window.pollEvent(event), we check if any messages have been sent to the operating system and subsequently passed on to SFML. We could check for lots of different types of messages here but content ourselves by checking if the window was closed. If it was we call window.close and the game will quit.

Next, we explicitly check for the current state of some of the keys at the given moment (as opposed to in a queue). Look carefully at the various if statements and the simple but important code that we call when the if statement is true.

If Keyboard::Left, bat.moveLeft(). Next is if  Keyboard::Right, bat.moveRight(). What is happening here is that once each frame we check to see if the left or right arrow (cursor) keys are currently being pressed and if they are we call the appropriate moveLeft or moveRight functions on our bat object. This will have the effect of updating the position.x variable that we coded previously.

        // This "while" loop goes round and round- perhaps forever
	while (window.isOpen())
	{
		/*
			Handle the player input
			*********************************************************************
			*********************************************************************
			*********************************************************************
		*/

		Event event;
		while (window.pollEvent(event))
		{
			if (event.type == Event::Closed)
				// Someone closed the window- bye
				window.close();
		}

		if (Keyboard::isKeyPressed(Keyboard::Left))
		{
			// move left...
			bat.moveLeft();
		}
		else if (Keyboard::isKeyPressed(Keyboard::Right))
		{
			// move right...
			bat.moveRight();
		}
		else if (Keyboard::isKeyPressed(sf::Keyboard::Escape))
		{
			// quit...
			// Someone closed the window- bye
			window.close();
		}

We will soon see the knock-on effect of changing the position.x variable. First let’s work out if any collisions have taken place.

Main.cpp part four

This next block of code comprises four main if statements although one of them has a further nested if statement.

The first main if statement checks whether the ball has hit the bottom of the screen. It does so by using the getPosition function we coded in the Ball class and checking the top variable of the returned FloatRect to see if it has exceeded the height of the screen. If it has then a number of things happen next. First, we call the hitBottom function to reposition the ball at the top of the screen and reverse its direction, then we subtract a life from the player. Next, still inside the if statement that determined the ball hit the bottom, we check if the player has run out of lives. If the player has run out of lives we reset the score to zero and the lives back to three. The player is essentially starting again.

In the next main if statement, we find out if the ball has hit the top of the screen by checking if the top of the ball is at a position less than zero. If it has, all we need to do is call the reboundBatOrTop function which reverses the direction. Furthermore, we add a point to the player’s score.

The next if statement checks for a collision on the left or right-hand side of the screen. The condition uses the logical OR || operator so it can test for the ball’s left-hand side being less than zero or the ball’s right-hand side being greater than the width of the screen in one condition. If the condition is true, all we need to do is call the reboundSides function on ball and the ball’s direction is reversed.

The last if statement is a little bit different to the others because it detects if the ball has hit the bat. It achieves this using the getPosition function just like the other collision detecting if statements did but this time it chains that function call with another call to a function called intersects. Take a look at this if condition closely and we will explore what is going on.

The ball.getPosition() part of the code returns a FloatRect object, just like we coded it to do. Now, because FloatRect, itself is a class, it too has functions that can be called. The returned FloatRect from getPosition is used to call its  intersects function passing in as a parameter the returned FloatRect from bat.getposition. Essentially we are saying, does the ball intersect the bat? If it does the condition is true and we call the reboundTopOrBat function which reverses the ball and sends it heading off towards the top of the screen again.

Enter the following code after the previous code we entered.

        /*
			Update the frame
			*********************************************************************
			*********************************************************************
			*********************************************************************
		*/

		// Handle ball hitting the bottom
		if (ball.getPosition().top > windowHeight)
		{
			// reverse the ball direction
			ball.hitBottom();

			// Remove a life
			lives --;

			// Check for zero lives
			if (lives < 1) {
				// reset the score
				score = 0;
				// reset the lives
				lives = 3;
			}

		}

		// Handle ball hitting top
		if (ball.getPosition().top < 0)
		{
			ball.reboundBatOrTop();

			// Add a point to the players score
			score++;

		}

		// Handle ball hitting sides
		if (ball.getPosition().left < 0 || ball.getPosition().left + 10 > windowWidth)
		{
			ball.reboundSides();
		}

		// Has the ball hit the bat?
		if (ball.getPosition().intersects(bat.getPosition()))
		{
			// Hit detected so reverse the ball and score a point
			ball.reboundBatOrTop();
		}

Nearly there!

Main.cpp part five

The final part of updating the current frame requires us to call the update functions of both the bat and the ball objects. In the bat update function, we simply update the position and in the ball update method we move the ball in the appropriate direction and then update its position. They are now ready to be drawn.

The last part of the code in this block is totally new. We are declaring an object of type stringstream and calling it ss. We can use ss to concatenate (add together) characters and numerical values to create a single string-like object. This, as we will see, is perfect for displaying a dynamic score and number of lives in our hud object. The slightly strange looking ss << "score:" << score... code does exactly this. We then set the String held in ss to hud which of course is a Text object.

Add the code we have just discussed.

ball.update();
bat.update();

// Update the HUD text
std::stringstream ss;
ss << "Score:" << score << "    Lives:" << lives;
hud.setString(ss.str());

Let’s draw.

Main.cpp part six

First, we clear the previous frame with window.clear, then we draw the bat and the ball using window.draw passing in a call to the getShape function of each of our two objects. As these functions return the appropriate RectangleShape the effect is that they are drawn on the screen. We then call window.draw and pass in hud to draw the HUD. Finally we call window.display to draw the current frame to the screen.


         /*
			Draw the frame
			*********************************************************************
			*********************************************************************
			*********************************************************************
		*/

		// Clear everything from the last frame
		window.clear(Color(26, 128, 182,255));

		window.draw(bat.getShape());

		window.draw(ball.getShape());

		// Draw our score
		window.draw(hud);

		// Show everything we just drew
		window.display();
	}// This is the end of the "while" loop

	return 0;
}

Run the game and you will be able to entertain yourself, just as I did when I was four years old.

What can we do better next time?

There are a few flaws with our game. Some are obvious and some not quite as obvious.

If you were to run this game on a super-fast PC and then run it on an ageing laptop, you would see that the game actually runs at totally different speeds. A professional game would control the number of frames per second (FPS) so that it ran exactly the same regardless of the speed of the PC. This is not especially tough and we could have incorporated it in this project. I just thought there were enough new ideas already.

Another issue is that we have only used boring shapes for all the game objects. This isn’t too much of an issue for a Pong clone but we will need to use real graphics if we are going to win Indie game of the year anytime soon.

There is also the issue that by creating and managing each and every object individually we will eventually have an extremely cluttered code base. Imagine if we did a Zombie Survival game with dozens of Zombies. How would we manage the zombies? Would we have dozens of zombie objects like z1, z2, z3, z4… etc.?

We will be improving on all these issues and solve the problems as we progress with more C++ tutorials and the next SFML project.

Note to self: Zombie Survival game seems like a good choice for next project.