0
\$\begingroup\$

So, I've been working on a game dev library using C++ and SDL2 (to test the library out I'm making a Pong clone). And now I'm writing a Scene Manager which is going good, I can switch between scenes no problem but when I first run the game and switch from main menu scene to game scene, the latter is already processing all events (collisions, movement of the ball, scoring, etc.).

For example, I'd sit in the main menu for a couple seconds then switch to the game and the ball is in the bottom right corner of the screen (it should be in the center at the beginning) and the score is 2-1 or something.

I thought this had something to do with how I store my scenes so I switched from an unordered_map to a vector but that didn't fix it. I honestly don't know what to do.

Below is the code to the scene manager, scenes and the main file.

Scene Manager header:

#ifndef CC_SCENEMANAGER
#define CC_SCENEMANAGER

#include "CCScene.hpp"

#include <cstdlib>
#include <unordered_map>

namespace cc
{
    class SceneManager
    {
        public:
            ~SceneManager();

            void Add(Scene *scene);
            void Run();

            void GetScene(int id);
        private:
            void GetNextSceneID();

            std::unordered_map<unsigned int, Scene *> mScenes;

            Scene *mCurrentScene = nullptr;

            int mSceneIDs = 0;
            int mActiveSceneID = 0;
            int mNextSceneID = 0;
    };
}

#endif // CC_SCENEMANAGER

Scene Manager source:

#include "CCSceneManager.hpp"
#include "CCKeyboard.hpp"
#include "CCLog.hpp"
#include "CCScene.hpp"
#include "CCWindow.hpp"
#include <SDL2/SDL.h>
#include <SDL2/SDL_render.h>

namespace cc
{
    SceneManager::~SceneManager()
    {
        for (int i = 0; i < mSceneIDs; i++)
        { mScenes[i]->OnDestroy(); }
    }

    void SceneManager::Add(Scene *scene)
    {
        mScenes[mSceneIDs] = scene;
        if (scene->mSceneID == 0)
        {
            scene->mSceneID = mSceneIDs;
            scene->SetNextSceneID();
            scene->OnCreate();
            LOG("sceneid, nextsceneid = ", scene->mSceneID, ", ", scene->mNextSceneID);
        }

        mSceneIDs++;
    }

    void SceneManager::Run()
    {
        bool running = true;

        while (running)
        {
            GetNextSceneID();

            if (mNextSceneID != -1)
            {
                if (mActiveSceneID != mNextSceneID)
                {
                    mActiveSceneID = mNextSceneID;
                    mCurrentScene->SetNextSceneID();
                }

                GetScene(mActiveSceneID);
                if (!mCurrentScene->mStarted)
                {
                    mCurrentScene->Start();
                    mCurrentScene->mStarted = true;
                    mCurrentScene->mQuitting = false;
                }
                else
                {
                    mCurrentScene->HandleInput();

                    mCurrentScene->keyStates = Keyboard::GetKeyboardState();
                    if (!mCurrentScene->handleKeyStatesAlone)
                    { mCurrentScene->HandleKeyStates(); }

                    mCurrentScene->Update();

                    SDL_RenderClear(Window::GetRenderer());

                    mCurrentScene->Render();

                    SDL_RenderPresent(Window::GetRenderer());
                }

                if (mCurrentScene->mQuitting)
                {
                    mCurrentScene->mStarted = false;
                    mCurrentScene->Exit();
                }
            }
            else
            { running = false; }
        }
    }

    void SceneManager::GetScene(int id)
    {
        LOG("id = ", id);
        auto scene = mScenes.find(id);
        if (scene == nullptr)
        {
            ERROR("There is no scene at ID: ", id);
            exit(-1);
        }
        else
        { mCurrentScene = scene->second; }
    }

    void SceneManager::GetNextSceneID()
    {
        if (mCurrentScene != nullptr)
        { mNextSceneID = mCurrentScene->mNextSceneID; }
    }
}

Scene header:

#ifndef CC_GAME
#define CC_GAME

#include <cstdint>

