Feeds:
Posts
Comments

Using the STL properly

So in my quest to learn C++ and perfect the code that my games are running, I’ve come across a lot of people who say that most C++ programmers are just writing C with classes and one of the best ways to take advantage of the language is to use the STL. The main reasoning is that the STL was written by the best minds in programming and has been made as efficient as possible, so using them will almost always be quicker than writing your own loops.

Take for instance removing objects from a vector. In my code there was a lot of this kind of code:

for(std::vector<Object*>::iterator i = objects.begin(); i != objects.end();)
{
    //process the objects

    if((*i)->isDead())
    {
        delete *i;
        i = objects.erase(i);
    }
    else
    {
        ++i;
    }
}

Which I used to think was really efficient, looked quite organised and was easy to follow. Unfortunately, it’s not that efficient at all. Whenever erase is called, every member of the vector that is after the element erased has to be moved up one space to fill in the gap. In my ParticleEngine class I’m managing 400 particles, of which 40 are going to be dead at any one time. That means that 40 times in one pass through that for loop a couple of hundred objects are having to be reassigned positions in memory, which is obviously going to be a massive slowdown and hog on performance. This was showing itself up in rudimentary testing when I’d see a jump in CPU usage on task manager whenever a ParticleEngine was called into being.

Needless to say I’ve now found a better way and the worst part is that the answer was under my nose the whole time. Scott Meyers’ book Effective STL documents the best way to erase elements from a container and needless to say, it isn’t the way above. He recommends using std::remove followed by the container’s erase member function. std::remove takes the elements to be removed and moves them to the end of the vector, returning an iterator to the end of the range that are not to be removed (or the beginning of the range of elements that are to be removed). Erase then uses this as it’s starting point and erases all elements from this point to the end of the original container. So how do we know which elements to remove/erase is the next question, leading on to another neat trick from Meyers’ book. Any pointers that aren’t needed any more can be deleted and set to NULL, that way all we have to look for is NULL pointers and mark all of these for erasing. Hence the following code can now replace the above:

void processObject(Object* o)
{
    //process o

    if(o->isDead())
    {
        delete o;
        o = NULL;
    }
}

//go through objects, processing each one
std::for_each(objects.begin(), objects.end(), processObject);

//remove any NULL Object*, then erase them from objects
objects.erase( std::remove(objects.begin(), objects.end(), static_cast<Object*>(NULL)), objects.end() );

This code requires one loop through the container for for_each, another for remove and then a loop through however many objects are to be erased. So for my ParticleEngine example, I’ve gone from reorganising a vector of 400 items up to 40 times a loop to once in std::remove. Much quicker and nicer.

As someone who’s interested in acquiring a job in programming in the near future, I recently came to the conclusion that I should probably create a few demo projects that can be sent in with job applications. As the industry is unlikely to be at the cutting edge of compilers, thanks to established code bases, and probably even less likely to be using the GCC compiler, I made a move over to Microsoft Visual Studio. To ensure the code worked, this meant a move away from all my C++11 code – the removal of smart pointers, the auto keyword, range based for loops, member initialization in header files and others.

To start this off I chose to work once again on my Brick Buster project, thinking that my knowledge of the code and it’s (relative) brevity would enable the process. The first thing to be removed was member initialization in header files, which meant a return to long constructor initialization lists. The move away from C++11 also meant losing delegating constructors, so I had to rewrite my Ball class. This actually helped clean the code up as I went from three to 2 constructors.

The next step was to remove all traces of smart pointers, a task that I thought would be arduous at best and fatal to stability at worst. In reality it was remarkably simple – I removed each smart pointer one at a time, compiling after each batch of changes to ensure that the code still worked. The only really tricky change came in the Game class where the currentState pointer had to be changed from a unique_ptr to a GameState*, which had a knock on effect for my FSM. In the end I decided to pass a reference to the currentState into the FSM, which would delete it and return a new GameState* which could be assigned to currentState:

currentState = FSM::changeState(currentState, player, imgCache, stateMan);

