Chapter 22: Using Game Objects and Building a Game

This chapter is the final stage of the Space Invaders ++ project. We will learn how to receive input from a gamepad using SFML to do all the hard work and we will also code a class that will handle communication between the invaders and the GameScreen class, as well as the player and the GameScreen class. The class will allow the player and the invaders to spawn bullets, but the exact same technique could be used for any kind of communication that you need between different parts of your own game, so it is useful to know. The final part of the game (as usual) will be the collision detection and the logic of the game itself. Once Space Invaders ++ is up and running, we will learn how to use the Visual Studio debugger, which will be invaluable when you are designing your own logic because it allows you to step through your code a line at a time and see the value of variables. It is also a useful tool for studying the execution flow of the patterns we have assembled over the course of this project.

Here is what we will do in this chapter:

  • Code a solution for spawning bullets
  • Handle the player's input, including with a gamepad
  • Detect collisions between all the necessary objects
  • Code the main logic of the game
  • Learn about debugging and understand the execution flow

Let's start by spawning bullets.

Spawning bullets

We need a way to spawn bullets from both the player and each of the invaders. The solutions to both are very similar but not identical. We need a way to allow GameInputHandler to spawn bullets when a keyboard key or gamepad button is pressed, and we need InvaderUpdateComponent to use its already existing logic to spawn bullets.

The GameScreen class has a vector holding all the GameObject instances, so GameScreen is the ideal candidate to move a bullet into position and set it moving up or down the screen, depending on who or what triggered the shot. We need a way for the GameInputHandler class and InvaderUpdateComponenet to communicate with the GameScreen class, but we also need to restrict the communication to just spawning bullets; we don't want them to be able to take control of any other part of the GameScreen class.

Let's code an abstract class that GameScreen can inherit from.

Coding the BulletSpawner class

Create a new header file in the Header Files/GameObjects filter called BulletSpawner.h and add the following code:

#include <SFML/Graphics.hpp>

class BulletSpawner

{

public:

    virtual void spawnBullet(

        sf::Vector2f spawnLocation, bool forPlayer) = 0;

};

The preceding code creates a new class called BulletSpawner with a single pure virtual function called spawnBullet. The spawnBullet function has two parameters. The first is a Vector2f instance that will determine the spawn location. Actually, as we will see soon, when the bullet is spawned, this position will be tweaked slightly, depending on whether the bullet is going up the screen (as a player bullet) or down the screen (as an invader bullet). The second parameter is a Boolean that will be true if the bullet belongs to the player or false if it belongs to an invader.

Create a new source file in the Source Files/GameObjects filter called BulletSpawner.cpp and add the following code:

/*********************************

******THIS IS AN INTERFACE********

*********************************/

Tip

As usual, this.cpp file is optional. I just wanted to bring balance to the source.

Now, go to GameScreen.h, since this is where we will implement the function of this class.

Updating GameScreen.h

First, update the include directives and the class declaration, as highlighted in the following code, to make the GameScreen class inherit from BulletSpawner:

#pragma once

#include "Screen.h"

#include "GameInputHandler.h"

#include "GameOverInputHandler.h"

#include "BulletSpawner.h"

class GameScreen : public Screen, public BulletSpawner

{

   …

   …

Next, add some extra functions and variable declarations, as highlighted in the following code, to GameScreen.h:

private:

    ScreenManagerRemoteControl* m_ScreenManagerRemoteControl;

    shared_ptr<GameInputHandler> m_GIH;

    int m_NumberInvadersInWorldFile = 0;

    vector<int> m_BulletObjectLocations;

    int m_NextBullet = 0;

    bool m_WaitingToSpawnBulletForPlayer = false;

    bool m_WaitingToSpawnBulletForInvader = false;

    Vector2f m_PlayerBulletSpawnLocation;

    Vector2f m_InvaderBulletSpawnLocation;

    Clock m_BulletClock;

    Texture m_BackgroundTexture;

    Sprite m_BackgroundSprite;

public:

    static bool m_GameOver;

    GameScreen(ScreenManagerRemoteControl* smrc, Vector2i res);

    void initialise() override;

    void virtual update(float fps);

    void virtual draw(RenderWindow& window);