namespace cc
{
    class Scene
    {
        public:
            virtual void OnCreate() { }
            virtual void OnDestroy() { }
            virtual void Start() = 0;
            virtual void Exit() = 0;
            virtual void HandleInput() = 0;
            virtual void HandleKeyStates() = 0;
            virtual void Update() = 0;
            virtual void Render() = 0;

            void SwitchScenes(int id);
            void Quit();

            const uint8_t *keyStates = nullptr;

            bool handleKeyStatesAlone = false;
        private:
            friend class SceneManager;

            void SetNextSceneID();

            bool mStarted = false;
            bool mQuitting = false;

            int mSceneID = 0;
            int mNextSceneID = 0;
    };
}

#endif // CC_GAME

Scene source:

#include "CCScene.hpp"

namespace cc
{
    void Scene::SwitchScenes(int id)
    {
        mNextSceneID = id;
        mQuitting = true;
    }

    void Scene::Quit()
    { SwitchScenes(-1); }

    void Scene::SetNextSceneID()
    { mNextSceneID = mSceneID; }
}

main.cpp:

#include "MainMenu.cpp"
#include "Game.cpp"
#include "Globals.cpp"

int main()
{
    cc::Window *window = cc::Window::GetInstance();
    window->Create("Pong", SCREEN_WIDTH, SCREEN_HEIGHT);

    cc::SceneManager sm;

    MainMenuScene mainMenu;
    sm.Add(&mainMenu);

    GameScene game;
    sm.Add(&game);

    sm.Run();

    return 0;
}

Here's also the code for the individual scenes (main menu, game) if it's relevant:

Main Menu:

#ifndef MAIN_MENU
#define MAIN_MENU

#include "Globals.cpp"

struct MainMenuScene : public cc::Scene
{
    cc::Text mainMenuText;

    Gamevars *gv= Gamevars::GetInstance();

    cc::Window *window = cc::Window::GetInstance();

    void OnCreate() override
    {
        gv->fontSmall.Create("./resources/fonts/pixel-coleco-font/PixelColeco.ttf", 36);
        gv->fontBig.Create("./resources/fonts/pixel-coleco-font/PixelColeco.ttf", 72);

        gv->DebugCreate();
    }

    void Start() override
    { mainMenuText.Create(window->GetRenderer(), "PONG", gv->fontBig.font); }

    void Exit() override
    { mainMenuText.Destroy(); }

    void OnDestroy() override
    {
        gv->fontSmall.Destroy();
        gv->fontBig.Destroy();
    }

    void HandleInput() override
    {
        while (cc::Event::PollEvent(&gv->e))
        {
            if (gv->e.type == cc::Event::CC_QUIT)
            { Quit(); }
            if (gv->e.type == cc::Event::CC_KEYDOWN)
            {
                switch (gv->e.key.keysym.sym)
                {
                    case cc::Keyboard::CCK_ESCAPE:
                        Quit();
                        break;

                    case cc::Keyboard::CCK_RETURN:
                        SwitchScenes(1);
                        break;

                    case cc::Keyboard::CCK_F:
                        window->SetFullscreenOrWindowed();
                        break;

                    case cc::Keyboard::CCK_F3:
                        gv->showDebug = !gv->showDebug;
                        break;

                    case cc::Keyboard::CCK_V:
                        window->EnableVSync();
                        break;

                    default:
                        break;
                }
            }
        }
    }

    void HandleKeyStates() override {}

    void Update() override
    {
        //gv->gTime.UpdateClock();

        gv->DebugUpdate();
    }

    void Render() override
    {
        mainMenuText.Render();

        gv->DebugRender();
    }
};

#endif // MAIN_MENU

Game:

#ifndef GAME
#define GAME

#include "Globals.cpp"

#include <algorithm>
#include <cmath>
#include <string>

struct Ball
{
    cc::Texture texture;

    int posX = 0;
    int posY = 0;

    float baseSpeed = 500.0f;

    float velX = baseSpeed;
    float velY = baseSpeed;

    float centerX = 0.0f;
    float centerY = 0.0f;

    bool moveVertically = false;
};

struct Player
{
    cc::Texture texture;

    int posX = 0;
    int posY = 0;

    int score = 0;

    float velocity = 0.0f;
};