Of course the major fallout from the loss of smart pointers is memory management. Making sure that the correct delete call is placed everywhere it is required is not an easy task and led to my destructors increasing in size from nothing to multiple lines. To counteract the repetition of for loops containing delete and erase instructions for vectors, I wrote a template deleting function which will work with any container that has an iterator:

template <typename T>
void deletePtrCntr(T& cntr) const
{
    for(typename T::iterator it = cntr.begin(); it != cntr.end();)
    {
        delete *it;
        it = cntr.erase(it);
    }
}

Next step was the auto keyword. Now I love the auto keyword, it makes writing iterators infinitely quicker (4 chars vs 25+) and easier and allows the user of std::pair in a much nicer way. However, with the loss of C++11 they were written out and replaced by their verbose C++03 counterparts. As part of this removal, range based for loops disappeared from the code as well, rewritten back to the same standard as every other for loop.

One thing that almost got forgotten in the change over was enum classes. I have two of these in Brick Buster: one for the states and one for the power ups. The reason I use them is to give them their own type (instead of returning an int from a function, I can return a State or a GamePowerUp) and to keep the namespace pollution down (instead of calling my enum members STATE_TITLE, I can call them through State::Title or GamePowerUp::BigBall), both brilliant additions in my opinion. However if these were to be removed it would mean renaming the members to make it more obvious, or so I thought until I had the brainwave to put them inside their own namespaces so that they can still be called through State::Title or GamePowerUp::Multiball. Of course any function that previously returned a State or GamePowerUp had to be changed to return an int, but this was a minor change that caused almost no nuisance.

Of course as with all code rewrites, improvements are made to the codebase and it now looks a lot more organised. I’ve changed (back) to references from pointers in my base GameState class on the basis that none of the classes should ever be NULL as they are constructed before any GameState is ever created. This makes things slightly easier, removing the need for pointer dereferencing and -> everywhere.

To make the code portable, I put the SDL files inside the project and pointed the linker at the relevant folders so that anyone can compile the game on their computer if they have VS. This has given me the incentive and knowledge to go an rewrite my other games for VS and hopefully they won’t take to long to complete either.

The Brick Buster for MS VS10 is on the downloads page

New bits and bobs

So I’ve added a new game to the downloads page – Blackjack. It’s a fairly basic game featuring yourself, 4 randomly named computer players (well, randomly named from a list of 30) and a dealer. You can see all the cards that are dealt (bar the dealer’s “hole” card) although this may change in future versions. The computer follows the basic strategy from the Wikipedia page on the game. If you, or the computer, is unfortunate to go bust during the game, your name will go black and get struck through making it obvious. You play through the use of buttons on the screen and there is a running commentary (current player and their score) going in the top left corner. Give it a look, as always it’s programmed in C++11 and SDL, using Code::Blocks and compiled using GCC 4.7.

There’s also been an update to Brick Buster – the text has changed to white (whew, big stuff!) and now when you destroy a brick it’s score will pop up on the screen where the brick used to be to let you know what you just scored. This only hangs around for 750ms (3/4 of a second) and is programmed through a rethinking of the ParticleEngine class. The Particle class was scrapped, and ParticleEngine renamed to ScoreEngine. It is now passed an SDL_Surface* along with the original Point2D in it’s constructor. This SDL_Surface* is rendered at the Point2D until it is considered “dead” (i.e. once the 750ms have run out) when it is erased.

The code that creates the ScoreEngines is shown below. The important thing was to remove the ScoreEngine code from the ball->move() call because only GameState’s can create SDL_Surface*’s, it makes no sense for the Ball to have any knowledge of an ImageCache. I’ve also made a fairly major change to where PowerUp’s are generated, finally finding a solution that allows me to remove the logic from the Ball class and put it in the Level class. Again, it makes no sense for the Ball to have any knowledge of the PowerUps in the game as it doesn’t have to interact with them (like the bricks) and the only reason the logic was there because that was how I first coded it (the ball hit a brick and a powerup was randomly generated). The moveScore return value was even there for ages before I realised that I could move the logic out because a moveScore > 0 meant that a brick had been destroyed and so a powerup could be randomly generated. Anyway, that is also shown below in the new Ball movement logic in Level.