    BulletSpawner* getBulletSpawner();

The new variables include a vector of int values that will hold the locations of all the bullets in the vector, which holds all the game objects. It also has a few control variables so that we can keep track of the next bullet to use, whether the bullet is for the player or an invader, and the position to spawn the bullet in. We have also declared a new sf::Clock instance because we want to limit the fire rate of the player. Finally, we have the getBulletSpawner function, which will return a pointer to this class in the form of a BulletSpawner. This will give the recipient access to the spawnBullet function, but nothing else.

Now, we can add the implementation of the spawnBullet function. Add the following code to GameScreen.h at the end of all the other code, but inside the closing curly brace of the GameScreen class:

/****************************************************

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

From BulletSpawner interface

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

*****************************************************/

void BulletSpawner::spawnBullet(Vector2f spawnLocation,

    bool forPlayer)

{

    if (forPlayer)

    {

        Time elapsedTime = m_BulletClock.getElapsedTime();

        if (elapsedTime.asMilliseconds() > 500) {

            m_PlayerBulletSpawnLocation.x = spawnLocation.x;

            m_PlayerBulletSpawnLocation.y = spawnLocation.y;

            m_WaitingToSpawnBulletForPlayer = true;

            m_BulletClock.restart();

        }

    }

    else

    {

        m_InvaderBulletSpawnLocation.x = spawnLocation.x;

        m_InvaderBulletSpawnLocation.y = spawnLocation.y;

        m_WaitingToSpawnBulletForInvader = true;

    }

}

The implementation of the spawnBullet function is a simple ifelse structure. The if block executes if a bullet is requested for the player and the else block executes if a bullet is requested for an invader.

The if block checks that at least half a second has passed since the last bullet was requested and, if it has, the m_WaitingToSpawnBulletForPlayer variable is set to true, the location to spawn the bullet at is copied, and the clock is restarted, ready to test the player's next request.

The else block records the spawn location for an invader's bullet and sets m_WaitingToSpawnBulletForInvader to true. No interaction with the Clock instance is necessary as the rate of fire for the invaders is controlled in the InvaderUpdateComponent class.

The last part of the BulletSpawner puzzle, before we get to actually spawning the bullets, is to add the definition of getBulletSpawner to the end of GameScreen.cpp. Here is the code to add:

BulletSpawner* GameScreen::getBulletSpawner()

{

    return this;

}

This returns a pointer to GameScreen, which gives us access to the spawnBullet function.

Handling the player's input

Add some more declarations to the GameInputHandler.h file so that your code matches what follows. I have highlighted the new code to add:

#pragma once

#include "InputHandler.h"

#include "PlayerUpdateComponent.h"

#include "TransformComponent.h"

class GameScreen;

class GameInputHandler : public InputHandler

{

private:

    shared_ptr<PlayerUpdateComponent> m_PUC;

    shared_ptr<TransformComponent> m_PTC;

    bool mBButtonPressed = false;

public:

    void initialize();

    void handleGamepad() override;

    void handleKeyPressed(Event& event,

        RenderWindow& window) override;