struct Enemy
{
    cc::Texture texture;

    int posX = 0;
    int posY = 0;

    int score = 0;

    float velocity = 0.0f;
};

class GameScene : public cc::Scene {
    Gamevars *gv= Gamevars::GetInstance();

    cc::Text playerScoreText;
    int pTextPos = SCREEN_WIDTH / 4;

    cc::Text enemyScoreText;
    int eTextPos = SCREEN_WIDTH / 4 + SCREEN_WIDTH / 2;

    cc::Text winText;
    int wTextPosX = 0;
    int wTextPosY = 0;

    cc::Text restartText;
    int rTextPosX = 0;
    int rTextPosY = SCREEN_HEIGHT / 4 + SCREEN_HEIGHT / 2;

    Ball ball;
    Player player;
    Enemy enemy;

    float minForceFactor = 0.5f;
    float maxForceFactor = 1.0f;
    float speedMultiplier = 3.5f;

    bool playerServing = false;
    bool gameOver = false;

    int maxScore = 11;
    int consecutiveServes = 1;

    cc::Window *window = cc::Window::GetInstance();

    void HandleBallCollisions(Ball &ball, Player &player, Enemy &enemy)
    {
        cc::Texture paddle;

        ball.centerX = ball.texture.textureQuad.x + ball.texture.textureQuad.w;
        ball.centerY = ball.texture.textureQuad.y + ball.texture.textureQuad.h;

        bool collisionPaddle = (ball.centerX < (SCREEN_WIDTH / 2.0f));

        paddle.textureQuad = (collisionPaddle ? player.texture.textureQuad : enemy.texture.textureQuad);

        if (ball.centerX >= paddle.textureQuad.x &&
            ball.centerX <= paddle.textureQuad.x + paddle.textureQuad.w &&
            ball.centerY >= paddle.textureQuad.y &&
            ball.centerY <= paddle.textureQuad.y + paddle.textureQuad.h)
        {
            ball.moveVertically = true;

            float relativeY = ball.centerY - (paddle.textureQuad.y + paddle.textureQuad.h / 2.0f);

            float forceFactor = std::lerp(minForceFactor, maxForceFactor,
                                          (relativeY + paddle.textureQuad.h / 2.0f) / paddle.textureQuad.h);
            float speedFactor = std::abs((collisionPaddle ? player.velocity : enemy.velocity) * gv->timeStep);

            forceFactor *= speedFactor;

            ball.velX *= -1.0f;

            if (collisionPaddle)
            {
                if (ball.velY == 0.0f)
                { ball.velY = -ball.baseSpeed; }
                else if (ball.velY != 0.0f && std::abs(player.velocity) > 0.01f)
                { ball.velY = std::clamp(player.velocity * gv->timeStep, -ball.baseSpeed, ball.baseSpeed) * forceFactor * speedMultiplier; }
            }
            else
            {
                if (ball.velY == 0.0f)
                { ball.velY = -ball.baseSpeed; }
                else if (ball.velY != 0.0f && std::abs(enemy.velocity) > 0.01f)
                { ball.velY = std::clamp(enemy.velocity * gv->timeStep, -ball.baseSpeed, ball.baseSpeed) * forceFactor * speedMultiplier; }
            }
        }

        if (ball.texture.textureQuad.y + ball.texture.textureQuad.h <= 0.0f)
        { ball.velY = -ball.velY; }
        else if (ball.texture.textureQuad.y + ball.texture.textureQuad.h >= SCREEN_HEIGHT)
        { ball.velY = -ball.velY; }

        if (ball.texture.textureQuad.x + ball.texture.textureQuad.w <= 0.0f)
        {
            enemy.score++;

            enemyScoreText.Update(std::to_string(enemy.score).c_str(), gv->fontBig.font, { 255, 255, 255 }, eTextPos, 0);

            ball.texture.SetPosition(ball.posX, ball.posY);
            ball.moveVertically = false;

            HandleServing(ball, player, enemy);
        }
        else if (ball.texture.textureQuad.x + ball.texture.textureQuad.w >= SCREEN_WIDTH)
        {
            player.score++;

            playerScoreText.Update(std::to_string(player.score).c_str(), gv->fontBig.font, { 255, 255, 255 }, pTextPos, 0);

            ball.texture.SetPosition(ball.posX, ball.posY);
            ball.moveVertically = false;

            HandleServing(ball, player, enemy);
        }

        LOG("This isn't supposed to be printed.");

        ball.texture.textureQuad.x += ball.velX * gv->timeStep;
        ball.texture.textureQuad.y += (ball.moveVertically ? ball.velY : 0.0f) * gv->timeStep;
    }