for(auto it = balls.begin(); it != balls.end();)
{
    int moveScore = (*it)->move(bat->getBox(), bat->getXVel(), bricks, powerUps);

    if(moveScore  < 0)
        it = balls.erase(it);
    else if (moveScore > 0)
    {
        sEngines.push_back(std::make_shared<ScoreEngine>((*it)->getPosition(), imgCache->getText(brickFont, "", moveScore)));
        generatePowerUp((*it)->getPosition());

        levelScore += moveScore;
        ++it;
    }
    else
    {
        ++it;
    }
}

The new code is up on the downloads page.

Inspired by this thread over at Gamedev, I set out on yet another learning journey to build my own particle engine. After looking at the links on that thread, I remembered that the legendary Lazy Foo has his own article on Particle Engines and headed over there to have a perusal (as you should, his explanation will make a lot more sense than mine…).

So it turns out that as with most of these things, you need to set up the base class first (well, it’s not technically a base class in the normal sense of the word, but it’s the basis of our engine) – a Particle class. Of course as each particle is drawn to the screen that makes it a Sprite in my project. An incredibly simple sprite at that too. One function to manage the sprite on a frame by frame basis and one to calculate if it’s dead. The two private functions are for creating the image filename and ensuring the particles appear in a circle.

class Particle : public Sprite
{
    private:
        int frame;

        inline const double distance(const Point2D p1, const Point2D p2) const;
        std::string chooseImage();
    public:
        Particle(Point2D point);
        ~Particle() = default;

        void manage();
        bool isDead();
};

Creation of a particle is a fairly simple routine – randomly create points either side of the centre point, ensuring that they’re no more than a distance of 75 from the centre (the radius, ensuring that the particles are in a circular shape). Particles are given a random frame number so they don’t all appear and disappear at the same time. chooseImage() randomly chooses whether the image will be dark grey or light grey.

Particle::Particle(Point2D point) : Sprite(chooseImage())
{
    //Set offsets
    short xDir, yDir;
    switch(rand() % 4)
    {
        case 0: xDir = 1; yDir = 1; break;
        case 1: xDir = -1; yDir = 1; break;
        case 2: xDir = 1; yDir = -1; break;
        case 3: xDir = -1; yDir = -1; break;
    }

    //ensure that the smoke appears in a circular shape
    do
    {
        position.x = point.x + (xDir * (rand() % 75));
        position.y = point.y + (yDir * (rand() % 75));
    }
    while(distance(point, position) > 75);

    //Initialize animation
    frame = rand() % 5;
}

manage() increments the frame counter each frame and isDead checks whether the frame counter has cleared 10, the maximum number of frames a particle can exist for.

The ParticleEngine class handles the management, creation and destruction of the particles in it’s constructor and managePartices() function. The two iterator functions are so that the particles can be drawn from drawSpriteCntr(T) in the Level class. isRunning() provides a check to see if the engine’s running time has expired.

class ParticleEngine
{
    private:
        using ParticlePtr = std::shared_ptr<Particle>;
        const unsigned short numberOfParticles = 500;
        const unsigned short engineTime = 350;

        Point2D position;
        std::vector<ParticlePtr> particles;
        const int startTime;

    public:
        ParticleEngine(Point2D point);
        ~ParticleEngine() = default;

        void manageParticles(int groundLevel);
        bool isRunning();

        std::vector<ParticlePtr>::iterator begin() { return std::begin(particles); }
        std::vector<ParticlePtr>::iterator end() { return std::end(particles); }
};

The constructor takes the Point2D passed in and moves it a little bit to centre the explosion on the point of impact, then creates the numberOfParticles stated by the const in the header.

ParticleEngine::ParticleEngine(Point2D point) : startTime(SDL_GetTicks())
{
    position.x = point.x + 4;
    position.y = point.y + 10;

    for( int p = 0; p < numberOfParticles; p++ )
    {
        particles.push_back(std::make_shared<Particle>(position));
    }
}

manageParticles() iterates through the particles, managing each of them (i.e. incrementing their frame counter). It checks whether they have expired and if so erases the current particle and pushes a new one onto the vector in it’s place.

