How to stop scenes from running before switching between them?
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.