    void HandleServing(Ball &ball, Player &player, Enemy &enemy)
    {
        if (consecutiveServes == 2 && player.score + enemy.score < 20)
        {
            consecutiveServes = 1;
            playerServing = !playerServing;
        }
        else if (consecutiveServes >= 0 && player.score + enemy.score >= 20)
        {
            consecutiveServes = 0;
            playerServing = !playerServing;
        }
        else
        { consecutiveServes++; }

        ball.velX = playerServing ? -ball.baseSpeed : ball.baseSpeed;
    }

    void HandleScore(Ball &ball, Player &player, Enemy &enemy)
    {
        if (std::abs(player.score - enemy.score) >= 2)
        {
            if (player.score >= maxScore && player.score > enemy.score)
            {
                gameOver = true;

                winText.Update("PLAYER 1 WINS!", gv->fontBig.font, { 255, 255, 255 }, wTextPosX, wTextPosY);
                wTextPosX = SCREEN_WIDTH / 2 - winText.TEXT_CENTER_W;
                wTextPosY = SCREEN_HEIGHT / 2 - winText.TEXT_CENTER_H;
                winText.SetX(wTextPosX);
                winText.SetY(wTextPosY);

                restartText.Update("PRESS R TO RESTART", gv->fontSmall.font, { 255, 255, 255 }, rTextPosX, rTextPosY);
                rTextPosX = SCREEN_WIDTH / 2 - restartText.TEXT_CENTER_W;
                restartText.SetX(rTextPosX);
                restartText.SetY(rTextPosY);

                ball.velX = 0.0f;
                gv->speed = 0.0f;

                ball.texture.SetAlpha(0);
                player.texture.SetAlpha(0);
                enemy.texture.SetAlpha(0);

                player.texture.SetPosition(player.posX, player.posY);
                enemy.texture.SetPosition(enemy.posX, enemy.posY);

                playerScoreText.Update("", gv->fontBig.font, { 0, 0, 0 }, -100, -100);
                enemyScoreText.Update("", gv->fontBig.font, { 0, 0, 0 }, -100, -100);
            }
            else if (enemy.score >= maxScore && enemy.score > player.score)
            {
                gameOver = true;

                winText.Update("PLAYER 2 WINS!", gv->fontBig.font, { 255, 255, 255 }, wTextPosX, wTextPosY);
                wTextPosX = SCREEN_WIDTH / 2 - winText.TEXT_CENTER_W;
                wTextPosY = SCREEN_HEIGHT / 2 - winText.TEXT_CENTER_H;
                winText.SetX(wTextPosX);
                winText.SetY(wTextPosY);

                restartText.Update("PRESS R TO RESTART", gv->fontSmall.font, { 255, 255, 255 }, rTextPosX, rTextPosY);
                rTextPosX = SCREEN_WIDTH / 2 - restartText.TEXT_CENTER_W;
                restartText.SetX(rTextPosX);
                restartText.SetY(rTextPosY);

                ball.velX = 0.0f;
                gv->speed = 0.0f;

                ball.texture.SetAlpha(0);
                player.texture.SetAlpha(0);
                enemy.texture.SetAlpha(0);

                player.texture.SetPosition(player.posX, player.posY);
                enemy.texture.SetPosition(enemy.posX, enemy.posY);

                playerScoreText.Update("", gv->fontBig.font, { 0, 0, 0 }, -100, -100);
                enemyScoreText.Update("", gv->fontBig.font, { 0, 0, 0 }, -100, -100);
            }
        }
    }