void ParticleEngine::manageParticles(int groundLevel)
{
    for(auto it = std::begin(particles); it != std::end(particles);)
    {
        (*it)->manage();

        if((*it)->isDead())
        {
            it = particles.erase(it);
            particles.push_back(std::make_shared<Particle>(position));
        }
        else
            ++it;
    }
}

Running the engine in the Level class is relatively simple after everything we’ve now set up. Add a std::vector<std::shared_ptr> to the Level members, then when a building is hit we call these two lines. The first one to position the engine, the second one to create it.

Point2D smokePos = bomb->getPosition();
pEngines.push_back(std::make_shared<ParticleEngine>(smokePos));

Then every ParticleEngine in the vector has to be managed at the end of the logic() function, through the following code, which basically says if the engine is running then manage it, otherwise erase it. Simple.

for(auto it = std::begin(pEngines); it != std::end(pEngines);)
{
    if((*it)->isRunning())
    {
        (*it)->manageParticles(groundLevel);
        ++it;
    }
    else
    {
        it = pEngines.erase(it);
    }
}

Finally we have to draw the engines to the screen. Again, thanks to our templated drawSpriteCntr(T) function, and our iterator functions in ParticleEngine, this is simple – iterate through the engines std::vector and draw them all to screen.

for(const auto& pE : pEngines)
{
    drawSpriteCntr(*pE);
}

And just like that, we have a working particle engine that produces effects such as this:

Now, no-one is going to mistake this for smoke anytime soon, but for me it does the job just fine. I’m sure I will return to this in an effort to improve it in the future but for now it’s a portable engine class that produces nice smoke effects that can easily be modified to produce other particle effects (fire for instance).

AudioCache

Whilst I’m happy with my games and the way they’ve come together, there has always been one thing missing – you can only play them in complete silence. There is no music, no sound effects, nothing to hook you in and help you to become part of the game. Every good game has sound and the best games make it an integral part of the experience. Whilst the rise of mobile gaming has reduced the impact of sound (in my experience at least – I very rarely have the sound on my iPhone turned on), in the PC and console market thousands is spent on creating the right atmosphere through the music and sound effects and also using sound as a key to the user about what’s about to happen (the music in Left 4 Dead is a prime example of this).

So to rectify this in my games (at a basic level anyway) I set out to integrate some audio. Luckily SDL has an extension library built to deal with sound already – SDL_mixer. I chose Bomber Run as my testing project of choice, figuring that the plane’s engine, building destruction and title menu music would give me enough variety to learn through.

Originally I started out using this wonderful tutorial to get a basic level of music playing on one game state. However when I tried to coordinate this across multiple game states (title and level), things got ugly and I came across a lot of null pointer segfault issues. This lead me to create an AudioCache that handled all of my sounds in the same way that my ImageCache handles all of my graphical needs.

In essence, AudioCache provides a wrapper around the SDL_mixer function calls and stores the sound files in a map in the same way that ImageCache stores it’s images, with the key being a std::string (i.e. the path to the file). I based all the functions on the ImageCache functions, because why try to reinvent the wheel?

The AudioCache class interface is pretty simple. The get…() function calls return the music or sound effect, whilst the play and stop methods are obvious. The load..() methods do the loading and flush() is used in the destructor (as in ImageCache)

class AudioCache
{
    public:
        AudioCache();
        virtual ~AudioCache();

        Mix_Music* getMusic(const std::string filename);
        Mix_Chunk* getEffect(const std::string filename);
        void playMusic(Mix_Music* music, int loops);
        void playEffect(int channel, Mix_Chunk* effect, int loops);
        void stopMusic();
        void stopChannel(int channel);

    private:
        std::map<std::string, Mix_Music*> music;
        std::map<std::string, Mix_Chunk*> effects;

        Mix_Music* loadMusic(const std::string file);
        Mix_Chunk* loadEffect(const std::string file);
        void flush();
};

The constructor for the AudioCache is incredibly simple. Initialize the SDL Audio subsystem, then open SDL_mixer with some settings

