Chapter 7: Dynamic Collision Detection and Physics – Finishing the Pong Game

In this chapter, we will code our second class. We will see that although the ball is obviously quite different from the bat, we will use the exact same techniques to encapsulate the appearance and functionality of a ball inside a Ball class, just like we did with the bat and the Bat class. We will then add the finishing touches to the Pong game by coding some dynamic collision detection and scorekeeping. This might sound complicated but as we are coming to expect, SFML will make things much easier than they otherwise would be.

We will cover the following topics in this chapter:

  • Coding the Ball class
  • Using the Ball class
  • Collision detection and scoring
  • Running the game

We will start by coding the class that represents the ball.

Coding the Ball class

To get started, we will code the header file. Right-click on Header Files in the Solution Explorer window and select ADD | New Item. Next, choose the Header File (.h) option and name the new file Ball.h. Click the Add button. Now, we are ready to code the file.

Add the following code to Ball.h:

#pragma once

#include <SFML/Graphics.hpp>

using namespace sf;

class Ball

{

private:

    Vector2f m_Position;    

    RectangleShape m_Shape;

    float m_Speed = 300.0f;

    float m_DirectionX = .2f;

    float m_DirectionY = .2f;

public:

    Ball(float startX, float startY);

    FloatRect getPosition();

    RectangleShape getShape();

    float getXVelocity();

    void reboundSides();

    void reboundBatOrTop();

    void reboundBottom();

    void update(Time dt);

};

The first thing you will notice is the similarity in the member variables compared to the Bat class. There is a member variable for the position, appearance, and speed, just like there was for the player's bat, and they are the same types (Vector2f, RectangleShape, and float, respectively). They even have the same names (m_Position, m_Shape, and m_Speed, respectively). The difference between the member variables of this class is that the direction is handled with two float variables that will track horizontal and vertical movement. These are m_DirectionX and m_DirectionY.

Note that we will need to code eight functions to bring the ball to life. There is a constructor that has the same name as the class, which we will use to initialize a Ball instance. There are three functions with the same name and usage as the Bat class. They are getPosition, getShape, and update. The getPosition and getShape functions will share the location and the appearance of the ball with the main function, and the update function will be called from the main function to allow the Ball class to update its position once per frame.

The remaining functions control the direction the ball will travel in. The reboundSides function will be called from main when a collision is detected with either side of the screen, the reboundBatOrTop function will be called in response to the ball hitting the player's bat or the top of the screen, and the reboundBottom function will be called when the ball hits the bottom of the screen.

Of course, these are just the declarations, so let's write the C++ that actually does the work in the Ball.cpp file.

Let's create the file, and 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.

Add the following code to Ball.cpp:

#include "Ball.h"

// This the constructor function

Ball::Ball(float startX, float startY)

{

    m_Position.x = startX;

    m_Position.y = startY;

    m_Shape.setSize(sf::Vector2f(10, 10));

    m_Shape.setPosition(m_Position);

}

In the preceding code, we have added the required include directive for the Ball class header file. The constructor function with the same name as the class receives two float parameters, which are used to initialize the m_Position member's Vector2f instance. The RectangleShape instance is then sized with the setSize function and positioned with setPosition. The size that's being used is 10 pixels wide and 10 high; this is arbitrary but works well. The position that's being used is, of course, taken from the m_Position Vector2f instance.

Add the following code underneath the constructor in the Ball.cpp function:

FloatRect Ball::getPosition()

{

    return m_Shape.getGlobalBounds();

}

RectangleShape Ball::getShape()

{

    return m_Shape;

}

float Ball::getXVelocity()

{

    return m_DirectionX;

}

In the preceding code, we are coding the three getter functions of the Ball class. They each return something to the main function. The first, getPosition, uses the getGlobalBounds function on m_Shape to return a FloatRect instance. This will be used for collision detection.

The getShape function returns m_Shape so that it can be drawn each frame of the game loop. The getXVelocity function tells the main function which direction the ball is traveling in, and we will see very soon exactly how this is useful to us. Since we don't ever need to get the vertical velocity, there is no corresponding getYVelocity function, but it would be simple to add one if we did.