    void Start() override
    {
        ball.texture.Create(window->GetRenderer(), "./resources/textures/ball.png");
        ball.posX = SCREEN_WIDTH / 2 - ball.texture.textureCenterX;
        ball.posY = SCREEN_HEIGHT / 2 - ball.texture.textureCenterY;
        ball.texture.SetPosition(ball.posX, ball.posY);
        ball.texture.SetColor({ 255, 0, 0 });

        player.texture.Create(window->GetRenderer(), "./resources/textures/paddle.png");
        player.posX = 0;
        player.posY = SCREEN_HEIGHT / 2 - player.texture.textureCenterY;
        player.texture.SetPosition(player.posX, player.posY);

        enemy.texture.Create(window->GetRenderer(), "./resources/textures/paddle.png");
        enemy.posX = SCREEN_WIDTH - enemy.texture.textureQuad.w;
        enemy.posY = SCREEN_HEIGHT / 2 - enemy.texture.textureCenterY;
        enemy.texture.SetPosition(enemy.posX, enemy.posY);

        playerScoreText.Create(window->GetRenderer(), std::to_string(player.score).c_str(), gv->fontBig.font, { 255, 255, 255 }, pTextPos, 0, playerScoreText.CENTER);
        enemyScoreText.Create(window->GetRenderer(), std::to_string(enemy.score).c_str(), gv->fontBig.font, { 255, 255, 255 }, eTextPos, 0, playerScoreText.CENTER);

        winText.Create(window->GetRenderer(), "WINS!", gv->fontBig.font);

        restartText.Create(window->GetRenderer(), "PRESS R TO RESTART", gv->fontSmall.font);

        handleKeyStatesAlone = true;
    }

    void Exit() override
    {
        ball.texture.Destroy();
        player.texture.Destroy();
        enemy.texture.Destroy();

        playerScoreText.Destroy();
        enemyScoreText.Destroy();

        winText.Destroy();

        restartText.Destroy();
    }

    void HandleInput() override
    {
        while (cc::Event::PollEvent(&gv->e))
        {
            if (gv->e.type == cc::Event::CC_QUIT)
            { Quit(); }
            if (gv->e.type == cc::Event::CC_KEYDOWN)
            {
                switch (gv->e.key.keysym.sym)
                {
                    case cc::Keyboard::CCK_ESCAPE:
                        SwitchScenes(0);
                        break;

                    case cc::Keyboard::CCK_F:
                        window->SetFullscreenOrWindowed();
                        break;

                    case cc::Keyboard::CCK_F3:
                        gv->showDebug = !gv->showDebug;
                        break;

                    case cc::Keyboard::CCK_V:
                        window->EnableVSync();
                        break;

                    case cc::Keyboard::CCK_R:
                        player.score = 0;
                        enemy.score = 0;

                        ball.velX = ball.baseSpeed;
                        gv->speed = 800.0f;

                        playerScoreText.Update(std::to_string(player.score).c_str(), gv->fontBig.font, { 255, 255, 255 }, pTextPos, 0);
                        enemyScoreText.Update(std::to_string(enemy.score).c_str(), gv->fontBig.font, { 255, 255, 255 }, eTextPos, 0);

                        ball.texture.SetAlpha(255);
                        player.texture.SetAlpha(255);
                        enemy.texture.SetAlpha(255);

                        gameOver = false;
                        break;

                    default:
                        break;
                }
            }
        }
    }

    void HandleKeyStates() override
    {
        if (keyStates[cc::Scancode::CCS_W] && !keyStates[cc::Scancode::CCS_S])
        {
            if (player.texture.textureQuad.y <= 0)
            { player.texture.textureQuad.y = 0; }
            else
            { player.velocity = -gv->speed; }
        }
        if (keyStates[cc::Scancode::CCS_S] && !keyStates[cc::Scancode::CCS_W])
        {
            if (player.texture.textureQuad.y >= SCREEN_HEIGHT - player.texture.textureQuad.h)
            { player.texture.textureQuad.y = SCREEN_HEIGHT - player.texture.textureQuad.h; }
            else
            { player.velocity = gv->speed; }
        }
        if (keyStates[cc::Scancode::CCS_UP] && !keyStates[cc::Scancode::CCS_DOWN])
        {
            if (enemy.texture.textureQuad.y <= 0)
            { enemy.texture.textureQuad.y = 0; }
            else
            { enemy.velocity = -gv->speed; }
        }
        if (keyStates[cc::Scancode::CCS_DOWN] && !keyStates[cc::Scancode::CCS_UP])
        {
            if (enemy.texture.textureQuad.y >= SCREEN_HEIGHT - enemy.texture.textureQuad.h)
            { enemy.texture.textureQuad.y = SCREEN_HEIGHT - enemy.texture.textureQuad.h; }
            else
            { enemy.velocity = gv->speed; }
        }
    }