AudioCache::AudioCache()
{
    SDL_InitSubSystem(SDL_INIT_AUDIO);

    if(Mix_OpenAudio(22050, AUDIO_S16SYS, 1, 2048) == -1)
    {
        printf("Unable to open audio!\n");
        exit(80);
    }
}

To get a music file or a sound effect, I use the same method as in the ImageCache for getting an image or text out of the map. Iterate through it to find the filename and if it isn’t there, add it to the map and return the file. This shows the method for getting a Mix_Music* file

Mix_Music* AudioCache::getMusic(const std::string file)
{
    auto i = music.find(file);

    if(i == std::end(music))
    {
        try
        {
            Mix_Music* temp = loadMusic(file.c_str());
            i = music.insert(i, std::make_pair(file, temp));
        }
        catch(const char* e)
        {
            printf("%s", e);
            exit(81);
        }
    }

    return i->second;
}

The play and stop methods are literally just wrappers for the SDL_mixer functions, keeping all the audio controls to one class and removing the need for any knowledge of what’s going on in the game state. It also allows me to add in any other functionality that I may want to do before or after calling the relevant functions (i.e. exception checking).

I’ve added an AudioCache pointer to my base GameState class, meaning that every GameState can access it and play sound. The base class now looks like this:

class GameState
{
    protected:
        ImageCache* imgCache;
        AudioCache* audioCache;
        StateManager* stateMan;

    public:
        GameState(ImageCache* ic, AudioCache* ac, StateManager* sm) : imgCache(ic), audioCache(ac), stateMan(sm) {}
        virtual void handleEvents(SDL_Event& event) = 0;
        virtual void logic() = 0;
        virtual void draw() = 0;
        virtual ~GameState() {};
};

Looks like some kind of Game Engine is emerging here…

The Bomber Run skyline

In your average city, the skyline is a mixed collection of tall buildings, small buildings, old and new, bright, colourful, glass skyscrapers and dull concrete office block abominations. Representing this using SDL and basic 2D graphics is never going to look like Crysis, but giving the user a city skyline that is varied and colourful is an important part of Bomber Run. Giving the user the same city skyline every time they play the game is certain to lead to boredom and a lack of replay value, so randomising as much of the process as possible is an important part of the game.

To randomise things in game, I use the basic random generator in C++. It needs to be included in the file containing the main() function, then in the first line of that main function, the random number generator is seeded through srand(time(0)), the time(0) function returning the number of seconds since Jan 1st 1970. Each time you then want a random number anywhere in your program, you can just use the rand() function, and modulo it to whatever range you want your random number to be in. So if I want a number between 1 and 10, call rand() % 10 and then add 1 to it (because as with all things programming, your modulo answer is zero leading, i.e. between 0-9).

How is this relevant to building creation in any way? It is the function used to randomly generate the height of my buildings. The building header file looks like so:


class Building : public Sprite {
    private:
        const short buildingWidth = 25;
        const short storeyHeight = 20;
        short numberOfStoreys, height;
        SDL_Rect box;
        SDL_Color buildingColor;
        short score;
        short hitpoints;
        bool destroyed = false;

    public:
        Building(const short groundLevel, const short x, const short maxStoreys = 25);
        ~Building() = default;

        const SDL_Rect getBox() const { return box; }
        const SDL_Color getColor() const { return buildingColor; }
        const short getNumberOfStoreys() const { return numberOfStoreys; }
        const short getStoreyHeight() const { return storeyHeight; }
        const short getScore() const { return score; }
        const bool isDestroyed() const { return destroyed; }

        void hitByBomb(const short groundLevel);
};

A fairly simple class, with a load of accessor methods and the hitByBomb method. The constructor does the majority of the work here:

Building::Building(const short groundLevel, const short x, const short maxStoreys) : Sprite("building/building") {
    numberOfStoreys = (rand() % maxStoreys) + 1;
    height = numberOfStoreys * storeyHeight;
    box.x = x;
    box.y = groundLevel - height;
    box.w = buildingWidth;
    box.h = height;

    position.x = x;
    position.y = box.y;

    buildingColor.r = rand() % 256;
    buildingColor.g = rand() % 256;
    buildingColor.b = rand() % 256;

    if(buildingColor.r + buildingColor.g + buildingColor.b > 700) {
        switch(rand() % 3) {
            case 0: buildingColor.r -= 75;
                    break;
            case 1: buildingColor.g -= 75;
                    break;
            case 2: buildingColor.b -= 75;
                    break;
        }
    }

    score = numberOfStoreys;

    if(numberOfStoreys > 15)
        hitpoints = 2;
    else
        hitpoints = 1;
}

The first line immediately shows the use of the rand() function, using a modulo of maxStoreys. This was passed in as a parameter to the ctor so that in level 1 the highest building is limited to 15 storeys, levels 2-4 it’s 20 and the rest of the way the highest is 25, giving the user a chance to get used to the aiming of the bomb in the first few levels whilst the buildings are low and the plane accelerates slowly. There is a + 1 added on to the end of this because we don’t want to have buildings of 0 storeys, that would be meaningless, so we ensure that every building has at least one floor. The reason we have height and numberOfStoreys is because the buildings are drawn as SDL_Rects, basically a coloured in box on the screen, not blitted as an image is. Thus to represent the rectangle we need an x and y coordinate to position it and a width and height to fill in the sides.

The SDL_Rect’s y coordinate is groundLevel – height because SDL’s coordinate system has (0,0) in the top left corner with the y axis increasing as you move down the screen, and the SDL_Rect is drawn from it’s top left corner so we need to move the point up the screen from groundLevel (which is defined as screenHeight – 30 in the Level class).

The colour of the building is made up of 3 randomly generated numbers between 0 and 255 (using rand() % 256). The reason for the if statement is to try and get away from buildings that are white or so close that they can’t be seen on the background. We randomly choose one of the 3 parts to subtract 75 from (red, green or blue) to avoid this.

The score of a building is the number of storeys high it is, thus bigger buildings give better scores. But (there’s always a but in gaming!), any buildings over 15 storeys high have 2 hitpoints and thus require 2 hits to destroy them, adding in another element of difficulty for the user (remember of course that in level 1 all buildings are below 15 storeys, so they’ve had a chance to get their eye in before this comes up in level 2).

The hitByBomb method reduces the hitpoints by 1, then checks whether this has led to the building being destroyed or not. If not, reduce the numberOfStoreys by half then recalculate the height and various y coordinates needed.

void Building::hitByBomb(const short groundLevel) {
    if(--hitpoints == 0)
        destroyed = true;
    else {
        numberOfStoreys /= 2;
        height = numberOfStoreys * storeyHeight;
        box.h = height;
        box.y = groundLevel - height;
        position.y = box.y;
    }
}

Of course, using all of this in the game is very easy with all of the logic hidden away in the Building class. We create a vector of 32 buildings, each moved across 25 pixels each time (the width of a building). When checking collisions, we iterate through the vector, checking collisions with the bomb. If a building has been destroyed, we erase it from the vector. A pretty simple and obvious algorithm, just how we like it.

To draw the buildings, I had to create a new method in my ImageCache class – drawRectangle(). This takes an SDL_Rect and a colour and draws the first one on the screen in the second’s colour. To draw the windows on I start at the top of the building and work my way down it for the number of storeys in the building, drawing the windows on the top of the rectangle as I get to each storey.

for(auto& building : buildings) 
{
    imgCache->drawRectangle(building->getBox(), building->getColor());

    for(int i=0; i < building->getNumberOfStoreys(); ++i)
        imgCache->render(building->getImage(), {building->getPosition().x, building->getPosition().y + i*building->getStoreyHeight()} );
}

Just one more note – the skyline on the Title screen is also randomly generated, limited to a height of 15 storeys so that the plane can fly above it but still fit underneath the title. This gave a some interest and colour to a Title screen that was very blank, white and dull, whilst also giving the user an idea of what to expect when they start playing the game.

Bunkers and BunkerTiles