    void handleKeyReleased(Event& event,

        RenderWindow& window) override;    

};

The GameInputHandler class now has access to the player's update component and the player's transform component. This is very useful because it means we can tell the PlayerUpdateComponent instance and the player's TransformComponent instance what keyboard keys and gamepad controls the player is manipulating. What we haven't seen yet is how exactly these two shared pointers will be initialized – after all, aren't the GameObject instances and all their components packed away in a vector? You can probably guess the solution has something to do with GameObjectSharer. Let's keep coding to find out more.

In the GameInputHanldler.cpp file, add a forward declaration of the BulletSpawner class after the include directives but before the initialize function, as highlighted in the following code:

#include "GameInputHandler.h"

#include "SoundEngine.h"

#include "GameScreen.h"

class BulletSpawner;

void GameInputHandler::initialize() {

In the GameInputHandler.cpp file, add the following highlighted code to the handleKeyPressed function:

void GameInputHandler::handleKeyPressed(

    Event& event, RenderWindow& window)

{

    // Handle key presses

    if (event.key.code == Keyboard::Escape)

    {

        SoundEngine::playClick();

        getPointerToScreenManagerRemoteControl()->

            SwitchScreens("Select");

    }

    

if (event.key.code == Keyboard::Left)

    {

        m_PUC->moveLeft();

    }

    if (event.key.code == Keyboard::Right)

    {

        m_PUC->moveRight();

    }

    if (event.key.code == Keyboard::Up)

    {

        m_PUC->moveUp();

    }

    if (event.key.code == Keyboard::Down)

    {

        m_PUC->moveDown();

    }

}

Notice that we are responding to keyboard presses just like we have been doing throughout this book. Here, however, we are calling the functions from the PlayerUpdateComponent class that we coded in Chapter 20, Game Objects and Components, in order to take the required actions.

In the GameInputHandler.cpp file, add the following highlighted code to the handleKeyReleased function:

void GameInputHandler::handleKeyReleased(

    Event& event, RenderWindow& window)

{

    if (event.key.code == Keyboard::Left)

    {

        m_PUC->stopLeft();

    }

    else if (event.key.code == Keyboard::Right)

    {

        m_PUC->stopRight();

    }

    else if (event.key.code == Keyboard::Up)

    {

        m_PUC->stopUp();

    }

    else if (event.key.code == Keyboard::Down)

    {

        m_PUC->stopDown();

    }

    else if (event.key.code == Keyboard::Space)

    {

        // Shoot a bullet

        SoundEngine::playShoot();

        Vector2f spawnLocation;

        spawnLocation.x = m_PTC->getLocation().x +

            m_PTC->getSize().x / 2;

        spawnLocation.y = m_PTC->getLocation().y;

        static_cast<GameScreen*>(getmParentScreen())->

            spawnBullet(spawnLocation, true);

    }

}

The preceding code also relies on calling functions from the PlayerUpdateComponent class to handle what happens when the player releases a keyboard key. The PlayerUpdateComponent class can then stop movement in the appropriate direction, depending on which keyboard key has just been released. When the space key is released, the getParentScreen function is chained with the spawnBullet function to trigger a bullet being spawned. Notice that the spawn coordinates (spawnLocation) are calculated using the shared pointer to the PlayerTransformComponent instance.

Let's learn about how SFML helps us interact with a gamepad and then we can return to the PlayerInputHandler class to add some more functionality.

Using a gamepad

Handling gamepad input is made exceptionally easy by SFML. Gamepad (or joystick) input is handled by the sf::Joystick class. SFML can handle input from up to eight gamepads, but this tutorial will stick to just one.

You can think of the position of a thumbstick/joystick as a 2D graph that starts at -100, -100 at the top left corner and goes to 100, 100 at the bottom right corner. The position of the thumbstick can, therefore, be represented by a 2D coordinate. The following diagram illustrates this with a few example coordinates:

All we need to do is grab the value and report it to the PlayerUpdateComponent class for each frame of the game loop. Capturing the position is as simple as the following two lines of code:

float x  = Joystick::getAxisPosition(0, sf::Joystick::X);

float y = Joystick::getAxisPosition(0, sf::Joystick::Y);

The zero parameter requests data from the primary gamepad. You can use values  0 through 7 to get input from eight gamepads.

There is something else we need to consider as well. Most gamepads, especially thumbsticks, are mechanically imperfect and will register small values even when they are not being touched. If we send these values to the PlayerUpdateComponent class, then the ship will aimlessly drift around the screen. For this reason, we will create a dead zone. This is a range of movement where we will ignore any values. 10 percent of the range of movement works quite well. Therefore, if the values that are retrieved from the getAxisPosition function are between -10 and 10 on either axis, we will ignore them.

To get input from the B button of the gamepad, we use the following line of code:

// Has the player pressed the B button?

if (Joystick::isButtonPressed(0, 1))

{

    // Take action here

}

The preceding code detects when the B button on an Xbox One gamepad is pressed. Other controllers will vary. The 0, 1 parameters refer to the primary gamepad and button number 1. To detect when a button is released, we will need to code a bit of our own logic. As we want to shoot a bullet on release and not when it is pressed, we will use a simple Boolean to track this. Let's code the rest of the GameInputHandler class and see how we can put what we have just learned into action.

In the GameInputHandler.cpp file, add the following highlighted code to the handleGamepad function:

void GameInputHandler::handleGamepad()

{

    float deadZone = 10.0f;

    float x  = Joystick::getAxisPosition(0, sf::Joystick::X);

    float y = Joystick::getAxisPosition(0, sf::Joystick::Y);    

    

    if (x < deadZone && x > -deadZone)

    {

        x = 0;

    }

    if (y < deadZone && y > -deadZone)

    {

        y = 0;

    }

    m_PUC->updateShipTravelWithController(x, y);    

    // Has the player pressed the B button?

    if (Joystick::isButtonPressed(0, 1))

    {

        mBButtonPressed = true;

    }

    // Has player just released the B button?

    if (!Joystick::isButtonPressed(0, 1) && mBButtonPressed)

    {

        mBButtonPressed = false;

        // Shoot a bullet

        SoundEngine::playShoot();

        Vector2f spawnLocation;

        spawnLocation.x = m_PTC->getLocation().x +

            m_PTC->getSize().x / 2;

        spawnLocation.y = m_PTC->getLocation().y;

        

        static_cast<GameScreen*>(getmParentScreen())->

            getBulletSpawner()->spawnBullet(

               spawnLocation, true);

    }

}

We begin by defining a dead zone of 10 and then proceed to capture the position of the thumbstick. The next two if blocks test whether the thumbstick position is within the dead zone. If it is, then the appropriate value is set to zero to avoid the ship drifting. Then, we can call the updateShipTravelWithController function on the PlayerUpdateComponent instance. That is the thumbstick dealt with.

The next if statement sets a Boolean to true if the B button on the gamepad is pressed. The next if statement detects when the B button is not pressed, and the Boolean is set to true. This indicates that the B button has just been released.

Inside the if block, we set the Boolean to false, ready to handle the next button release, play a shooting sound, get the location to spawn the bullet, and call the spawnBullet function by chaining the getmParentScreen and getBulletSpawner functions.

Coding the PhysicsEnginePlayMode class

This is the class that will do all the collision detection. In this game, there are several collision events we want to watch out for:

  • Has an invader reached the left- or right-hand side of the screen? If so, all the invaders need to drop down one row and head back in the other direction.
  • Has an invader collided with the player? As the invaders get lower, we want them to be able to bump into the player and cause a life to be lost.
  • Has an invader bullet hit the player? Each time an invader bullet hits the player, we need to hide the bullet, ready for reuse, and deduct a life from the player.
  • Has a player bullet hit an invader? Each time the player hits an invader, the invader should be destroyed, the bullet hidden (ready for reuse), and the player's score increased.

This class will have an initialize function that the GameScreen class will call to prepare for detecting collisions, a detectCollisions function that the GameScreen class will call once for each frame after all the game objects have updated themselves, and three more functions which will be called from the detectCollisions function to separate out the work of detecting the different collisions I have just listed.

Those three functions are detectInvaderCollisions, detectPlayerCollisionsAndInvaderDirection, and handleInvaderDirection. Hopefully, the names of these functions make it clear what will happen in each function.

Create a new source file in the Header Files/Engine filter called PhysicsEnginePlayMode.h and add the following code:

#pragma once

#include "GameObjectSharer.h"

#include "PlayerUpdateComponent.h"

class PhysicsEnginePlayMode

{

private:

    shared_ptr<PlayerUpdateComponent> m_PUC;

    GameObject* m_Player;

    bool m_InvaderHitWallThisFrame = false;

    bool m_InvaderHitWallPreviousFrame = false;

    bool m_NeedToDropDownAndReverse = false;

    bool m_CompletedDropDownAndReverse = false;

    void detectInvaderCollisions(

        vector<GameObject>& objects,

        const vector<int>& bulletPositions);

    void detectPlayerCollisionsAndInvaderDirection(

        vector<GameObject>& objects,

        const vector<int>& bulletPositions);

    void handleInvaderDirection();

public:

    void initilize(GameObjectSharer& gos);

    void detectCollisions(

        vector<GameObject>& objects,

        const vector<int>& bulletPositions);

};

Study the preceding code to make a note of the parameters that are passed to each of the functions. Also take note of the four member Boolean variables that will be used throughout the class. Furthermore, notice that there is a pointer to a GameObject type being declared which will be a permanent reference to the player ship, so we don't need to keep finding the GameObject that represents the player for each frame of the game loop.

Create a new source file in the Source Files/Engine filter called PhysicsEnginePlayMode.cpp and add the following include directives and the detectInvaderCollisions function. Study the code and then we will discuss it:

#include "DevelopState.h"

#include "PhysicsEnginePlayMode.h"

#include <iostream>

#include "SoundEngine.h"

#include "WorldState.h"

#include "InvaderUpdateComponent.h"

#include "BulletUpdateComponent.h"

void PhysicsEnginePlayMode::

detectInvaderCollisions(

    vector<GameObject>& objects,

    const vector<int>& bulletPositions)

{

Vector2f offScreen(-1, -1);

auto invaderIt = objects.begin();

auto invaderEnd = objects.end();

for (invaderIt;

    invaderIt != invaderEnd;

    ++invaderIt)

{

    if ((*invaderIt).isActive()

        && (*invaderIt).getTag() == "invader")

    {

        auto bulletIt = objects.begin();

        // Jump to the first bullet

        advance(bulletIt, bulletPositions[0]);

        auto bulletEnd = objects.end();

        for (bulletIt;

            bulletIt != bulletEnd;

            ++bulletIt)

        {

            if ((*invaderIt).getEncompassingRectCollider()

                .intersects((*bulletIt)

                    .getEncompassingRectCollider())

                && (*bulletIt).getTag() == "bullet"

                && static_pointer_cast<

                      BulletUpdateComponent>(

                (*bulletIt).getFirstUpdateComponent())

                ->m_BelongsToPlayer)

            {

                SoundEngine::playInvaderExplode();

                (*invaderIt).getTransformComponent()

                    ->getLocation() = offScreen;

                (*bulletIt).getTransformComponent()

                    ->getLocation() = offScreen;

                WorldState::SCORE++;

                WorldState::NUM_INVADERS--;

                (*invaderIt).setInactive();

            }

        }

    }

}

}

The preceding code loops through all the game objects. The first if statement checks whether the current game object is both active and an invader:

if ((*invaderIt).isActive()

        && (*invaderIt).getTag() == "invader")

If it is an active invader, another loop is entered and each of the game objects that represents a bullet is looped through:

auto bulletIt = objects.begin();

// Jump to the first bullet

advance(bulletIt, bulletPositions[0]);

auto bulletEnd = objects.end();

for (bulletIt;

    bulletIt != bulletEnd;

    ++bulletIt)

The next if statement checks whether the current invader has collided with the current bullet and whether that bullet was fired by the player (we don't want invaders shooting themselves):

if ((*invaderIt).getEncompassingRectCollider()

        .intersects((*bulletIt)

        .getEncompassingRectCollider())

        && (*bulletIt).getTag() == "bullet"

        && static_pointer_cast<BulletUpdateComponent>(

        (*bulletIt).getFirstUpdateComponent())

        ->m_BelongsToPlayer)

When this test is true, a sound is played, the bullet is moved off-screen, the number of invaders is decremented, the player's score is increased, and the invader is set to inactive.

Now, we will detect player collisions and the invader's direction of travel.

Add the detectPlayerCollisionsAndInvaderDirection function, as follows:

void PhysicsEnginePlayMode::

detectPlayerCollisionsAndInvaderDirection(

    vector<GameObject>& objects,

    const vector<int>& bulletPositions)

{

Vector2f offScreen(-1, -1);

FloatRect playerCollider =

    m_Player->getEncompassingRectCollider();

shared_ptr<TransformComponent> playerTransform =

    m_Player->getTransformComponent();

Vector2f playerLocation =

    playerTransform->getLocation();

auto it3 = objects.begin();

auto end3 = objects.end();

for (it3;

    it3 != end3;

    ++it3)

{

    if ((*it3).isActive() &&

        (*it3).hasCollider() &&

        (*it3).getTag() != "Player")

    {

        // Get a reference to all the parts of

        // the current game object we might need

        FloatRect currentCollider = (*it3)

            .getEncompassingRectCollider();

        // Detect collisions between objects

        // with the player

        if (currentCollider.intersects(playerCollider))

        {

            if ((*it3).getTag() == "bullet")

            {

                SoundEngine::playPlayerExplode();

                WorldState::LIVES--;

                (*it3).getTransformComponent()->

                    getLocation() = offScreen;

            }

            if ((*it3).getTag() == "invader")

            {

                SoundEngine::playPlayerExplode();

                SoundEngine::playInvaderExplode();

                WorldState::LIVES--;

                (*it3).getTransformComponent()->

                    getLocation() = offScreen;

                WorldState::SCORE++;

                (*it3).setInactive();

            }

        }

        shared_ptr<TransformComponent>

            currentTransform =

            (*it3).getTransformComponent();

        Vector2f currentLocation =

            currentTransform->getLocation();

        string currentTag = (*it3).getTag();

        Vector2f currentSize =

            currentTransform->getSize();

        // Handle the direction and descent

        // of the invaders

        if (currentTag == "invader")

        {

            // This is an invader

            if (!m_NeedToDropDownAndReverse &&

                !m_InvaderHitWallThisFrame)

            {

                // Currently no need to dropdown

                // and reverse from previous frame

                // or any hits this frame

                if (currentLocation.x >=

                    WorldState::WORLD_WIDTH –

                            currentSize.x)

                {

                    // The invader is passed its

                    // furthest right position

                    if (static_pointer_cast

                        <InvaderUpdateComponent>((*it3)

                        .getFirstUpdateComponent())->

                        isMovingRight())

                    {

                        // The invader is travelling

                        // right so set a flag that

                        // an invader has collided

                         

                        m_InvaderHitWallThisFrame

                                         = true;

                    }

                }

                else if (currentLocation.x < 0)

                {

                    // The invader is past its furthest

                    // left position

                    if (!static_pointer_cast

                        <InvaderUpdateComponent>(        

                            (*it3).getFirstUpdateComponent())

                        ->isMovingRight())

                    {

                        // The invader is travelling

                        // left so set a flag that an

                        // invader has collided

                        m_InvaderHitWallThisFrame

                                         = true;

                    }

                }

            }

            else if (m_NeedToDropDownAndReverse

                && !m_InvaderHitWallPreviousFrame)

            {

                // Drop down and reverse has been set

                if ((*it3).hasUpdateComponent())

                {

                    // Drop down and reverse

                    static_pointer_cast<

                            InvaderUpdateComponent>(            

                            (*it3).getFirstUpdateComponent())

                    ->dropDownAndReverse();

                }

            }

        }

    }

}

}

The preceding code is longer than the previous function because we are checking for more conditions. Before the code loops through all the game objects, it gets a reference to all the relevant player data. This is so we don't have to do this for every check:

FloatRect playerCollider =

    m_Player->getEncompassingRectCollider();

shared_ptr<TransformComponent> playerTransform =

    m_Player->getTransformComponent();

Vector2f playerLocation =

    playerTransform->getLocation();

Next, the loop goes through every game object. The first if test checks whether the current object is active, has a collider, and is not the player. We don't want to test the player colliding with themselves:

if ((*it3).isActive() &&

    (*it3).hasCollider() &&

    (*it3).getTag() != "Player")

The next if test does the actual collision detection to see if the current game object intersects with the player:

if (currentCollider.intersects(playerCollider))

Next, there are two nested if statements: one that handles collisions with a bullet belonging to an invader and one that handles collisions with an invader.

Next, the code checks each and every game object that is an invader to see whether it has hit the left- or right-hand side of the screen. Note that the m_NeedToDropDownAndReverse and m_InvaderHitWallLastFrame Boolean variables are used because it will not always be the first invader in the vector that hits the side of the screen. Therefore, detecting the collision and triggering dropdown and reversal are handled in consecutive frames to guarantee that all the invaders drop down and reverse, regardless of which one of them triggers it.

Finally, when both conditions are true, handleInvaderDirection is called.

Add the handleInvaderDirection function, as follows:

void PhysicsEnginePlayMode::handleInvaderDirection()

{

    if (m_InvaderHitWallThisFrame) {

        m_NeedToDropDownAndReverse = true;

        m_InvaderHitWallThisFrame = false;

    }

    else {

        m_NeedToDropDownAndReverse = false;

    }

}

This function just sets and unsets Booleans accordingly so that the next pass through the detectPlayerCollisionAndDirection function will actually drop-down the invaders and change their direction.

Add the initialize function to prepare the class for action:

void PhysicsEnginePlayMode::initilize(GameObjectSharer& gos) {

    m_PUC = static_pointer_cast<PlayerUpdateComponent>(

        gos.findFirstObjectWithTag("Player")

        .getComponentByTypeAndSpecificType("update", "player"));

    m_Player = &gos.findFirstObjectWithTag("Player");

}

In the preceding code, the pointer to PlayerUpdateComponent is initialized, as well as the pointer to the player GameObject. This will avoid calling these relatively slow functions during the game loop.

Add the detectCollisions function, which will be called from the GameScreen class once each frame:

void PhysicsEnginePlayMode::detectCollisions(

    vector<GameObject>& objects,

    const vector<int>& bulletPositions)

{

    detectInvaderCollisions(objects, bulletPositions);

    detectPlayerCollisionsAndInvaderDirection(

        objects, bulletPositions);

    handleInvaderDirection();    

}

The detectCollisions function calls the three functions that handle the different phases of collision detection. You could have lumped all the code into this single function, but then it would be quite unwieldy. Alternatively, you could separate the three big functions into their own .cpp files, just like we did with the update and draw functions in the Thomas Was Late game.

In the next section, we will create an instance of the PhysicsEngineGameMode class and use it in the GameScreen class as we bring the game to life.

Making the game

By the end of this section, we will have a playable game. In this section, we will add code to the GameScreen class to bring together everything we have been coding over the last three chapters. To get started, add an instance of PhysicsEngineGameMode to GameScreen.h by adding an extra include directive, as follows:

#include "PhysicsEnginePlayMode.h"

Then, declare an instance, as highlighted in the following code:

private:

    ScreenManagerRemoteControl* m_ScreenManagerRemoteControl;

    shared_ptr<GameInputHandler> m_GIH;

    PhysicsEnginePlayMode m_PhysicsEnginePlayMode;

Now, open the GameScreen.cpp file, add some extra include directives, and forward-declare the BulletSpawner class, as highlighted in the following code:

#include "GameScreen.h"

#include "GameUIPanel.h"

#include "GameInputHandler.h"

#include "GameOverUIPanel.h"

#include "GameObject.h"

#include "WorldState.h"

#include "BulletUpdateComponent.h"

#include "InvaderUpdateComponent.h"

class BulletSpawner;

int WorldState::WORLD_HEIGHT;

int WorldState::NUM_INVADERS;

int WorldState::NUM_INVADERS_AT_START;

Next, in the GameScreen.cpp file, update the initialize function by adding the following highlighted code inside the existing code:

void GameScreen::initialise()

{

    m_GIH->initialize();

    m_PhysicsEnginePlayMode.initilize(

        m_ScreenManagerRemoteControl->

        shareGameObjectSharer());

    WorldState::NUM_INVADERS = 0;

    // Store all the bullet locations and

    // Initialize all the BulletSpawners in the invaders

    // Count the number of invaders

    int i = 0;

    auto it = m_ScreenManagerRemoteControl->

        getGameObjects().begin();

    auto end = m_ScreenManagerRemoteControl->

        getGameObjects().end();

    for (it;

        it != end;

        ++it)

    {

        if ((*it).getTag() == "bullet")

        {

            m_BulletObjectLocations.push_back(i);

        }

        if ((*it).getTag() == "invader")

        {

            static_pointer_cast<InvaderUpdateComponent>(

                (*it).getFirstUpdateComponent())->

                initializeBulletSpawner(

                    getBulletSpawner(), i);

            WorldState::NUM_INVADERS++;

        }

        ++i;

    }

    m_GameOver = false;

    if (WorldState::WAVE_NUMBER == 0)

    {

        WorldState::NUM_INVADERS_AT_START =

            WorldState::NUM_INVADERS;

            

        WorldState::WAVE_NUMBER = 1;

        WorldState::LIVES = 3;

        WorldState::SCORE = 0;

    }

}

The preceding code in the initialize function initializes the physics engine that will handle all the collision detection. Next, it loops through all the game objects and performs two tasks: one task in each of the if blocks.

The first if block tests whether the current game object is a bullet. If it is, then its integer location in the vector of game objects is stored in the m_BulletObjectLocations vector. Remember from when we coded the physics engine that this vector is useful when doing collision detection. The vector will also be used in this class to keep track of the next bullet to use when the player or an invader wants to take a shot.

The second if block detects whether the current game object is an invader and, if it is, calls the initializeBulletSpawner function on its update component and passes in a pointer to a BulletSpawner by calling the getBulletSpawner function. The invaders are now capable of spawning bullets.

Now, we need to add some code to the update function to handle what happens in each frame of the game during the updating phase. This is highlighted in the following code. All the new code goes inside the already existing if(!m_GameOver) block:

void GameScreen::update(float fps)

{

    Screen::update(fps);

    if (!m_GameOver)

    {

        if (m_WaitingToSpawnBulletForPlayer)

        {

            static_pointer_cast<BulletUpdateComponent>(

                m_ScreenManagerRemoteControl->

                getGameObjects()

                [m_BulletObjectLocations[m_NextBullet]].

                getFirstUpdateComponent())->

                spawnForPlayer(

                      m_PlayerBulletSpawnLocation);

            

            m_WaitingToSpawnBulletForPlayer = false;

            m_NextBullet++;

            if (m_NextBullet == m_BulletObjectLocations

                      .size())

            {

                m_NextBullet = 0;

            }

        }

        if (m_WaitingToSpawnBulletForInvader)

        {

            static_pointer_cast<BulletUpdateComponent>(

                m_ScreenManagerRemoteControl->

                getGameObjects()

                [m_BulletObjectLocations[m_NextBullet]].

                getFirstUpdateComponent())->

                spawnForInvader(

                      m_InvaderBulletSpawnLocation);

            

            m_WaitingToSpawnBulletForInvader = false;

            m_NextBullet++;

            if (m_NextBullet ==

                      m_BulletObjectLocations.size())

            {

                m_NextBullet = 0;

            }

        }

        auto it = m_ScreenManagerRemoteControl->

            getGameObjects().begin();

        auto end = m_ScreenManagerRemoteControl->

            getGameObjects().end();

        for (it;

            it != end;

            ++it)

        {

            (*it).update(fps);

        }

        

        m_PhysicsEnginePlayMode.detectCollisions(

            m_ScreenManagerRemoteControl->getGameObjects(),

            m_BulletObjectLocations);

        if (WorldState::NUM_INVADERS <= 0)

        {

            WorldState::WAVE_NUMBER++;

            m_ScreenManagerRemoteControl->

                loadLevelInPlayMode("level1");

        }

        

        if (WorldState::LIVES <= 0)

        {

            m_GameOver = true;            

        }

    }

}

In the preceding new code, the first if block checks whether a new bullet is required for the player. If it is the next available bullet, the GameObject instance, has its BulletUpdateComponent instance's spawnForPlayer function called. The specific GameObject instance to use is identified using the m_NextBulletObject variable with the m_BulletObjectLocations vector. The remaining code in the first if block prepares for the next bullet to be fired.

The second if block executes if an invader is waiting for a bullet to be fired. Exactly the same technique is used to activate a bullet, except the spawnForInvader function is used, which sets it moving downward.

Next, there is a loop which loops through every game object. This is key to everything because, inside the loop, the update function is called on every GameObject instance.

The final line of code in the preceding new code calls the detectCollisions function to see if any of the GameObject instances (in their just-updated positions) have collided.

Finally, we will add some code to the draw function in GameScreen.cpp. The new code is highlighted inside the existing code in the following listing:

void GameScreen::draw(RenderWindow & window)

{    

    // Change to this screen's view to draw

    window.setView(m_View);

    window.draw(m_BackgroundSprite);

    // Draw the GameObject instances

    auto it = m_ScreenManagerRemoteControl->

        getGameObjects().begin();

    auto end = m_ScreenManagerRemoteControl->

        getGameObjects().end();

    for (it;

        it != end;

        ++it)

    {

        (*it).draw(window);

    }

    // Draw the UIPanel view(s)

    Screen::draw(window);

}

The preceding code simply calls the draw function on each of the GameObject instances in turn. Now, you have completed the Space Invaders ++ project and can run the game. Congratulations!

Understanding the flow of execution and debugging

Much of the last four chapters has been about the code structure. It is very possible that you still have doubts and uncertainties about which class instantiates which instance or in what order the various functions are called. Wouldn't it be useful if there was a way to execute the project and follow the path of execution from int main() right through to return 0; in the Space Invaders ++.cpp file? It turns out we can, and the following is how to do it.

We will now explore the debugging facilities in Visual Studio while simultaneously trying to understand the structure of the project.

Open the Space Invaders ++.cpp file and find the first line of code, as follows:

GameEngine m_GameEngine;

The preceding code is the first line of code that gets executed. It declares an instance of the GameEngine class and sets all our hard work in motion.

Right-click the preceding line of code and select Breakpoint | Insert Breakpoint. The following is what the screen should look like:

Notice that there is a red circle next to the line of code. This is a breakpoint. When you run the code, execution will pause at this point and we will have some interesting options available to us.

Run the game in the usual way. When execution pauses, an arrow indicates the current line of execution, as shown in the following screenshot:

If you hover the mouse over the m_GameEngine text and then click the arrow (the top-left corner in the following screenshot), you will get a preview of all the member variables and their values in the m_GameEngine instance:

Let's progress through the code. In the main menu, look for the following set of icons:

If you click the arrow icon highlighted in the previous screenshot, it will move to the next line of code. This arrow icon is the Step into button. The next line of code will be the top of the GameEngine constructor function. You can keep clicking the Step into button and examine the value of any of the variables at any stage.

If you click into the initialization of m_Resolution, then you will see the code jumps into the Vector2i class provided by SFML. Keep clicking to see the code flow progress through all the steps that make up our game.

If you want to skip to the next function, you can click the Step out button, as shown in the following screenshot:

Follow the flow of execution for as long as it interests you. When you are done, simply click the Stop button, as shown in the following screenshot:

Alternatively, if you want to run the game without stepping through the code, you can click the Continue button shown in the following screenshot. Note, however, that if the breakpoint is placed inside a loop, it will stop each time the flow of execution reaches the breakpoint:

If you want to examine the flow of code from a different starting point and don't want to have to click through every line or function from the start, then all you need to do is set a different breakpoint.

You can delete a breakpoint by stopping debugging (with the Stop button), right-clicking the red circle, and selecting Delete Breakpoint.

You could then begin stepping through the game loop by setting a breakpoint at the first line of code in the update function of GameEngine.cpp. You can put a breakpoint anywhere, so feel free to explore the flow of execution in individual components or anywhere else. One of the key parts of the code that is worth examining is the flow of execution in the update function of the GameScreen class. Why not try it?

While what we have just explored is useful and instructive, the real purpose of these facilities provided by Visual Studio is to debug our games. Whenever you get behavior that is not as you expect, just add a breakpoint to any likely lines that might be causing the problem, step through the execution, and observe the variable values.

Reusing the code to make a different game and building a design mode

On a few occasions, we have already discussed  the possibility that this system we have coded can be reused to make a totally different game. I just thought it was worth giving this fact a full hearing.

The way that you would make a different game is as follows. I have already mentioned that you could code the appearance of game objects into new components that derive from the GraphicsComponent class and that you could code new behaviors into classes that derive from the UpdateComponent class.

Suppose you wanted a set of game objects that had overlapping behaviors; consider perhaps a 2D game where the enemy hunted the player and then shot at the player at a certain distance.

Maybe you could have an enemy type that got close to the player and fired a pistol at the player and an enemy type that took long distance shots at the player, like a sniper might.

You could code an EnemyShooterUpdateComponent class and an EnemySniperUpdateComponent class. You could get a shared pointer to the player transform component during the start function and code an abstract class (such as  BulletSpawner) to trigger spawning shots at the player, and you would be done.

Consider, however, that both of these game objects would have code to take a shot and code to close in on the player. Then consider that, at some stage, you might want a "brawler" enemy who tries to punch the player.

The current system can also have multiple update components. You could then have a ChasePlayerUpdateComponent class which closes in on the player and separate update components to punch, shoot, or snipe the player. The punching/shooting/sniping component would enforce some values on the chasing component regarding when to stop and start chasing, and then the more specific component (punch, shoot, or snipe) would attack the player when prompted that the time was right.

As we've already mentioned, the ability to call the update function on multiple different update components is already built into the code, although it has never been tested. If you take a look at the update function in GameObject.cpp, you will see this code:

    for (int i = m_FirstUpdateComponentLocation; i <

        m_FirstUpdateComponentLocation +

        m_NumberUpdateComponents; i++)

    {

   …

}

In the preceding code, the update function would be called on as many update components that are present. You just need to code them and add them to specific game objects in the level1.txt file. Using this system, a game object can have as many update components as it needs, allowing you to encapsulate very specific behaviors and share them as needed around the required game objects.

When you want to create a pool of objects, like we did for the invaders and the bullets, you can be more efficient than we were in the Space Invaders ++ project. For the purposes of showing you how to position objects in the game world, we added all the invaders and bullets individually. In a real project, you would simply design a type that represents a pool of bullets, perhaps a magazine of bullets, like so:

[NAME]magazine of bullets[-NAME]

You could do the same for a fleet of invaders:

[NAME]fleet of invaders[-NAME]

Then, you would code the factory to handle a magazine or a fleet, probably with a for loop, and the slightly cumbersome text file would be improved upon. And, of course, there is no limit to the number of different levels you can design across multiple text files. More likely names for these text files are beach_level.txt or urban_level.txt.

You might have wondered about the names of some of the classes, such as  PhysicsEnginePlayMode or GameObjectFactoryPlayMode. This implies that …PlayMode is just one option for these classes.

The suggestion I am making here is that, even if you use the fleet/magazine strategy in your level design files, they could still become cumbersome and unwieldy as they grow. It would be much better if you could view the levels and edit them on-screen and then save changes back to the file.

You would certainly need new physics engine rules (detecting clicks and drags on objects), a new screen type (that didn't update each frame), and probably new classes for interpreting and building the objects from the text files. The point is, however, that the Entity-Component/screen/UI panel/input handling systems could remain unchanged.

There isn't even anything stopping you from devising some completely new component types, for example, a scrolling background object that detects which direction the player is moving and moves accordingly, or perhaps an interactive lift object that detects when the player is standing on it and then accepts input to move up and down. We could even have a door that opens and closes, or a teleport object that detects input when the player is touching it and loads a new level from another text file. The point here is that these are all game mechanics that can be easily integrated into the same system.

I could go on about these possibilities for much longer, but you would probably rather make your own game.

Summary

In this chapter, we finally completed the Space Invaders ++ game. We coded a way for game objects to request bullets to be spawned, learned how to receive input from a gamepad, and we put in the final logic of the game to bring it to life.

Perhaps the most important thing to take from this chapter, however, is how the toil of the last four chapters will help you get started on your next project.

There is one final chapter in this slightly chunky book, and it is a short and simple one, I promise.

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

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