    void Update() override
    {
        gv->gTime.UpdateClock();

        gv->accumulatedTime += gv->gTime.GetDeltaTime();

        while (gv->accumulatedTime >= gv->timeStep)
        {
            player.velocity = 0.0f;
            enemy.velocity = 0.0f;

            HandleKeyStates();

            player.texture.textureQuad.y += player.velocity * gv->timeStep;
            enemy.texture.textureQuad.y += enemy.velocity * gv->timeStep;

            HandleBallCollisions(ball, player, enemy);

            HandleScore(ball, player, enemy);

            gv->accumulatedTime -= gv->timeStep;
        }

        gv->DebugUpdate();
    }

    void Render() override
    {
        ball.texture.Render();

        player.texture.Render();
        enemy.texture.Render();

        playerScoreText.Render();
        enemyScoreText.Render();

        if (gameOver)
        {
            winText.Render();
            restartText.Render();
        }

        gv->DebugRender();
    }
};

#endif // GAME

Any help (even a point in the right direction to solving this problem) is greatly appreciated.

\$\endgroup\$

3 Answers 3

0
\$\begingroup\$

I think the simplest solution is to do what other engines (such as Godot) do.

A scene is like any other object in the game, specifically.

  • It has a definition (In OOP terms that would be the class).
  • It can be created/destroyed (new/free an instance of the class).
  • Multiple instances can be created and placed into the active tree.
  • Typically one instance (of one scene) is the root scene.
  • The "Scene Manager" is only really managing the root scene.

So when you switch to a scene the scene manager instances a new scene and set it as the root.

When you change scene, the scene manager frees/deletes that instance. Creates a new scene (typically of a different class) and sets the new scene as the root. ... repeat ...

TL;DR the old scene is still "active" because you are not freeing off all its (instance level) resources, when you switch to a different scene.

--

In your case the scene manager is taking instances of the scenes - to make things work as I have described, the scene manager would need to be refactored to manage factories to create the scenes - instead of the scene instances.

\$\endgroup\$
0
\$\begingroup\$

This isn't a design problem, it's an implementation problem.

You solve it by simply pausing your timer. Instead of accumulating a delta time each frame, just accumulate 0.

\$\endgroup\$
0
\$\begingroup\$

Okay, first of all thank you to @DavidT and @Maximus Minimus for their answers, I appreciate it a lot.