In Space Invaders, one of the most important parts to the gameplay are the 4 “bunkers” that are provided for the player to hide underneath to avoid the alien bullets. Trying to program these posed a challenge to me, as they decompose in pieces and not in one big go. Different parts of the bunker can be destroyed whilst the rest could be left standing. After playing around with various Space Invader clones (freespaceinvaders.org is good fun) I decided that the best way to model this was to build the bunker out of smaller pieces (the bunker tiles).

The BunkerTile header file is remarkably simple, comprising only 3 member variables (2 of which are const and equal!), a ctor, dtor and 2 member functions.

class BunkerTile : public Sprite {
    private:
        const int tileWidth = 10;
        const int tileHeight = tileWidth;
        SDL_Rect box;

    public:
        BunkerTile(const float x, const float y, const std::string imgFile);
        ~BunkerTile() {};

        const SDL_Rect getBox() const { return box; }
        const int changeTile();
};

getBox() is my default function for returning the Axis Aligned Bounding Box (AABB or “hit box” to CoD players) that is used for collision detection, returning the SDL_Rect that surrounds the tile (x, y at the top left of it, width and height as defined by the member variables).

changeTile() changes the image of the tile as it gets hit, a crass (but effective for someone not au fait with graphics programming) way of showing decomposition of the bunker.

The Bunker header file is also fairly simple, mainly providing std::vector iterator functions so the user can access every tile contained within the bunker.

class Bunker {
    private:
        typedef std::shared_ptr<BunkerTile> TilePtr;
        std::vector<TilePtr> tiles;

        void loadTiles(const int bunkerNum);
    public:
        Bunker(const int bunkerNum);
        ~Bunker() {};

        std::vector<TilePtr>::iterator begin() { return tiles.begin(); }
        std::vector<TilePtr>::iterator end() { return tiles.end(); }
        std::vector<TilePtr>::iterator erase(std::vector<TilePtr>::iterator it) { return tiles.erase(it); }
};

This basically shows that a Bunker is merely a collection (std::vector) of BunkerTiles. It’s a way of grouping the tiles together into a class effectively (a Bunker is an object, and it is composed of BunkerTiles) to make working with them easier at the game level (i.e. drawing a bunker can be done through my drawSpriteCntr() templated method thanks to the begin and end methods being implemented).

To make editing bunkers easier, and trying to avoid the need for endless compile time when I wanted to move a Bunker 10px to the right, the coordinates and image name are kept in txt files in the same kind of format as my Brick Buster clone (i.e. 50 430 tilelc => x y imageFilename). This meant that I could reuse the same loadResources() method from my Brick Buster Level class to load the Bunker coordinates, a wonderful saving on code writing time.

The collision code for checking if a bullet has hit the bunker is a bit confusing, with nested for loops, but it’s fairly easy to explain (says the guy who wrote it…).

for(auto it = bunkers.begin(); it != bunkers.end();) 
{
    for(auto tileIt = (*it)->begin(); tileIt != (*it)->end();) 
    {
        if(checkCollision(bulletBox, (*tileIt)->getBox())) 
        {
            resetBullet = true;

            if((*tileIt)->changeTile() == -1)
                tileIt = (*it)->erase(tileIt);
            else
                ++tileIt;
        }
        else
            ++tileIt;
    }
    ++it
}

So the idea is that we want to check if any of the bunkers have been hit, and then we want to check if any of the tiles inside each bunker have been hit. If the bullet has hit the bunker (simple AABB collision detection), raise the flag as true, then change the tile’s image. If the changeTile() method returns -1, that means it has been hit for a third time and needs to be destroyed. If it doesn’t need to be destroyed, we need to look at the next tile. Ideally this will be optimized so that once the resetBullet flag is raised as true, we break out of everything instead of checking aimlessly for collisions we know aren’t going to take place, because a bullet can only hit one tile at a time (theoretically I suppose it could straddle two tiles next to each other, but it would be more efficient to ignore this as the user wouldn’t notice it if it weren’t there).

And to draw the bunkers, it’s as simple as typing:

for(auto& x : bunkers)
    drawSpriteCntr(*x);

As there is a vector of 4 bunkers in the game, we iterate through all of them, drawing their contents (dereferenced as they’re std::shared_ptr because of the dynamic allocation during runtime)