Add the following functions underneath the previous code we just added:

void Ball::reboundSides()

{

    m_DirectionX = -m_DirectionX;

}

void Ball::reboundBatOrTop()

{

    m_DirectionY = -m_DirectionY;

}

void Ball::reboundBottom()

{

    m_Position.y = 0;

    m_Position.x = 500;

    m_DirectionY = -m_DirectionY;

}

In the preceding code, the three functions whose names begin with rebound… handle what happens when the ball collides with various places. In the reboundSides function, m_DirectionX has its value inverted, which will have the effect of making a positive value negative and a negative value positive, thereby reversing (horizontally) the direction the ball is traveling in. reboundBatOrTop does exactly the same but with m_DirectionY, which has the effect of reversing the direction the ball is traveling in vertically. The reboundBottom function repositions the ball at the top center of the screen and sends it downward. This is just what we want after the player has missed a ball and it has hit the bottom of the screen.

Finally, for the Ball class, add the update function, as follows:

void Ball::update(Time dt)

{

    // Update the ball's position

    m_Position.y += m_DirectionY * m_Speed * dt.asSeconds();

    m_Position.x += m_DirectionX * m_Speed * dt.asSeconds();

    // Move the ball

    m_Shape.setPosition(m_Position);

}

In the preceding code, m_Position.y and m_Position.x are updated using the appropriate direction velocity, the speed, and the amount of time the current frame took to complete. The newly updated m_Position values are then used to change the position the m_Shape RectangleShape instance is positioned at.

The Ball class is done, so let's put it into action.

Using the Ball class

To put the ball into action, add the following code to make the Ball class available in the main function:

#include "Ball.h"

Add the following highlighted line of code to declare and initialize an instance of the Ball class using the constructor function that we have just coded:

// Create a bat

Bat bat(1920 / 2, 1080 - 20);

// Create a ball

Ball ball(1920 / 2, 0);

// Create a Text object called HUD

Text hud;

Add the following code positioned exactly as highlighted:

/*

Update the bat, the ball and the HUD

****************************************************

****************************************************

****************************************************

*/

// Update the delta time

Time dt = clock.restart();

bat.update(dt);

ball.update(dt);

// Update the HUD text

std::stringstream ss;

ss << "Score:" << score << "    Lives:" << lives;

hud.setString(ss.str());

In the preceding code, we simply call update on the ball instance. The ball will be repositioned accordingly.

Add the following highlighted code to draw the ball on each frame of the game loop:

/*

Draw the bat, the ball and the HUD

*********************************************

*********************************************

*********************************************

*/

window.clear();

window.draw(hud);

window.draw(bat.getShape());

window.draw(ball.getShape());

window.display();

At this stage, you could run the game and the ball would spawn at the top of the screen and begin its descent toward the bottom of the screen. It would, however, disappear off the bottom of the screen because we are not detecting any collisions yet. Let's fix that now.

Collision detection and scoring

Unlike in the Timber!!! game when we simply checked whether a branch in the lowest position was on the same side as the player's character, in this game, we will need to mathematically check for the intersection of the ball with the bat or the ball with any of the four sides of the screen.

Let's look at some hypothetical code that would achieve this so that we understand what we are doing. Then, we will turn to SFML to solve the problem for us.

The code for testing the intersection of two rectangles would look something like this. Don't use the following code. It is for demonstration purposes only:

if(objectA.getPosition().right > objectB.getPosition().left

    && objectA.getPosition().left < objectB.getPosition().right )

{    

    // objectA is intersecting objectB on x axis    

    // But they could be at different heights    

    

    if(objectA.getPosition().top < objectB.getPosition().bottom         

        && objectA.getPosition().bottom > objectB.getPosition().top )

        {       

            // objectA is intersecting objectB on y axis as well

            // Collision detected  

        }

}

We don't need to write this code; however, we will be using the SFML intersects function, which works on FloatRect objects. Think or look back to the Bat and Ball classes; they both had a getPosition function, which returned a FloatRect of the object's current location. We will see how we can use getPosition, along with intersects, to do all our collision detection.

Add the following highlighted code at the end of the update section of the main function:

/*

Update the bat, the ball and the HUD

**************************************

**************************************

**************************************

*/

// Update the delta time

Time dt = clock.restart();

bat.update(dt);

ball.update(dt);

// Update the HUD text

std::stringstream ss;

ss << "Score:" << score << "    Lives:" << lives;

hud.setString(ss.str());

// Handle ball hitting the bottom

if (ball.getPosition().top > window.getSize().y)

{

    // reverse the ball direction

    ball.reboundBottom();

    // Remove a life

    lives--;

    // Check for zero lives

    if (lives < 1) {

        // reset the score

        score = 0;

        // reset the lives

        lives = 3;

    }

}

In the preceding code, the first if condition checks whether the ball has hit the bottom of the screen:

if (ball.getPosition().top > window.getSize().y)

If the top of the ball is at a greater position than the height of the window, then the ball has disappeared off the bottom of the player's view. In response, the ball.reboundBottom function is called. Remember that, in this function, the ball is repositioned at the top of the screen. At this point, the player has lost a life, so the lives variable is decremented.

The second if condition checks whether the player has run out of lives (lives < 1). If this is the case, the score is reset to 0, the number of lives is reset to 3, and the game is restarted. In the next project, we will learn how to keep and display the player's highest score.

Add the following code underneath the previous code:

// Handle ball hitting top

if (ball.getPosition().top < 0)

{

    ball.reboundBatOrTop();

    // Add a point to the players score

    score++;

}

In the preceding code, we are detecting that the top of the ball hits the top of the screen. When this occurs, the player is awarded a point and ball.reboundBatOrTop is called, which reverses the vertical direction of travel and sends the ball back toward the bottom of the screen.

Add the following code underneath the previous code:

// Handle ball hitting sides

if (ball.getPosition().left < 0 ||

    ball.getPosition().left + ball.getPosition().width> window.getSize().x)

{

    ball.reboundSides();

}

In the preceding code, the if condition detects a collision with the left-hand side of the ball with the left-hand side of the screen or the right-hand side of the ball (left + 10) with the right-hand side of the screen. In either event, the ball.reboundSides function is called and the horizontal direction of travel is reversed.

Add the following code:

// Has the ball hit the bat?

if (ball.getPosition().intersects(bat.getPosition()))

{

    // Hit detected so reverse the ball and score a point

    ball.reboundBatOrTop();

}

In the preceding code, the intersects function is used to determine whether the ball has hit the bat. When this occurs, we use the same function that we used for a collision with the top of the screen to reverse the vertical direction of travel of the ball.

Running the game

You can now run the game and bounce the ball around the screen. The score will increase when you hit the ball with the bat and the lives will decrease when you miss it. When lives gets to 0, the score will reset, and the lives will go back up to 3, as follows:

Summary

Congratulations; that's the second game completed! We could have added more features to that game such as coop play, high scores, sound effects, and more, but I just wanted to use the simplest possible example to introduce classes and dynamic collision detection. Now that we have these topics in our game developer's arsenal, we can move on to a much more exciting project and yet more game development topics.

In the next chapter, we will plan the Zombie Arena game, learn about the SFML View class, which acts as a virtual camera into our game world, and code some more classes.

FAQ

Q) Isn't this game a little quiet?

A) I didn't add sound effects to this game because I wanted to keep the code as short as possible while using our first classes and learning to use the time to smoothly animate all the game objects. If you want to add sound effects, then all you need to do is add the .wav files to the project, use SFML to load the sounds, and play a sound effect in each of the collision events. We will do this in the next project.

Q) The game is too easy! How can I make the ball speed up a little?

A) There are lots of ways you can make the game more challenging. One simple way would be to add a line of code in the Ball class' reboundBatOrTop function that increases the speed. As an example, the following code would increase the speed of the ball by 10% each time the function is called:

// Speed up a little bit on each hit

m_Speed = m_Speed * 1.1f;

The ball would get quite fast quite quickly. You would then need to devise a way to reset the speed back to 300.0f when the player has lost all their lives. You could create a new function in the Ball class, perhaps called resetSpeed, and call it from main when the code detects that the player has lost their last life.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.100.20