Now, it was a design problem and not an implementation problem like Maximus suggested. But I still appreciate the answer (it made sense at the beginning and didn't hurt to try the solution). With that out of the way, onto my solution.

Inspite of all the answers, I got it on my own and used a lambda to cache(?) my scene objects in the mScenes map:

mScenes[name] = [name]() { return new TScene(name); };

I also needed to change the map a bit:

std::unordered_map<std::string, std::function<Scene *(void)>> mScenes;

And it solved the issue, Scene objects are not created as soon as they're added to the map which doesn't process anything from the GameScene for example until I switch to it.

So, yeah. Once again, thank you for the answers, now I'm gonna post the SceneManager and Scene code if anybody would like to see all of the changes :)

Scene Manager header:

#ifndef CC_SCENEMANAGER
#define CC_SCENEMANAGER

#include "CCScene.hpp"
#include "CCLog.hpp"
#include "CCWindow.hpp"

#include <cstring>
#include <functional>
#include <string>
#include <unordered_map>
#include <cstring>

namespace cc
{
    extern const std::string &_end_cc;

    class SceneManager
    {
        public:
            ~SceneManager();

            template <typename TScene>
            void Add(const std::string &name)
            {
                if (name == _end_cc)
                {
                    ERROR("Scene cannot have this name. It has been reserved internally for exiting the game. Please choose another name.");
                    cc::Window::GetInstance()->Destroy();
                }
                else
                {
                    if (mScenes.find(name) != mScenes.end())
                    { WARNING("Scene with name: ", name, " already exists. Overwriting..."); }
                    mScenes[name] = [name]() { return new TScene(name); };
                }
            }

            void SwitchScene(const std::string &name);
            void Run();

            static SceneManager *GetInstance()
            {
                static SceneManager instance;
                return &instance;
            }
        private:
            friend class Scene;

            std::unordered_map<std::string, std::function<Scene *(void)>> mScenes;

            Scene *mCurrentScene = nullptr;
    };
}

#endif // CC_SCENEMANAGER

Scene Manager source:

#include <SDL2/SDL_render.h>

#include <string>

namespace cc
{
    const std::string &_end_cc = "_end_cc";

    SceneManager::~SceneManager()
    {
        for (auto &scene : mScenes)
        { scene.second()->OnDestroy(); }
    }

    void SceneManager::SwitchScene(const std::string &name)
    {
        if (mCurrentScene->mSceneName != name)
        {
            auto scene = mScenes.find(name);
            if (scene == nullptr)
            {
                ERROR("Scene with id: ", name, " does not exist!");
                exit(-1);
            }
            else
            {
                mCurrentScene = scene->second();
                mCurrentScene->OnCreate();
            }
        }
    }

    void SceneManager::Run()
    {
        bool running = true;

        for (const auto &[key, value] : mScenes)
        { LOG(key, ": "); }

        mCurrentScene = mScenes.begin()->second();
        mCurrentScene->OnCreate();

        while (running)
        {
            if (mCurrentScene->mSceneName != _end_cc)
            {
                if (mCurrentScene != nullptr)
                {
                    if (!mCurrentScene->started)
                    {
                        mCurrentScene->OnStart();
                        mCurrentScene->started = true;
                        mCurrentScene->mQuitting = false;
                    }

                    mCurrentScene->OnHandleInput();

                    mCurrentScene->keyStates = cc::Keyboard::GetKeyboardState();
                    if (!mCurrentScene->handleKeyStatesAlone)
                    { mCurrentScene->OnHandleKeyStates(); }

                    mCurrentScene->OnUpdate();

                    SDL_RenderClear(cc::Window::GetInstance()->GetRenderer());

                    mCurrentScene->OnRender();

                    SDL_RenderPresent(cc::Window::GetInstance()->GetRenderer());

                    if (mCurrentScene->mQuitting)
                    {
                        mCurrentScene->OnExit();
                        mCurrentScene->started = false;
                        mCurrentScene->mQuitting = true;
                    }
                }
            }
            else
            { running = false; }
        }
    }
}

Scene header:

#ifndef CC_SCENE
#define CC_SCENE

#include <cstdint>
#include <string>

namespace cc
{
    class Scene
    {
        public:
            Scene(const std::string &name) : mSceneName(name) { }

            virtual void OnCreate() { }
            virtual void OnDestroy() { }
            virtual void OnStart() = 0;
            virtual void OnExit() = 0;
            virtual void OnHandleInput() = 0;
            virtual void OnHandleKeyStates() = 0;
            virtual void OnUpdate() = 0;
            virtual void OnRender() = 0;

            void SwitchScene(const std::string &name);
            void Quit();

            const uint8_t *keyStates = nullptr;

            bool handleKeyStatesAlone = false;
            bool started = false;
        private:
            friend class SceneManager;

            bool mQuitting = false;
        protected:
            std::string mSceneName = "";
    };
}

#endif // CC_SCENE

Scene source:

#include "CCScene.hpp"
#include "CCSceneManager.hpp"

namespace cc
{
    void Scene::SwitchScene(const std::string &name)
    {
        mQuitting = true;
        cc::SceneManager::GetInstance()->SwitchScene(name);
    }

    void Scene::Quit()
    { mSceneName = _end_cc; }
}
```
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .