1

I'm working on a new project and an implementing a basic scene change. I have the different scenes setup as their own classes, with the intialisation function being used to create and reposition different SFML objects. I saw this answer and have written my scene switcher similarly:

// Create scene monitoring variable
int scene[2];
scene[0] = 0; // Set current scene to menu
scene[1] = 0; // Set scene change to no

...

// Check for scene change
if(scene[1] == 0) {
    // Run tick function based on current scene
    switch(scene[0]) {
        case 0:
            // Main menu - run tick function
            menu.tick();
    }
}
if(scene[1] == 1) {
    // Reset scene that you've changed to
    switch(scene[0]) {
        case 0:
            // Main menu - reset it
            menu = Menu(window, scene);    // <-- Reinitialise menu here
    }
    // Set change variable to 0
    scene[1] = 0;
}

You can see the full code on the github repository.

However, this doesn't seem to work properly - as soon as a scene change is made, the screen goes blank. The class is reintialised (I added a cout to check), the draw function is still run and mouse clicks are still processed, yet nothing appears in the window.

Am I doing something wrong here?

  • Do you have any error message or just a blank window? When I run the code, I've got an exception trying to draw the new Menu object. It seems to lose the window reference or something... – alseether Jan 16 '18 at 08:41
  • No, I don't get any errors at all. The program continues to run perfectly (all cout's are output, variables changed etc. but nothing is displayed to the window. Do you get the error during buildtime or runtime? –  Jan 16 '18 at 21:27

1 Answers1

2

Doing things that way can lead into leak memory errors. I suggest you a different approach: the StateStack

How this works?

The basics of having a StateStack object is store each possible state of your game/app into a stack. This way, you can process each one in the stack order.

What is an State?

An State is something that can be updated, drawn and handle events. We can make an interface or an abstract class to make our screens behave like a State.

Which are the advantages?

With a stack structure, you can easily control how your different scenes are going to handle the three different processing methods. For instance. If you have a mouse click while you're in a pause menu, you won't that click event to reach the menu state or the "game" state. To achieve this, the solution is really easy, simply return false in your handleEvent method if you don't want the event go further this particular state. Note that this idea is also expandable to draw or update methods. In your pause menu, you won't update your "game" state. In your "game" state you won't draw tour menu state.

Example

With this points in mind, this is one possible way of implementation. First, the State interface:

class State{
public:
    virtual bool update() = 0;
    virtual bool draw(sf::RenderTarget& target) const = 0;

    // We will use a vector instead a stack because we can iterate vectors (for drawing, update, etc)
    virtual bool handleEvent(sf::Event e, std::vector<State*> &stack) = 0;
};

Following this interface we can have a example MenuState and PauseState:

MenuState

class MenuState : public State{
public:
    MenuState(){
        m_count = 0;

        m_font.loadFromFile("Roboto-Regular.ttf");
        m_text.setFont(m_font);
        m_text.setString("MenuState: " + std::to_string(m_count));
        m_text.setPosition(10, 10);
        m_text.setFillColor(sf::Color::White);

    }

    virtual bool update() {
        m_count++;
        m_text.setString("MenuState: " + std::to_string(m_count));
        return true;
    }

    virtual bool draw(sf::RenderTarget &target) const{
        target.draw(m_text);
        return true;
    }

    virtual bool handleEvent(sf::Event e, std::vector<State*> &stack){
        if (e.type == sf::Event::KeyPressed){
            if (e.key.code == sf::Keyboard::P){
                stack.push_back(new PauseState());
                return true;
            }
        }
        return true;
    }

private:
    sf::Font m_font;
    sf::Text m_text;
    unsigned int m_count;
};

PauseState

class PauseState : public State{
public:
    PauseState(){
        sf::Font f;
        m_font.loadFromFile("Roboto-Regular.ttf");
        m_text.setFont(m_font);
        m_text.setString("PauseState");
        m_text.setPosition(10, 10);
        m_text.setFillColor(sf::Color::White);

    }

    virtual bool update() {
        // By returning false, we prevent States UNDER Pause to update too
        return false;
    }

    virtual bool draw(sf::RenderTarget &target) const{
        target.draw(m_text);
        // By returning false, we prevent States UNDER Pause to draw too
        return false;
    }

    virtual bool handleEvent(sf::Event e, std::vector<State*> &stack){
        if (e.type == sf::Event::KeyPressed){
            if (e.key.code == sf::Keyboard::Escape){
                stack.pop_back();
                return true;
            }
        }
        return false;
    }

private:
    sf::Font m_font;
    sf::Text m_text;
};

By the way, while I was doing this, I notice that you must have the fonts as an attribute of the class in order to keep the reference. If not, when your text is drawn, its font is lost ant then it fails. Another way to face this is using a resource holder, which is much more efficient and robust.

Said this, our main will look like:

Main

int main() {
    // Create window object
    sf::RenderWindow window(sf::VideoMode(720, 720), "OpenTMS");

    // Set window frame rate
    window.setFramerateLimit(60);

    std::vector<State*> stack;

    // Create menu
    stack.push_back(new MenuState());

    // Main window loops
    while (window.isOpen()) {
        // Create events object
        sf::Event event;
        // Loop through events
        while (window.pollEvent(event)) {
            // Close window
            if (event.type == sf::Event::Closed) {
                window.close();
            }

            handleEventStack(event, stack);
        }

        updateStack(stack);
        // Clear window
        window.clear(sf::Color::Black);
        drawStack(window, stack);
        // Display window contents
        window.display();
    }

    return 0;
}

The stack functions are simple for-loop but, with the detail that iterate the vector backwards. This is the way to imitate that stack behavior, starting from top (size-1 index) and ending at 0.

Stack functions

void handleEventStack(sf::Event e, std::vector<State*> &stack){
    for (int i = stack.size()-1; i >=0; --i){
        if (!stack[i]->handleEvent(e, stack)){
            break;
        }
    }
}

void updateStack(std::vector<State*> &stack){
    for (int i = stack.size() - 1; i >= 0; --i){
        if (!stack[i]->update()){
            break;
        }
    }
}

void drawStack(sf::RenderTarget &target, std::vector<State*> &stack){
    for (int i = stack.size() - 1; i >= 0; --i){
        if (!stack[i]->draw(target)){
            break;
        }
    }
}

You can learn more about StateStacks and gamedev in general with this book

alseether
  • 1,889
  • 2
  • 24
  • 39
  • 1
    Wait, what? OP's code does not have dynamic allocation so it can't possibly leak, but yours has a naked `new` and no `delete` so it definitely does... – Quentin Jan 16 '18 at 09:53
  • @Quentin Busted!!! XD. Feel free to improve the code please, I've made it in about 30 mins... – alseether Jan 16 '18 at 09:54
  • Thanks for this. I will definately have to look into this. Is the memory error what was causing the blank screen? –  Jan 16 '18 at 21:04
  • It could be. If the `sf::Text` object *points* to a `sf::Font` which was constructed in the Constructor scope, when you try to draw it in the `draw` method, that font is lost. In my case I had an exception, but I'm not sure if it depends on the compiler/debugger (I'm on VS2013). The thing is, I'm not sure how it worked in first place. – alseether Jan 17 '18 at 07:17
  • I don't think so as the fonts are saved as one of the menus data members and so I don't think they should be destroyed once the setup function scope ends. I'm using GCC 7.2.1 on Linux to compile it all. –  Jan 17 '18 at 16:38