How to Make a Space Invaders Clone with SFML - Part 2

Okay - part 2 of the series. In this installment, I'll demonstrate how to make a simple intro/splash screen for your game. Because we love SFML and THOR so much, our intro will be a plug for the fine folks who made this game possible.

As discussed in the previous part if this series, we have a game object that consists of static methods that will themselves instantiate state objects. Our first object will be defined in <intro.hpp>:



#ifndef INTRO_HPP
#define INTRO_HPP

#include <SFML/Graphics.hpp>
#include <iostream>

namespace nasic
{
    class intro
    {
        public:
            intro();
            ~intro();
            void show(sf::RenderWindow& window);
            const sf::Uint32 introState() const {return m_introstate;};

            enum introstate
            {
                s_uninitialized,
                s_playing
            };

        private:
            static sf::Uint32 m_introstate;
    };
}

#endif // INTRO_HPP

This is a very simple interface - again all wrapped in our nasic namespace. It consists of only two public methods and a static data member to keep track of the state internally. Here is the implementation:



#include <intro.hpp>

nasic::intro::intro()
{

}

nasic::intro::~intro()
{

}

void nasic::intro::show(sf::RenderWindow& window)
{
    using nasic::intro;

    if(!m_introstate == introstate::s_uninitialized)
        return;

    m_introstate = introstate::s_playing;

    sf::Clock clock;

    //create the intro image
    sf::Texture tex;
    if(!tex.loadFromFile("img/intro.png"))
    {
        std::cerr<<"Could not load image."<<std::endl;
    }

    sf::Sprite sprite;
    sprite.setTexture(tex);
    sprite.setOrigin(sprite.getGlobalBounds().width/2.f, sprite.getGlobalBounds().height/4.f);
    sprite.setPosition(window.getSize().x/2.f, window.getSize().y/2.f);
    sprite.setTextureRect(sf::IntRect(0,0,800,230));

    while(m_introstate == introstate::s_playing
          && clock.getElapsedTime().asSeconds() < 5.f)
    {
        window.clear();

        if(clock.getElapsedTime().asSeconds() > 3.f)
        {
            sprite.setTextureRect(sf::IntRect(0,230,800,230));
        }

        window.draw(sprite);

        window.display();
    }
    return;
}

//instantiate static members
sf::Uint32 nasic::intro::m_introstate = uninitialized;

Basically, there is only one thing going on here. We create a sprite that shows a portion of a texture based on the amount of time that has elapsed since the creation of the "intro" object. In this case, our shameless plug will last 5 seconds. The "intro.png" will be distributed with the final package, so fear not - I won't leave you hanging or make you do horrible exercizes in graphic design. Unless of course you like to, in which case by all means go right ahead. Finally, don't forget to add the header to the game object definition (game.hpp) and update the nasic::Game::intro() method to instantiate our intro object and play it back.


...
void nasic::Game::intro()
{
    using nasic::Game;
    m_window.setTitle("Intro");
    std::cout<<"Intro State"<<std::endl;

    //create an intro object
    nasic::intro gameIntro;
    gameIntro.show(m_window);
}
...

Also, recall that our game loop transitions straight from the intro to the menu. Because we can get the state of our intro through nasic::Intro::introState(), we are not limited to this flow. However, most games transition in a manner similar to what we are going with here. I'll leave it as an exercise for you to handle any alternative scenarios, such as exiting the application from the intro state, should you choose to support this action. All you would need to do is snap in another state to the "introstate" enum and handle it through the interface provided (nasic::Intro::introState()) in the game class. In fact, in the next part we will this very thing for the menu class. In this case, I don't think a 5 second delay will be detrimental to our players' enjoyment of the game.


...
void nasic::Game::run()
{
    switch(m_state)
    {
    case state::s_intro:
    {
        intro();
        m_state = state::s_menu;
    }
    break;
...

That about does it for part two of the series. Stay tuned for the next part in the series where we set up a main menu complete with support for keyboard and mouse events, as well as some eye candy.

Go to Part 3