Feeds:
Posts
Comments

Archive for June, 2012

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.

Read Full Post »

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)

Read Full Post »

The most famous part of Space Invaders is the Invaders themselves. Their movement is known and expected behaviour: they move to the edge of the screen as a group, move down, then move back in the other direction at an increased pace, then repeat. Modelling this movement is easy with one Alien, modelling it with one column of Aliens is easy. The problem comes in when you introduce more than one Alien into a row, then start adding in more rows (up to 5 rows of 11 aliens). Then dealing with what happens when Aliens are shot down by the player and making sure the group still moves as a cohesive unit adds an extra level of difficulty. So, taking all of that in, how did I come to my design.

Lets start with the Alien class:

class Alien : public Sprite {
    private:
        const int alienWidth = 30;
        const int alienHeight = 25;
        const float alienStepDown = 15;
        const float alienStepAcross = 10;
        SDL_Rect box;
        bool alive = true;
        int score;

    public:
        Alien(const std::string file, const float x, const float y, const int xDirection = 1);
        ~Alien() = default;

        const bool isDead() const { return !alive; }
        const int getScore() const { return score; }
        const SDL_Rect getBox() {
            box.x = position.x;
            box.y = position.y;
            return box;
        }
        const int getHeight() const { return alienHeight; }
        const int getWidth() const { return alienWidth; }
        void setImage(const std::string newImage) { image = newImage; }

        void changeXDirection(const int newDirection) { direction.x *= newDirection; }

        void moveAcross();
        void moveDown();
};

Not a lot to the interface really, some getter methods, a set method, a couple of moving methods and one to change the x direction. The move across and move down methods are very easy as well – add the relevant part of the direction vector to the position, when moving down multiply the x direction by -1.

The AlienFleet is a class to contain the aliens in and control their movement. As a result, 4 of the public methods give access to the fleet’s vector:

class AlienFleet {
    private:
        typedef std::shared_ptr<Alien> AlienPtr;
        typedef std::shared_ptr<Bullet> BulletPtr;
        const int xSpacing = 40;
        const int ySpacing = 35;
        int topLeftX = 0;
        int topLeftY = 30;
        int topLeftXLimit;
        bool atEdgeOfScreen = false;
        int timeBetweenMoves = 750;
        int lastMoveTime = 0;
        int timeSinceLastMove = 0;
        int fleetDirection = 1;
        std::vector<AlienPtr> fleet;

        void loadAliens();
        void changeImage(AlienPtr& aptr);
        void fireBullet(const SDL_Rect& alienBox, std::vector<BulletPtr>& alienBullets) const;
    public:
        AlienFleet(const short screenWidth);
        ~AlienFleet() {};

        void move(std::vector<BulletPtr>& alienBullets, const short screenWidth);

        const unsigned size() const { return fleet.size(); }
        std::vector<AlienPtr>::iterator begin() { return fleet.begin(); }
        std::vector<AlienPtr>::iterator end() { return fleet.end(); }
        std::vector<AlienPtr>::iterator erase(std::vector<AlienPtr>::iterator it) { return fleet.erase(it); }

        void rebuildFleet();
};

The move method is the one that controls when the fleet’s movement around the screen:

void AlienFleet::move(std::vector<BulletPtr>& alienBullets, const short screenWidth) {
    //wait for enough time to elapse between moves
    timeSinceLastMove = SDL_GetTicks() - lastMoveTime;

    if(timeSinceLastMove > timeBetweenMoves) {
        //if the fleet is at the edge of the screen, move it down,
        //set the atEdgeOfScreen flag to false, reduce the time between moves and mvoe topLeftY down
        if(atEdgeOfScreen) {
            for(auto it = fleet.begin(); it != fleet.end(); ++it) {
                AlienPtr al = (*it);
                al->moveDown();
                changeImage(al);
                fleetDirection *= -1;
                fireBullet(al->getBox(), alienBullets);
            }

            atEdgeOfScreen = false;
            timeBetweenMoves *= 0.95;
            topLeftY += fleet[0]->getDirection().y;
        }
        else {
            //otherwise move the fleet across, checking to see if it has reached the edge of the screen
            for(auto it = fleet.begin(); it != fleet.end(); ++it) {
                AlienPtr al = (*it);
                al->moveAcross();
                changeImage(al);

                fireBullet(al->getBox(), alienBullets);

                if(!atEdgeOfScreen) {
                    SDL_Rect alienBox = al->getBox();
                    if(alienBox.x <= 0 || alienBox.x + alienBox.w >= screenWidth)
                        atEdgeOfScreen = true;
                }
            }
            //move topLeftX across, making sure it stays within it's limits
            if(fleet.size() == 1)
                topLeftX = fleet[0]->getPosition().x;
            else
                topLeftX += fleet[0]->getDirection().x;

            if(topLeftX < 0)
                topLeftX = 0;
            else if(topLeftX > topLeftXLimit)
                topLeftX = topLeftXLimit;
        }
        lastMoveTime = SDL_GetTicks();
    }
}

Each time the fleet is moved, the image of each alien is changed to give the impression of animated movement. When the fleet moves down, the time between each move is reduced to 95% of the previous delay. The topLeftX and topLeftY keep track of where the top left corner of the current fleet is, so that when it is all killed off, it can be rebuilt right on top of where it is through the rebuildFleet and loadAliens method. These have to be limited so that when the fleet is rebuilt it is all on the screen. To check the fleet is at the edge of the screen, get the SDL_Rect around each alien and see if it’s limits are at or past the edge of the screen. Each time the alien’s move, the fireBullet method is called. This is a fairly basic method, with a 2% chance that a bullet is actually fired and added to the alienBullets vector that is passed in by reference.

void AlienFleet::fireBullet(const SDL_Rect& alienBox, std::vector<BulletPtr>& alienBullets) const  
{
    if(rand() % 100 < 2) 
    {
        BulletPtr bptr(new Bullet(alienBox.x + alienBox.w/2, alienBox.y + alienBox.h, 1, "bullet/alienbullet"));
        alienBullets.push_back(bptr);
    }

}

The loadAliens method is quite simple, iterating through 5 rows of 11 aliens and using the topLeftX/Y members along with the x/ySpacing constants and the i and j variables from the for loop methods to position each alien on the screen.

void AlienFleet::loadAliens()
{
    for(int i=0; i<5; ++i)
    {
        for(int j=0; j<11; ++j)
        {
            std::string image = "";
            switch(i)
            {
                case 0: image = "alien1";
                        break;
                case 1:
                case 2: image = "alien3";
                        break;
                case 3:
                case 4: image = "alien5";
                        break;
            }

            AlienPtr aptr(new Alien(image, topLeftX + j*xSpacing, topLeftY + i*ySpacing));
            fleet.push_back(aptr);
        }
    }
}

Remember, you can get a copy of the source code on the downloads page. I’ll be putting a copy of the game without the source code online soon as well.

Read Full Post »

Within a game, as mentioned in the StateManager post, there are a variety of states that the game can be in. They start off with an intro menu screen, from which you can play the game, view the high scores table, view the controls or exit the game. Each of these states has a lot of common features that they have to implement – they have to handle the user’s input, move any Sprites on the screen, calculate any collision logic, and draw themselves to screen. To represent this and perform all these operations (remembering that changing state takes place through the StateManager class and rendering takes place through the ImageCache class) there is a base GameState class.

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

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

It is an abstract base class because it contains 3 pure virtual functions (handleEvents, logic and draw), shown by the “= 0” on the end of the method signature. This means that they have to be overridden by any derived classes of GameState but don’t need to have a method definition provided for the base class. The base class has a pointer to an ImageCache and StateManager, this allows each state to have the access to the ImageCache and StateManager methods without having to create a new copy of each whenever the state is changed (which would completely defeat the point of the ImageCache). I did originally represent these with references, but changed to a pointer as it seemed more obvious what was going on with a pointer.

GameStates are managed with the StateManager class and changed with the FSM class, but within the Game class they are represented with a std::unique_ptr

//in the Game class definition
std::unique_ptr<GameState> currentState;

//the Game class constructor
Game::Game() : imgCache(screenWidth, screenHeight), currentState(new Title(&imgCache, &stateMan)) {}

//in Game::changeState()
if(stateMan.getNextState() == State::Exit)
    stateMan.setThisState(State::Exit);
else if (stateMan.getNextState() != State::Empty) {
    FSM fsm;
    currentState = fsm.changeState(&imgCache, &stateMan, player);
}

So the unique_ptr is initialized in the Game constructor with the Title state, passing in the address of the imgCache and stateMan classes. To change the state, call the changeState method from FSM, passing in the address of the imgCache, stateMan and a reference to the player (we know it’s a reference from the method signature in the FSM interface: std::unique_ptr changeState(ImageCache* cache, StateManager* sm, Player& p1) ).

All of this combines to give a game that can change state seamlessly and without any memory leaks (thank you smart pointers!)

Read Full Post »

So I’ve finally got to the point with a few projects where I feel I can upload them to the internet for your perusal. There are currently 3 games on the downloads page with one more nearly completed (a Space Invaders clone). I’ve provided a bit of detail on each game below:

Pong Clone 3000
Most game programmers first project, this was my second after Blackjack (which upon further review is very, very shoddy). It was built as a two player game originally with a lot of the code from Blackjack reused. It was only after I finished Brick Buster (see below) that I came back to Pong Clone 3000 and rewrote the entire codebase. I then added in a single player option with basic AI: if the ball is moving left, the computer centres themselves on the y axis (within a tolerance so they actually stop instead of flitting around very close to the centre); if the ball is moving left, wait until it is over halfway across then align the centre of the bat with the centre of the ball. As the ball is set to speed up upon contact with each of the bats the computer struggles later on in rallies, but does very well at the start of a rally (when human error can lead to some interesting games)

Brick Buster
The project that lead to ImageCache, StateManager, High Scores and a massive learning curve. The ImageCache came from loading the same brick image multiple times and having to deal with erasing it when it was hit by the user. The StateManager was needed to juggle between the level, level complete, game over, title and high score states and ensure that the correct level was loaded. The high scores came from a desire to make the game more competitive and fun for the user. The levels are stored in .lvl files (.txt files renamed) as a colour and x and y coordinate (i.e. red 230 50). This made changing coordinates of bricks a lot easier – one change in the text file and the game can be run again with the change shown, no need to recompile again.

Bomber Run
Both the building’s height and colour are randomly generated at runtime, and the game uses the same StateManager class as Brick Buster to deal with the different levels. The ImageCache class had to be extended by one method for Bomber Run – a drawRectangle() method was added. To draw the buildings the rectangle is drawn to the screen, then starting from the top and working down, the building’s windows are drawn on top. A building over 15 storeys high takes two hits to destroy, knocking down half of it’s height in the first hit, every other building is destroyed in one hit. As the level gets higher, the plane speeds up quicker (0.05 * levelNumber each time it goes off the screen).

Please enjoy the games and let me know of any bugs that you find or improvements that you can recommend.

Read Full Post »

So we all play games to have fun and to enjoy a break from the tedium of real life, but there is a competitive edge within all of us that is brought about by gaming and more importantly, by that wretched device of measuring our performance – the high scores table. No game would be complete without some sort of scoring system – Pac Man had one and nowadays, you can’t move for AAA multiplayer titles that have a newer and better way to track scoring in the virtual worlds (most unfortunately falling by the wayside when everyone goes back to COD or BF3 within a month of their release). Still, there has to be some way of tracking scores and keeping track of a top 5 scores of all time that is remembered across playing experiences (i.e. if I was #1 yesterday, that score had better be there today when I open the game up or I’ll lose interest in the game very quickly!).

The easiest way that I could think to approach this was with a txt file that has the top 5 scores in it, and is read from or written to at the appropriate times.

Writing out to the file is done in the GameOver constructor, through a rwHighScore() method (rw for read/write)

void GameOver::rwHighScore() {
    //open an ifstream on high scores.txt to read in the file's contents
    std::ifstream inf("high scores.txt");

    if (!inf)
        throw("Could not open high scores.txt");

    //create a vector with the player's score initialized in it, using an initializer list from C++11
    std::vector<int> hscores{player.getScore()};

    //read in scores until the end of the file is reached
    while(1) {
        int hs;
        inf >> hs;
        if( inf.eof() )
            break;
        hscores.push_back(hs);
    }

    //open an ofstream on the file to write the top 5 scores out
    std::ofstream outf("high scores.txt");         

    if (!outf)
        throw("Could not open high scores.txt");

    //sort the vector using a greater than lambda function
    std::sort(hscores.begin(), hscores.end(), [](int a, int b){ return a > b; });

    //write the first five elements to the file
    for(auto it = hscores.begin(); it < hscores.begin()+5; ++it) {
        outf << *it << std::endl;
    }

    outf.close();
    inf.close();
}

So the player’s score is added to a vector, then the 5 high scores from the file are read in and added to the vector. The vector is sorted from biggest to smallest, then the first 5 elements only are written back out to the file.

In the HighScore state, scores are read in in the constructor using the following method:

void HighScore::readScoresIn() {
    std::fstream inf("high scores.txt");

    if(!inf)
        throw("Can't open high scores.txt");

    //position the fstream at the start of the file
    inf.seekg(0, std::ios::beg);
    
    //read in the first 5 elements
    for(int i=0; i<5; ++i)
        inf >> hs[i];

    //sort the elements from biggest to smallest using a lambda function
    std::sort(hs, hs + 5,  [](int a, int b){ return a > b; });

    inf.close();
}

Again, nice and simple. It can be taken from one project to another and just works, providing something for the player to aim for during their gaming adventures.

Read Full Post »

Every object that is drawn to the screen in XNA is called a Sprite. In C++ there is no such object, so I thought that to make things easier (and always remembering the DRY principle of programming) I would make a base class called Sprite that could be inherited by any object that would be drawn to the screen in my game. I found that every object that was being drawn to the screen was requiring a getPosition()/getImage() method and the majority of them needed a getDirection() method as well, so to reduce the repetition, these were the first on my hitlist for the Sprite class

To be drawn to the screen, an object obviously needs an image and a position to draw. As already discussed, the actual images and their SDL_Surface*’s are controlled and organised by ImageCache, so what the Sprite class actually looks after is the location of the image file for each object. A lot of images in games tend to be moving as well, so every Sprite has a direction vector as well. All the Sprite class provides is the ability to get these variables through virtual getter functions.

class Sprite {
    protected:
        Point2D position = {0,0};
        Vector2D direction = {0,0};
        std::string image = "";

    public:
        Sprite(const std::string imgFile) : image(imgFile) {}
        virtual ~Sprite() {};

        virtual const Point2D getPosition() const { return position; }
        virtual const Vector2D getDirection() const { return direction; }
        virtual const std::string getImage() const { return image; }
};

The real flexibility of the Sprite class comes when rendering the images to screen. Instead of having a draw method in each state that is littered with lots of repeat calls to imgCache.render(x->getImage(), x->getPosition()), we can hide that call away in a templated function and pass in each Sprite to be drawn

//in the level.h file
template
void drawSprite(T& sprite) const {
    imgCache->render(sprite->getImage(), sprite->getPosition());
}

//in the level.cpp draw() method
drawSprite(plane);
drawSprite(bomb);
//etc

Much nicer, easier to read and less repetitive typing. I’ve even got a template function for drawing a container of sprites (i.e. a vector of bricks or aliens)

//in level.h
template
void drawSpriteCntr(T& cntr) const {
    for(const auto& x : cntr)
        imgCache->render(x->getImage(), x->getPosition());
}

//in level.cpp draw() method
drawSpriteCntr(*alienFleet);
drawSpriteCntr(alienBullets);
//etc etc

Again, this just helps to save on a few lines, but it makes the code so much more elegant.

Read Full Post »

Older Posts »