Press Space to continue not working.

Hi so my Game has a Menu with a Start Button that opens a Game Story first before it shows a text “Press Space to continue” to start the game. But its not working. I defined Esc Key to exit the program but it does not work during Start button clicked. [Preformatted text](https://we.tl/t-gWsRvJcELs)

#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <iostream>
#include <memory>
#include <vector>
#include "Game.h"
#include "Button.h"
#include "Story.h"
#include "StoryLines.h"
#include "Sprite.h"
#include "TextureManager.h"

// Define GameState enumeration
enum GameState {
    MENU,
    STORY,
    PLAYING,
    TUTORIAL,
    OPTIONS,
    ABOUT,
    EXIT,
    TUTORIAL_FIGHT_FAILURE
};

// Constants
const int FRAME_DELAY = 16; // Approximately 60 FPS
const int FONT_SIZE = 30;   // Increased font size
const int TEXT_WRAP_WIDTH = 600; // Width for text wrapping

// Constants for the volume bar
const int VOLUME_BAR_WIDTH = 300;       // Width of the volume bar
const int VOLUME_BAR_HEIGHT = 20;       // Height of the volume bar
const SDL_Color VOLUME_BAR_COLOR = { 0, 255, 0, 255 }; // Green color for the volume bar
const SDL_Color VOLUME_BAR_BACKGROUND_COLOR = { 100, 100, 100, 255 }; // Gray color for the background

// Declare global objects
TTF_Font* storylineFont = nullptr;
TTF_Font* defaultFont = nullptr;
SDL_Texture* menuBackdropTexture = nullptr;
SDL_Texture* speechBubbleTexture = nullptr;
Mix_Music* backgroundMusic = nullptr;
Mix_Music* combatMusic = nullptr;
static Story storyInstance(nullptr, nullptr, {}, 0); // Static Story instance
static Story tutorialStoryInstance(nullptr, nullptr, {}, 0); // Static Tutorial Story instance
SDL_Renderer* renderer = nullptr; // Ensure renderer is defined

// Function definitions should be placed after all includes and before main()
float time_(void) {
    static Uint64 start = 0;
    static float frequency = 0;
    if (start == 0) {
        start = SDL_GetPerformanceCounter();
        frequency = (float)SDL_GetPerformanceFrequency();
        return 0.0f;
    }
    Uint64 counter = SDL_GetPerformanceCounter();
    return ((float)(counter - start) / frequency);
}

// Helper function to render text
void renderText(SDL_Renderer* renderer, TTF_Font* font, const std::string& text, SDL_Color color, int x, int y, int wrapWidth = 0) {
    SDL_Surface* surface = wrapWidth > 0
        ? TTF_RenderText_Blended_Wrapped(font, text.c_str(), color, wrapWidth)
        : TTF_RenderText_Blended(font, text.c_str(), color);
    if (!surface) {
        return;
    }
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
    if (!texture) {
        SDL_FreeSurface(surface);
        return;
    }
    int width = surface->w;
    int height = surface->h;
    SDL_Rect rect = { x, y, width, height };
    SDL_RenderCopy(renderer, texture, nullptr, &rect);
    SDL_DestroyTexture(texture);
    SDL_FreeSurface(surface);
}

int main(int argc, char** argv) {
    Uint32 frameStart;
    int frameTime;
    GameState gameState = MENU;

    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
        std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl;
        return 1;
    }

    // Initialize SDL_image
    if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
        std::cerr << "Failed to initialize SDL_image: " << IMG_GetError() << std::endl;
        SDL_Quit();
        return 1;
    }

    // Initialize SDL_ttf
    if (TTF_Init() == -1) {
        std::cerr << "Failed to initialize SDL_ttf: " << TTF_GetError() << std::endl;
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize SDL_mixer
    if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
        std::cerr << "Failed to initialize SDL_mixer: " << Mix_GetError() << std::endl;
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize the game
    std::unique_ptr<Game> game = std::make_unique<Game>();
    if (!game->init()) {
        std::cerr << "Failed to initialize game." << std::endl;
        Mix_CloseAudio();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Get the renderer from the game
    renderer = game->getRenderer();

    // Load fonts
    defaultFont = TTF_OpenFont("assets/Font/arial.ttf", FONT_SIZE);
    storylineFont = TTF_OpenFont("assets/Font/lucida_blackletter_regular.ttf", FONT_SIZE);
    if (!defaultFont || !storylineFont) {
        std::cerr << "Failed to load fonts: " << TTF_GetError() << std::endl;
        Mix_CloseAudio();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load textures
    menuBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/menu_backdrop.png", renderer);
    speechBubbleTexture = TextureManager::LoadTexture("assets/speech_bubble.png", renderer);
    if (!menuBackdropTexture || !speechBubbleTexture) {
        std::cerr << "Failed to load textures." << std::endl;
        TTF_CloseFont(defaultFont);
        TTF_CloseFont(storylineFont);
        Mix_CloseAudio();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load music
    backgroundMusic = Mix_LoadMUS("assets/Music/background_music.mp3");
    combatMusic = Mix_LoadMUS("assets/Music/combat_music.mp3");
    if (!backgroundMusic || !combatMusic) {
        std::cerr << "Failed to load music: " << Mix_GetError() << std::endl;
        SDL_DestroyTexture(menuBackdropTexture);
        SDL_DestroyTexture(speechBubbleTexture);
        TTF_CloseFont(defaultFont);
        TTF_CloseFont(storylineFont);
        Mix_CloseAudio();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize static Story instances
    storyInstance = Story(renderer, storylineFont, storyLines, 50); // 50ms delay between characters
    tutorialStoryInstance = Story(renderer, storylineFont, tutorialStoryLines, 50); // 50ms delay between characters

    // Define constants for button dimensions, spacing, and offset
    const int BUTTON_WIDTH = 180;       // Default width of each button
    const int ABOUT_BUTTON_WIDTH = 250; // Wider width for the "About Developer" button
    const int BUTTON_HEIGHT = 70;      // Height of each button
    const int BUTTON_SPACING = 50;      // Positive spacing between buttons
    const int NUM_BUTTONS = 4;          // Number of buttons

    // Get the dimensions of the window
    int windowWidth, windowHeight;
    SDL_GetRendererOutputSize(renderer, &windowWidth, &windowHeight);

    // Calculate button positions
    int totalButtonHeight = NUM_BUTTONS * BUTTON_HEIGHT + (NUM_BUTTONS - 1) * BUTTON_SPACING;
    int startY = (windowHeight - totalButtonHeight) / 2 + 50; // Center vertically

    // Define buttons using the static renderer
    Button buttons[NUM_BUTTONS] = {
        Button((windowWidth - BUTTON_WIDTH) / 2, startY, BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/start button.png", "assets/Buttons/clicked sta2.png", "assets/Buttons/clicked sta2.png", renderer), // Start Button (Index 0)
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/tutorial button.png", "assets/Buttons/clicked tutorial2.png", "assets/Buttons/clicked tutorial2.png", renderer), // Tutorial Button (Index 1)
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + 2 * (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/option button.png", "assets/Buttons/option_clicked.png", "assets/Buttons/option_clicked.png", renderer), // Options Button (Index 2)
        Button((windowWidth - ABOUT_BUTTON_WIDTH) / 2, startY + 3 * (BUTTON_HEIGHT + BUTTON_SPACING), ABOUT_BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/about develper button.png", "assets/Buttons/clicked ab de 2.png", "assets/Buttons/clicked ab de 2.png", renderer) // About Developer Button (Index 3)
    };

    // Set onClick callbacks for each button
    buttons[0].setOnClick([&]() {
        gameState = STORY; // Change game state to STORY
        storyInstance.reset(); // Reset the story
        });
    buttons[1].setOnClick([&]() {
        gameState = TUTORIAL; // Change game state to TUTORIAL
        tutorialStoryInstance.reset(); // Reset the tutorial story
        });
    buttons[2].setOnClick([&]() {
        gameState = OPTIONS; // Change game state to OPTIONS
        });
    buttons[3].setOnClick([&]() {
        gameState = ABOUT; // Change game state to ABOUT
        });


    // Variables for volume control
    int musicVolume = MIX_MAX_VOLUME; // Initial volume for music
    int soundVolume = MIX_MAX_VOLUME; // Initial volume for sound effects

    // Main Game Loop
    while (gameState != EXIT) {
        frameStart = SDL_GetTicks();
        SDL_Event event;

        // Event handling
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
                gameState = EXIT;
            }

            // Handle returning to the menu from TUTORIAL, OPTIONS, or ABOUT
            if ((gameState == TUTORIAL || gameState == OPTIONS || gameState == ABOUT) && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_b) {
                    gameState = MENU; // Transition back to the MENU state
                    for (int i = 0; i < NUM_BUTTONS; ++i) {
                        buttons[i].resetState(); // Reset button textures to their default state
                    }
                }
            }

            // Handle advancing the story
            if (gameState == STORY && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (storyInstance.isComplete()) {
                        gameState = PLAYING; // Transition to the PLAYING state
                        game->startCombat(); // Start combat logic
                    }
                    else {
                        storyInstance.advanceToNextLine(); // Move to the next line of the story
                    }
                }
            }

            // Handle advancing the tutorial story
            if (gameState == TUTORIAL && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (tutorialStoryInstance.isComplete()) {
                        gameState = TUTORIAL_FIGHT_FAILURE; // Transition to TUTORIAL_FIGHT_FAILURE state
                    }
                    else {
                        tutorialStoryInstance.advanceToNextLine(); // Move to the next line of the story
                    }
                }
            }

            // Handle button events
            if (gameState == MENU) {
                for (int i = 0; i < NUM_BUTTONS; ++i) {
                    buttons[i].handleEvent(&event); // Call handleEvent for each button
                }
            }
        }

        // Render based on game state
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);

        switch (gameState) {
        case MENU:
            SDL_RenderCopy(renderer, menuBackdropTexture, nullptr, nullptr);
            for (int i = 0; i < NUM_BUTTONS; ++i) {
                buttons[i].render(renderer);
            }
            break;

        case STORY: {
            // Render the speech bubble
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2, // Center horizontally
                windowHeight - 400,      // Y-Position
                800,                     // Width
                300                      // Height
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

            // Update and render the story
            storyInstance.update(SDL_GetTicks());
            storyInstance.render(renderer,
                speechBubbleRect.x + 50,  // Offset from left edge
                speechBubbleRect.y + 90,  // Lowered offset from top edge
                TEXT_WRAP_WIDTH);         // Maximum width for text wrapping

            // Display a message to press SPACE to continue, but only if the story is complete
            if (storyInstance.isComplete()) {
                const std::string continueMessage = "Press SPACE to continue";
                renderText(renderer, defaultFont, continueMessage, { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case TUTORIAL: {
            // Only render the speech bubble if the tutorial story is not complete
            if (!tutorialStoryInstance.isComplete()) {
                // Render the speech bubble
                SDL_Rect speechBubbleRect = {
                    (windowWidth - 800) / 2, // Center horizontally
                    windowHeight - 400,      // Y-Position
                    800,                     // Width
                    300                      // Height
                };
                SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

                // Update and render the tutorial story
                tutorialStoryInstance.update(SDL_GetTicks());
                tutorialStoryInstance.render(renderer,
                    speechBubbleRect.x + 50,  // Offset from left edge
                    speechBubbleRect.y + 90,  // Lowered offset from top edge
                    TEXT_WRAP_WIDTH);         // Maximum width for text wrapping
            }

            // Display a message to press B to return to the menu, but only if the story is complete
            if (tutorialStoryInstance.isComplete()) {
                const std::string returnMessage = "Press B to return to menu";
                renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 100);
            }
            break;
        }

        case TUTORIAL_FIGHT_FAILURE: {
            // Render the speech bubble
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2, // Center horizontally
                windowHeight - 400,      // Y-Position
                800,                     // Width
                300                      // Height
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

            // Render the tutorial fight failure story
            tutorialStoryInstance.update(SDL_GetTicks());
            tutorialStoryInstance.render(renderer,
                speechBubbleRect.x + 50,  // Offset from left edge
                speechBubbleRect.y + 90,  // Lowered offset from top edge
                TEXT_WRAP_WIDTH);         // Maximum width for text wrapping

            // Display a message to press B to return to the menu
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 100);

            break;
        }

        case OPTIONS: {
            const std::string optionsMessage =
                "Options Menu:\n\n"
                "Here you can configure game settings such as audio volume.\n"
                "Use the Up/Down arrow keys to adjust music volume.";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, optionsMessage, { 255, 255, 255, 255 }, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 50);

            // Display current music volume level
            std::string musicVolumeText = "Music Volume: " + std::to_string(musicVolume);
            renderText(renderer, defaultFont, musicVolumeText, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 150);

            // Draw the music volume bar at the bottom of the window
            int volumeBarX = (windowWidth - VOLUME_BAR_WIDTH) / 2; // Center the bar horizontally
            int volumeBarY = windowHeight - 100;                  // Position the bar above the return message
            SDL_Rect volumeBarBackground = { volumeBarX, volumeBarY, VOLUME_BAR_WIDTH, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_BACKGROUND_COLOR.r, VOLUME_BAR_BACKGROUND_COLOR.g, VOLUME_BAR_BACKGROUND_COLOR.b, VOLUME_BAR_BACKGROUND_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarBackground);

            // Calculate the width of the filled part of the volume bar
            int filledWidth = (musicVolume * VOLUME_BAR_WIDTH) / MIX_MAX_VOLUME;
            SDL_Rect volumeBarFilled = { volumeBarX, volumeBarY, filledWidth, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_COLOR.r, VOLUME_BAR_COLOR.g, VOLUME_BAR_COLOR.b, VOLUME_BAR_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarFilled);

            // Handle volume adjustment using arrow keys
            const Uint8* keystates = SDL_GetKeyboardState(nullptr);
            if (keystates[SDL_SCANCODE_UP]) {
                if (musicVolume < MIX_MAX_VOLUME) {
                    musicVolume += 1; // Adjust volume incrementally
                    Mix_VolumeMusic(musicVolume);
                }
            }
            if (keystates[SDL_SCANCODE_DOWN]) {
                if (musicVolume > 0) {
                    musicVolume -= 1; // Adjust volume incrementally
                    Mix_VolumeMusic(musicVolume);
                }
            }
            break;
        }

        case ABOUT: {
            const std::string aboutMessage =
                "Knight Revenger is a story-driven RPG built using SDL in C++, where you play as a wrongfully exiled Knight on a quest to reclaim your honor and uncover the truth behind your expulsion. Featuring challenging gameplay, memorable characters, and immersive visuals and music, the game explores themes of redemption, identity, and justice in a richly crafted world.\n\n"
                "This project is brought to you by a diverse team of students, blending creativity and collaboration to deliver an unforgettable gaming experience.\n\n"
                "Thank you for supporting our journey!";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, aboutMessage, { 255, 255, 255, 255 }, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 50);
            break;
        }

        case PLAYING: {
            // Start combat music
            if (Mix_PlayMusic(combatMusic, -1) == -1) {
                std::cerr << "Failed to play combat music: " << Mix_GetError() << std::endl;
            }
            // Combat logic here
            break;
        }
        }

        // Present the rendered frame (only once per frame)
        SDL_RenderPresent(renderer);

        // Frame timing
        frameTime = SDL_GetTicks() - frameStart;
        if (FRAME_DELAY > frameTime) {
            SDL_Delay(FRAME_DELAY - frameTime);
        }
    }

    // Cleanup
    SDL_DestroyTexture(menuBackdropTexture);
    SDL_DestroyTexture(speechBubbleTexture);
    Mix_FreeMusic(backgroundMusic);
    Mix_FreeMusic(combatMusic);
    Mix_CloseAudio();
    TTF_CloseFont(defaultFont);
    TTF_CloseFont(storylineFont);
    TTF_Quit();
    IMG_Quit();
    SDL_Quit();

    return 0;
}

Set some breakpoints in the code to debug. Make sure that the callback function for each button is properly called when the buttons are pressed.
Also print some debug info to the console whenever a callback function is called.

ok got that fixed, but now trying to transition from one song to another. When Start button is clicked story music plays, and when user press Space to continue I use Mix_FadeOutMusic(1000)

#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <iostream>
#include <memory>
#include <vector>
#include "Game.h"
#include "Button.h"
#include "Story.h"
#include "StoryLines.h"
#include "Sprite.h"
#include "TextureManager.h"

// Define GameState enumeration
enum GameState {
    MENU,
    STORY,
    PLAYING,
    TUTORIAL,
    OPTIONS,
    ABOUT,
    EXIT,
    TUTORIAL_FIGHT_FAILURE
};

// Constants
const int FRAME_DELAY = 16; // Approximately 60 FPS
const int FONT_SIZE = 30;   // Increased font size
const int TEXT_WRAP_WIDTH = 600; // Width for text wrapping

// Constants for the volume bar
const int VOLUME_BAR_WIDTH = 300;       // Width of the volume bar
const int VOLUME_BAR_HEIGHT = 20;       // Height of the volume bar
const SDL_Color VOLUME_BAR_COLOR = { 0, 255, 0, 255 }; // Green color for the volume bar
const SDL_Color VOLUME_BAR_BACKGROUND_COLOR = { 100, 100, 100, 255 }; // Gray color for the background

// Declare global objects
TTF_Font* storylineFont = nullptr;
TTF_Font* defaultFont = nullptr;
SDL_Texture* menuBackdropTexture = nullptr;
SDL_Texture* speechBubbleTexture = nullptr;
Mix_Music* backgroundMusic = nullptr;
Mix_Music* combatMusic = nullptr;
Mix_Music* storyMusic = nullptr;
static Story storyInstance(nullptr, nullptr, {}, 0); // Static Story instance
static Story tutorialStoryInstance(nullptr, nullptr, {}, 0); // Static Tutorial Story instance
SDL_Renderer* renderer = nullptr; // Ensure renderer is defined

// Function definitions should be placed after all includes and before main()
float time_(void) {
    static Uint64 start = 0;
    static float frequency = 0;
    if (start == 0) {
        start = SDL_GetPerformanceCounter();
        frequency = (float)SDL_GetPerformanceFrequency();
        return 0.0f;
    }
    Uint64 counter = SDL_GetPerformanceCounter();
    return ((float)(counter - start) / frequency);
}

// Helper function to render text
void renderText(SDL_Renderer* renderer, TTF_Font* font, const std::string& text, SDL_Color color, int x, int y, int wrapWidth = 0) {
    SDL_Surface* surface = wrapWidth > 0
        ? TTF_RenderText_Blended_Wrapped(font, text.c_str(), color, wrapWidth)
        : TTF_RenderText_Blended(font, text.c_str(), color);
    if (!surface) {
        return;
    }
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
    if (!texture) {
        SDL_FreeSurface(surface);
        return;
    }
    int width = surface->w;
    int height = surface->h;
    SDL_Rect rect = { x, y, width, height };
    SDL_RenderCopy(renderer, texture, nullptr, &rect);
    SDL_DestroyTexture(texture);
    SDL_FreeSurface(surface);
}

int main(int argc, char** argv) {
    Uint32 frameStart;
    int frameTime;
    GameState gameState = MENU;

    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
        std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl;
        return 1;
    }

    // Initialize SDL_image
    if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
        std::cerr << "Failed to initialize SDL_image: " << IMG_GetError() << std::endl;
        SDL_Quit();
        return 1;
    }

    // Initialize SDL_ttf
    if (TTF_Init() == -1) {
        std::cerr << "Failed to initialize SDL_ttf: " << TTF_GetError() << std::endl;
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize SDL_mixer
    if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
        std::cerr << "Failed to initialize SDL_mixer: " << Mix_GetError() << std::endl;
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize the game
    std::unique_ptr<Game> game = std::make_unique<Game>();
    if (!game->init()) {
        std::cerr << "Failed to initialize game." << std::endl;
        Mix_CloseAudio();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Get the renderer from the game
    renderer = game->getRenderer();

    // Load fonts
    defaultFont = TTF_OpenFont("assets/Font/arial.ttf", FONT_SIZE);
    storylineFont = TTF_OpenFont("assets/Font/lucida_blackletter_regular.ttf", FONT_SIZE);
    if (!defaultFont || !storylineFont) {
        std::cerr << "Failed to load fonts: " << TTF_GetError() << std::endl;
        Mix_CloseAudio();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load textures
    menuBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/menu_backdrop.png", renderer);
    speechBubbleTexture = TextureManager::LoadTexture("assets/speech_bubble.png", renderer);
    if (!menuBackdropTexture || !speechBubbleTexture) {
        std::cerr << "Failed to load textures." << std::endl;
        TTF_CloseFont(defaultFont);
        TTF_CloseFont(storylineFont);
        Mix_CloseAudio();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load music
    backgroundMusic = Mix_LoadMUS("assets/Music/background_music.mp3");
    storyMusic = Mix_LoadMUS("assets/Music/story_music.mp3");
    combatMusic = Mix_LoadMUS("assets/Music/combat_music.mp3");

    if (!backgroundMusic || !combatMusic || !storyMusic) {
        SDL_DestroyTexture(menuBackdropTexture);
        SDL_DestroyTexture(speechBubbleTexture);
        TTF_CloseFont(defaultFont);
        TTF_CloseFont(storylineFont);
        Mix_CloseAudio();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize static Story instances
    storyInstance = Story(renderer, storylineFont, storyLines, 50); // 50ms delay between characters
    tutorialStoryInstance = Story(renderer, storylineFont, tutorialStoryLines, 50); // 50ms delay between characters

    // Define constants for button dimensions, spacing, and offset
    const int BUTTON_WIDTH = 180;       // Default width of each button
    const int ABOUT_BUTTON_WIDTH = 250; // Wider width for the "About Developer" button
    const int BUTTON_HEIGHT = 70;      // Height of each button
    const int BUTTON_SPACING = 50;      // Positive spacing between buttons
    const int NUM_BUTTONS = 4;          // Number of buttons

    // Get the dimensions of the window
    int windowWidth, windowHeight;
    SDL_GetRendererOutputSize(renderer, &windowWidth, &windowHeight);

    // Calculate button positions
    int totalButtonHeight = NUM_BUTTONS * BUTTON_HEIGHT + (NUM_BUTTONS - 1) * BUTTON_SPACING;
    int startY = (windowHeight - totalButtonHeight) / 2 + 50; // Center vertically

    // Define buttons using the static renderer
    Button buttons[NUM_BUTTONS] = {
        Button((windowWidth - BUTTON_WIDTH) / 2, startY, BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/start button.png", "assets/Buttons/clicked sta2.png", "assets/Buttons/clicked sta2.png", renderer), // Start Button (Index 0)
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/tutorial button.png", "assets/Buttons/clicked tutorial2.png", "assets/Buttons/clicked tutorial2.png", renderer), // Tutorial Button (Index 1)
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + 2 * (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/option button.png", "assets/Buttons/option_clicked.png", "assets/Buttons/option_clicked.png", renderer), // Options Button (Index 2)
        Button((windowWidth - ABOUT_BUTTON_WIDTH) / 2, startY + 3 * (BUTTON_HEIGHT + BUTTON_SPACING), ABOUT_BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/about develper button.png", "assets/Buttons/clicked ab de 2.png", "assets/Buttons/clicked ab de 2.png", renderer) // About Developer Button (Index 3)
    };

    // Set onClick callbacks for each button
    buttons[0].setOnClick([&]() {
        gameState = STORY; // Change game state to STORY
        storyInstance.reset(); // Reset the story

        // Stop background music and start story music
        if (Mix_PlayingMusic()) {
            Mix_HaltMusic(); // Stop any currently playing music
        }
        if (Mix_PlayMusic(storyMusic, -1) == -1) { // Play story music in a loop
            std::cerr << "Failed to play story music: " << Mix_GetError() << std::endl;
        }
        });

    buttons[1].setOnClick([&]() {
        gameState = TUTORIAL; // Change game state to TUTORIAL
        tutorialStoryInstance.reset(); // Reset the tutorial story
        });

    buttons[2].setOnClick([&]() {
        gameState = OPTIONS; // Change game state to OPTIONS
        });

    buttons[3].setOnClick([&]() {
        gameState = ABOUT; // Change game state to ABOUT
        });

    // Variables for volume control
    int musicVolume = MIX_MAX_VOLUME; // Initial volume for music
    int soundVolume = MIX_MAX_VOLUME; // Initial volume for sound effects

    // Main Game Loop
    while (gameState != EXIT) {
        frameStart = SDL_GetTicks();
        SDL_Event event;

        // Event handling
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
                gameState = EXIT;
            }

            // Handle returning to the menu from TUTORIAL, OPTIONS, or ABOUT
            if ((gameState == TUTORIAL || gameState == OPTIONS || gameState == ABOUT) && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_b) {
                    gameState = MENU; // Transition back to the MENU state
                    for (int i = 0; i < NUM_BUTTONS; ++i) {
                        buttons[i].resetState(); // Reset button textures to their default state
                    }
                }
            }

            // Handle advancing the story
            if (gameState == STORY && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (storyInstance.isComplete()) {
                        // Stop story music
                        if (Mix_PlayingMusic()) {
                            Mix_FadeOutMusic(1000);
                        }

                        // Transition to the PLAYING state
                        gameState = PLAYING;
                        game->startCombat(); // Start combat logic

                        // Wait for the fade-out to complete before starting combat music
                        SDL_Delay(1000);

                    }
                    else {
                        storyInstance.advanceToNextLine(); // Move to the next line of the story
                    }
                }
            }

            // Handle advancing the tutorial story
            if (gameState == TUTORIAL && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (tutorialStoryInstance.isComplete()) {
                        gameState = TUTORIAL_FIGHT_FAILURE; // Transition to TUTORIAL_FIGHT_FAILURE state
                    }
                    else {
                        tutorialStoryInstance.advanceToNextLine(); // Move to the next line of the story
                    }
                }
            }

            // Handle button events
            if (gameState == MENU) {
                for (int i = 0; i < NUM_BUTTONS; ++i) {
                    buttons[i].handleEvent(&event); // Call handleEvent for each button
                }
            }
        }

        // Render based on game state
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        switch (gameState) {
        case MENU:
            // Start background music if not already playing
            if (Mix_PlayingMusic() == 0) { // Check if music is not already playing
                if (Mix_PlayMusic(backgroundMusic, -1) == -1) {
                    std::cerr << "Failed to play background music: " << Mix_GetError() << std::endl;
                }
            }
            SDL_RenderCopy(renderer, menuBackdropTexture, nullptr, nullptr);
            for (int i = 0; i < NUM_BUTTONS; ++i) {
                buttons[i].render(renderer);
            }
            break;

        case STORY: {
            // Render the speech bubble
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2, // Center horizontally
                windowHeight - 400,      // Y-Position
                800,                     // Width
                300                      // Height
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

            // Update and render the story
            storyInstance.update(SDL_GetTicks());
            storyInstance.render(renderer,
                speechBubbleRect.x + 50,  // Offset from left edge
                speechBubbleRect.y + 90,  // Lowered offset from top edge
                TEXT_WRAP_WIDTH);         // Maximum width for text wrapping

            // Check if the story is complete
            if (storyInstance.isComplete()) {
                const std::string continueMessage = "Press SPACE to continue";
                renderText(renderer, defaultFont, continueMessage, { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case TUTORIAL: {
            // Only render the speech bubble if the tutorial story is not complete
            if (!tutorialStoryInstance.isComplete()) {
                // Render the speech bubble
                SDL_Rect speechBubbleRect = {
                    (windowWidth - 800) / 2, // Center horizontally
                    windowHeight - 400,      // Y-Position
                    800,                     // Width
                    300                      // Height
                };
                SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

                // Update and render the tutorial story
                tutorialStoryInstance.update(SDL_GetTicks());
                tutorialStoryInstance.render(renderer,
                    speechBubbleRect.x + 50,  // Offset from left edge
                    speechBubbleRect.y + 90,  // Lowered offset from top edge
                    TEXT_WRAP_WIDTH);         // Maximum width for text wrapping
            }

            // Display a message to press B to return to the menu, but only if the story is complete
            if (tutorialStoryInstance.isComplete()) {
                const std::string returnMessage = "Press B to return to menu";
                renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 100);
            }
            break;
        }

        case TUTORIAL_FIGHT_FAILURE: {
            // Render the speech bubble
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2, // Center horizontally
                windowHeight - 400,      // Y-Position
                800,                     // Width
                300                      // Height
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

            // Render the tutorial fight failure story
            tutorialStoryInstance.update(SDL_GetTicks());
            tutorialStoryInstance.render(renderer,
                speechBubbleRect.x + 50,  // Offset from left edge
                speechBubbleRect.y + 90,  // Lowered offset from top edge
                TEXT_WRAP_WIDTH);         // Maximum width for text wrapping

            // Display a message to press B to return to the menu
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 100);
            break;
        }

        case OPTIONS: {
            const std::string optionsMessage =
                "Options Menu:\n"
                "Here you can configure game settings such as audio volume.\n"
                "Use the Up/Down arrow keys to adjust music volume.";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, optionsMessage, { 255, 255, 255, 255 }, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 50);

            // Display current music volume level
            std::string musicVolumeText = "Music Volume: " + std::to_string(musicVolume);
            renderText(renderer, defaultFont, musicVolumeText, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 150);

            // Draw the music volume bar at the bottom of the window
            int volumeBarX = (windowWidth - VOLUME_BAR_WIDTH) / 2; // Center the bar horizontally
            int volumeBarY = windowHeight - 100;                  // Position the bar above the return message
            SDL_Rect volumeBarBackground = { volumeBarX, volumeBarY, VOLUME_BAR_WIDTH, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_BACKGROUND_COLOR.r, VOLUME_BAR_BACKGROUND_COLOR.g, VOLUME_BAR_BACKGROUND_COLOR.b, VOLUME_BAR_BACKGROUND_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarBackground);

            // Calculate the width of the filled part of the volume bar
            int filledWidth = (musicVolume * VOLUME_BAR_WIDTH) / MIX_MAX_VOLUME;
            SDL_Rect volumeBarFilled = { volumeBarX, volumeBarY, filledWidth, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_COLOR.r, VOLUME_BAR_COLOR.g, VOLUME_BAR_COLOR.b, VOLUME_BAR_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarFilled);

            // Handle volume adjustment using arrow keys
            const Uint8* keystates = SDL_GetKeyboardState(nullptr);
            if (keystates[SDL_SCANCODE_UP]) {
                if (musicVolume < MIX_MAX_VOLUME) {
                    musicVolume += 1; // Adjust volume incrementally
                    Mix_VolumeMusic(musicVolume);
                }
            }
            if (keystates[SDL_SCANCODE_DOWN]) {
                if (musicVolume > 0) {
                    musicVolume -= 1; // Adjust volume incrementally
                    Mix_VolumeMusic(musicVolume);
                }
            }
            break;
        }

        case ABOUT: {
            const std::string aboutMessage =
                "Knight Revenger is a story-driven RPG built using SDL in C++, where you play as a wrongfully exiled Knight on a quest to reclaim your honor and uncover the truth behind your expulsion. Featuring challenging gameplay, memorable characters, and immersive visuals and music, the game explores themes of redemption, identity, and justice in a richly crafted world.\n"
                "This project is brought to you by a diverse team of students, blending creativity and collaboration to deliver an unforgettable gaming experience.\n"
                "Thank you for supporting our journey!";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, aboutMessage, { 255, 255, 255, 255 }, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 50);
            break;
        }
        case PLAYING: {
            // Start combat music
            if (Mix_PlayMusic(combatMusic, -1) == -1) {
                std::cerr << "Failed to play combat music: " << Mix_GetError() << std::endl;
            }
            // Combat logic here
            break;
        }
        }

        // Present the rendered frame (only once per frame)
        SDL_RenderPresent(renderer);

        // Frame timing
        frameTime = SDL_GetTicks() - frameStart;
        if (FRAME_DELAY > frameTime) {
            SDL_Delay(FRAME_DELAY - frameTime);
        }
    }

    // Cleanup
    SDL_DestroyTexture(menuBackdropTexture);
    SDL_DestroyTexture(speechBubbleTexture);
    Mix_HaltMusic();
    Mix_FreeMusic(backgroundMusic);
    Mix_FreeMusic(combatMusic);
    Mix_FreeMusic(storyMusic);
    Mix_CloseAudio();
    TTF_CloseFont(defaultFont);
    TTF_CloseFont(storylineFont);
    TTF_Quit();
    IMG_Quit();
    SDL_Quit();
    return 0;
}
case PLAYING: {
    combatMusic = Mix_LoadMUS("assets/Music/combat_music.mp3");
    // Start combat music
    if (Mix_PlayMusic(combatMusic, -1) == -1) {
        std::cerr << "Failed to play combat music: " << Mix_GetError() << std::endl;
    }
    // Combat logic here
    break;
}

I tried putting load combat music in Playing case but its still not working.

I don’t see Mix_Init() being called, instead you jump straight to opening an audio device. In Mix_Init(), you need to add the flag for mp3 to load/play that file. That’s my guess why no audio is produced. There is no error thrown when you opened the device because Mix_OpenAudio() just defaults to being able to open formats that SDL natively supports (WAVE and similar).

It’s also a good idea to check whether Mix_LoadMUS() returns NULL after loading the music file, when debugging this gives you a chance to report problematic file paths so you can check for spelling errors and if the file exists.

its still not working. I also checked my filepath and its correct.

#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <iostream>
#include <memory>
#include <vector>
#include "Game.h"
#include "Button.h"
#include "Story.h"
#include "StoryLines.h"
#include "Sprite.h"
#include "TextureManager.h"

// Define GameState enumeration
enum GameState {
    MENU,
    STORY,
    PLAYING,
    TUTORIAL,
    OPTIONS,
    ABOUT,
    EXIT,
    TUTORIAL_FIGHT_FAILURE
};

// Constants
const int FRAME_DELAY = 16; // Approximately 60 FPS
const int FONT_SIZE = 30;   // Increased font size
const int TEXT_WRAP_WIDTH = 600; // Width for text wrapping

// Constants for the volume bar
const int VOLUME_BAR_WIDTH = 300;       // Width of the volume bar
const int VOLUME_BAR_HEIGHT = 20;       // Height of the volume bar
const SDL_Color VOLUME_BAR_COLOR = { 0, 255, 0, 255 }; // Green color for the volume bar
const SDL_Color VOLUME_BAR_BACKGROUND_COLOR = { 100, 100, 100, 255 }; // Gray color for the background

// Declare global objects
TTF_Font* storylineFont = nullptr;
TTF_Font* defaultFont = nullptr;
SDL_Texture* menuBackdropTexture = nullptr;
SDL_Texture* speechBubbleTexture = nullptr;
Mix_Music* backgroundMusic = nullptr;
Mix_Music* combatMusic = nullptr;
Mix_Music* storyMusic = nullptr;
static Story storyInstance(nullptr, nullptr, {}, 0); // Static Story instance
static Story tutorialStoryInstance(nullptr, nullptr, {}, 0); // Static Tutorial Story instance
SDL_Renderer* renderer = nullptr; // Ensure renderer is defined

// Function definitions should be placed after all includes and before main()
float time_(void) {
    static Uint64 start = 0;
    static float frequency = 0;
    if (start == 0) {
        start = SDL_GetPerformanceCounter();
        frequency = (float)SDL_GetPerformanceFrequency();
        return 0.0f;
    }
    Uint64 counter = SDL_GetPerformanceCounter();
    return ((float)(counter - start) / frequency);
}

// Helper function to render text
void renderText(SDL_Renderer* renderer, TTF_Font* font, const std::string& text, SDL_Color color, int x, int y, int wrapWidth = 0) {
    SDL_Surface* surface = wrapWidth > 0
        ? TTF_RenderText_Blended_Wrapped(font, text.c_str(), color, wrapWidth)
        : TTF_RenderText_Blended(font, text.c_str(), color);
    if (!surface) {
        SDL_Log("Failed to create text surface: %s", TTF_GetError());
        return;
    }
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
    if (!texture) {
        SDL_FreeSurface(surface);
        SDL_Log("Failed to create text texture: %s", SDL_GetError());
        return;
    }
    int width = surface->w;
    int height = surface->h;
    SDL_Rect rect = { x, y, width, height };
    SDL_RenderCopy(renderer, texture, nullptr, &rect);
    SDL_DestroyTexture(texture);
    SDL_FreeSurface(surface);
}

int main(int argc, char** argv) {
    Uint32 frameStart;
    int frameTime;
    GameState gameState = MENU;

    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
        SDL_Log("Failed to initialize SDL: %s", SDL_GetError());
        return 1;
    }

    // Initialize SDL_image
    if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
        SDL_Log("Failed to initialize SDL_image: %s", IMG_GetError());
        SDL_Quit();
        return 1;
    }

    // Initialize SDL_ttf
    if (TTF_Init() == -1) {
        SDL_Log("Failed to initialize SDL_ttf: %s", TTF_GetError());
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize SDL_mixer with MP3 support
    if (Mix_Init(MIX_INIT_MP3) != MIX_INIT_MP3) {
        SDL_Log("Failed to initialize SDL_mixer with MP3 support: %s", Mix_GetError());
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize SDL_mixer audio
    if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
        SDL_Log("Failed to initialize SDL_mixer audio: %s", Mix_GetError());
        Mix_Quit();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize the game
    std::unique_ptr<Game> game = std::make_unique<Game>();
    if (!game->init()) {
        SDL_Log("Failed to initialize game.");
        Mix_CloseAudio();
        Mix_Quit();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Get the renderer from the game
    renderer = game->getRenderer();

    // Load fonts
    defaultFont = TTF_OpenFont("assets/Font/arial.ttf", FONT_SIZE);
    storylineFont = TTF_OpenFont("assets/Font/lucida_blackletter_regular.ttf", FONT_SIZE);
    if (!defaultFont || !storylineFont) {
        SDL_Log("Failed to load fonts: %s", TTF_GetError());
        Mix_CloseAudio();
        Mix_Quit();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load textures
    menuBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/menu_backdrop.png", renderer);
    speechBubbleTexture = TextureManager::LoadTexture("assets/speech_bubble.png", renderer);
    if (!menuBackdropTexture || !speechBubbleTexture) {
        SDL_Log("Failed to load textures.");
        TTF_CloseFont(defaultFont);
        TTF_CloseFont(storylineFont);
        Mix_CloseAudio();
        Mix_Quit();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load music
    backgroundMusic = Mix_LoadMUS("assets/Music/background_music.mp3");
    if (!backgroundMusic) {
        SDL_Log("Failed to load background music: %s", Mix_GetError());
        SDL_Log("Check if the file exists at: assets/Music/background_music.mp3");
    }

    storyMusic = Mix_LoadMUS("assets/Music/story_music.mp3");
    if (!storyMusic) {
        SDL_Log("Failed to load story music: %s", Mix_GetError());
        SDL_Log("Check if the file exists at: assets/Music/story_music.mp3");
    }

    combatMusic = Mix_LoadMUS("assets/Music/combat_music.mp3");
    if (!combatMusic) {
        SDL_Log("Failed to load combat music: %s", Mix_GetError());
        SDL_Log("Check if the file exists at: assets/Music/combat_music.mp3");
    }

    // Initialize static Story instances
    storyInstance = Story(renderer, storylineFont, storyLines, 50); // 50ms delay between characters
    tutorialStoryInstance = Story(renderer, storylineFont, tutorialStoryLines, 50); // 50ms delay between characters

    // Define constants for button dimensions, spacing, and offset
    const int BUTTON_WIDTH = 180;       // Default width of each button
    const int ABOUT_BUTTON_WIDTH = 250; // Wider width for the "About Developer" button
    const int BUTTON_HEIGHT = 70;      // Height of each button
    const int BUTTON_SPACING = 50;      // Positive spacing between buttons
    const int NUM_BUTTONS = 4;          // Number of buttons

    // Get the dimensions of the window
    int windowWidth, windowHeight;
    SDL_GetRendererOutputSize(renderer, &windowWidth, &windowHeight);

    // Calculate button positions
    int totalButtonHeight = NUM_BUTTONS * BUTTON_HEIGHT + (NUM_BUTTONS - 1) * BUTTON_SPACING;
    int startY = (windowHeight - totalButtonHeight) / 2 + 50; // Center vertically

    // Define buttons using the static renderer
    Button buttons[NUM_BUTTONS] = {
        Button((windowWidth - BUTTON_WIDTH) / 2, startY, BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/start button.png", "assets/Buttons/clicked sta2.png", "assets/Buttons/clicked sta2.png", renderer), // Start Button (Index 0)
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/tutorial button.png", "assets/Buttons/clicked tutorial2.png", "assets/Buttons/clicked tutorial2.png", renderer), // Tutorial Button (Index 1)
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + 2 * (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/option button.png", "assets/Buttons/option_clicked.png", "assets/Buttons/option_clicked.png", renderer), // Options Button (Index 2)
        Button((windowWidth - ABOUT_BUTTON_WIDTH) / 2, startY + 3 * (BUTTON_HEIGHT + BUTTON_SPACING), ABOUT_BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/about develper button.png", "assets/Buttons/clicked ab de 2.png", "assets/Buttons/clicked ab de 2.png", renderer) // About Developer Button (Index 3)
    };

    // Set onClick callbacks for each button
    buttons[0].setOnClick([&]() {
        gameState = STORY; // Change game state to STORY
        storyInstance.reset(); // Reset the story

        // Stop background music and start story music
        if (Mix_PlayingMusic()) {
            Mix_HaltMusic(); // Stop any currently playing music
        }
        if (Mix_PlayMusic(storyMusic, -1) == -1) { // Play story music in a loop
            SDL_Log("Failed to play story music: %s", Mix_GetError());
        }
        });

    buttons[1].setOnClick([&]() {
        gameState = TUTORIAL; // Change game state to TUTORIAL
        tutorialStoryInstance.reset(); // Reset the tutorial story
        });

    buttons[2].setOnClick([&]() {
        gameState = OPTIONS; // Change game state to OPTIONS
        });

    buttons[3].setOnClick([&]() {
        gameState = ABOUT; // Change game state to ABOUT
        });

    // Variables for volume control
    int musicVolume = MIX_MAX_VOLUME; // Initial volume for music
    int soundVolume = MIX_MAX_VOLUME; // Initial volume for sound effects

    // Main Game Loop
    while (gameState != EXIT) {
        frameStart = SDL_GetTicks();
        SDL_Event event;

        // Event handling
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
                gameState = EXIT;
            }

            // Handle returning to the menu from TUTORIAL, OPTIONS, or ABOUT
            if ((gameState == TUTORIAL || gameState == OPTIONS || gameState == ABOUT) && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_b) {
                    gameState = MENU; // Transition back to the MENU state
                    for (int i = 0; i < NUM_BUTTONS; ++i) {
                        buttons[i].resetState(); // Reset button textures to their default state
                    }
                }
            }

            // Handle advancing the story
            if (gameState == STORY && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (storyInstance.isComplete()) {
                        // Stop story music
                        if (Mix_PlayingMusic()) {
                            Mix_FadeOutMusic(1000);
                        }

                        // Transition to the PLAYING state
                        gameState = PLAYING;
                        game->startCombat(); // Start combat logic

                        // Load combat music only when transitioning to PLAYING
                        combatMusic = Mix_LoadMUS("assets/Music/combat_music.mp3");
                        if (!combatMusic) {
                            SDL_Log("Failed to load combat music: %s", Mix_GetError());
                            SDL_Log("Check if the file exists at: assets/Music/combat_music.mp3");
                        }
                        else {
                            if (Mix_PlayMusic(combatMusic, -1) == -1) {
                                SDL_Log("Failed to play combat music: %s", Mix_GetError());
                            }
                        }

                        // Wait for the fade-out to complete before starting combat music
                        SDL_Delay(1000);

                    }
                    else {
                        storyInstance.advanceToNextLine(); // Move to the next line of the story
                    }
                }
            }

            // Handle advancing the tutorial story
            if (gameState == TUTORIAL && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (tutorialStoryInstance.isComplete()) {
                        gameState = TUTORIAL_FIGHT_FAILURE; // Transition to TUTORIAL_FIGHT_FAILURE state
                    }
                    else {
                        tutorialStoryInstance.advanceToNextLine(); // Move to the next line of the story
                    }
                }
            }

            // Handle button events
            if (gameState == MENU) {
                for (int i = 0; i < NUM_BUTTONS; ++i) {
                    buttons[i].handleEvent(&event); // Call handleEvent for each button
                }
            }
        }

        // Render based on game state
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        switch (gameState) {
        case MENU:
            // Start background music if not already playing
            if (Mix_PlayingMusic() == 0) { // Check if music is not already playing
                if (Mix_PlayMusic(backgroundMusic, -1) == -1) {
                    SDL_Log("Failed to play background music: %s", Mix_GetError());
                }
            }
            SDL_RenderCopy(renderer, menuBackdropTexture, nullptr, nullptr);
            for (int i = 0; i < NUM_BUTTONS; ++i) {
                buttons[i].render(renderer);
            }
            break;

        case STORY: {
            // Render the speech bubble
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2, // Center horizontally
                windowHeight - 400,      // Y-Position
                800,                     // Width
                300                      // Height
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

            // Update and render the story
            storyInstance.update(SDL_GetTicks());
            storyInstance.render(renderer,
                speechBubbleRect.x + 50,  // Offset from left edge
                speechBubbleRect.y + 90,  // Lowered offset from top edge
                TEXT_WRAP_WIDTH);         // Maximum width for text wrapping

            // Check if the story is complete
            if (storyInstance.isComplete()) {
                const std::string continueMessage = "Press SPACE to continue";
                renderText(renderer, defaultFont, continueMessage, { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case TUTORIAL: {
            // Only render the speech bubble if the tutorial story is not complete
            if (!tutorialStoryInstance.isComplete()) {
                // Render the speech bubble
                SDL_Rect speechBubbleRect = {
                    (windowWidth - 800) / 2, // Center horizontally
                    windowHeight - 400,      // Y-Position
                    800,                     // Width
                    300                      // Height
                };
                SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

                // Update and render the tutorial story
                tutorialStoryInstance.update(SDL_GetTicks());
                tutorialStoryInstance.render(renderer,
                    speechBubbleRect.x + 50,  // Offset from left edge
                    speechBubbleRect.y + 90,  // Lowered offset from top edge
                    TEXT_WRAP_WIDTH);         // Maximum width for text wrapping
            }

            // Display a message to press B to return to the menu, but only if the story is complete
            if (tutorialStoryInstance.isComplete()) {
                const std::string returnMessage = "Press B to return to menu";
                renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 100);
            }
            break;
        }

        case TUTORIAL_FIGHT_FAILURE: {
            // Render the speech bubble
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2, // Center horizontally
                windowHeight - 400,      // Y-Position
                800,                     // Width
                300                      // Height
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

            // Render the tutorial fight failure story
            tutorialStoryInstance.update(SDL_GetTicks());
            tutorialStoryInstance.render(renderer,
                speechBubbleRect.x + 50,  // Offset from left edge
                speechBubbleRect.y + 90,  // Lowered offset from top edge
                TEXT_WRAP_WIDTH);         // Maximum width for text wrapping

            // Display a message to press B to return to the menu
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 100);
            break;
        }

        case OPTIONS: {
            const std::string optionsMessage =
                "Options Menu:\n"
                "Here you can configure game settings such as audio volume.\n"
                "Use the Up/Down arrow keys to adjust music volume.";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, optionsMessage, { 255, 255, 255, 255 }, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 50);

            // Display current music volume level
            std::string musicVolumeText = "Music Volume: " + std::to_string(musicVolume);
            renderText(renderer, defaultFont, musicVolumeText, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 150);

            // Draw the music volume bar at the bottom of the window
            int volumeBarX = (windowWidth - VOLUME_BAR_WIDTH) / 2; // Center the bar horizontally
            int volumeBarY = windowHeight - 100;                  // Position the bar above the return message
            SDL_Rect volumeBarBackground = { volumeBarX, volumeBarY, VOLUME_BAR_WIDTH, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_BACKGROUND_COLOR.r, VOLUME_BAR_BACKGROUND_COLOR.g, VOLUME_BAR_BACKGROUND_COLOR.b, VOLUME_BAR_BACKGROUND_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarBackground);

            // Calculate the width of the filled part of the volume bar
            int filledWidth = (musicVolume * VOLUME_BAR_WIDTH) / MIX_MAX_VOLUME;
            SDL_Rect volumeBarFilled = { volumeBarX, volumeBarY, filledWidth, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_COLOR.r, VOLUME_BAR_COLOR.g, VOLUME_BAR_COLOR.b, VOLUME_BAR_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarFilled);

            // Handle volume adjustment using arrow keys
            const Uint8* keystates = SDL_GetKeyboardState(nullptr);
            if (keystates[SDL_SCANCODE_UP]) {
                if (musicVolume < MIX_MAX_VOLUME) {
                    musicVolume += 1; // Adjust volume incrementally
                    Mix_VolumeMusic(musicVolume);
                }
            }
            if (keystates[SDL_SCANCODE_DOWN]) {
                if (musicVolume > 0) {
                    musicVolume -= 1; // Adjust volume incrementally
                    Mix_VolumeMusic(musicVolume);
                }
            }
            break;
        }

        case ABOUT: {
            const std::string aboutMessage =
                "Knight Revenger is a story-driven RPG built using SDL in C++, where you play as a wrongfully exiled Knight on a quest to reclaim your honor and uncover the truth behind your expulsion. Featuring challenging gameplay, memorable characters, and immersive visuals and music, the game explores themes of redemption, identity, and justice in a richly crafted world.\n"
                "This project is brought to you by a diverse team of students, blending creativity and collaboration to deliver an unforgettable gaming experience.\n"
                "Thank you for supporting our journey!";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, aboutMessage, { 255, 255, 255, 255 }, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 50);
            break;
        }
        case PLAYING: {
            // Start combat music
            if (Mix_PlayMusic(combatMusic, -1) == -1) {
                SDL_Log("Failed to play combat music: %s", Mix_GetError());
            }
            // Combat logic here
            break;
        }
        }

        // Present the rendered frame (only once per frame)
        SDL_RenderPresent(renderer);

        // Frame timing
        frameTime = SDL_GetTicks() - frameStart;
        if (FRAME_DELAY > frameTime) {
            SDL_Delay(FRAME_DELAY - frameTime);
        }
    }

    // Cleanup
    SDL_DestroyTexture(menuBackdropTexture);
    SDL_DestroyTexture(speechBubbleTexture);
    Mix_HaltMusic();
    Mix_FreeMusic(backgroundMusic);
    Mix_FreeMusic(combatMusic);
    Mix_FreeMusic(storyMusic);
    Mix_CloseAudio();
    Mix_Quit();  // Shutdown SDL_mixer
    TTF_CloseFont(defaultFont);
    TTF_CloseFont(storylineFont);
    TTF_Quit();
    IMG_Quit();
    SDL_Quit();
    return 0;
}

I’m not sure it’s a good idea to call Mix_PlayMusic every frame. Wouldn’t that restart the music?

so how should I change music?

Call it once when you want to change the music. Not repeatedly.

You mean call PlayMusic once for all three music files or once for individual music files? I tried doing it for all three music files and it crashed my Game. Added Sound Manager Class to handle all music related things.

#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <iostream>
#include <memory>
#include <vector>
#include "Game.h"
#include "Button.h"
#include "Story.h"
#include "StoryLines.h"
#include "Sprite.h"
#include "TextureManager.h"
#include "SoundManager.h"

// Define GameState enumeration
enum GameState {
    MENU,
    STORY,
    PLAYING,
    TUTORIAL,
    OPTIONS,
    ABOUT,
    EXIT,
    TUTORIAL_FIGHT_FAILURE
};

// Constants
const int FRAME_DELAY = 16; // Approximately 60 FPS
const int FONT_SIZE = 30;   // Increased font size
const int TEXT_WRAP_WIDTH = 600; // Width for text wrapping

// Constants for the volume bar
const int VOLUME_BAR_WIDTH = 300;       // Width of the volume bar
const int VOLUME_BAR_HEIGHT = 20;       // Height of the volume bar
const SDL_Color VOLUME_BAR_COLOR = { 0, 255, 0, 255 }; // Green color for the volume bar
const SDL_Color VOLUME_BAR_BACKGROUND_COLOR = { 100, 100, 100, 255 }; // Gray color for the background

// Declare global objects
TTF_Font* storylineFont = nullptr;
TTF_Font* defaultFont = nullptr;
SDL_Texture* menuBackdropTexture = nullptr;
SDL_Texture* speechBubbleTexture = nullptr;
SoundManager soundManager; // Use SoundManager for music and sound effects
SDL_Renderer* renderer = nullptr; // Ensure renderer is defined

// Function definitions should be placed after all includes and before main()
float time_(void) {
    static Uint64 start = 0;
    static float frequency = 0;
    if (start == 0) {
        start = SDL_GetPerformanceCounter();
        frequency = (float)SDL_GetPerformanceFrequency();
        return 0.0f;
    }
    Uint64 counter = SDL_GetPerformanceCounter();
    return ((float)(counter - start) / frequency);
}

// Helper function to render text
void renderText(SDL_Renderer* renderer, TTF_Font* font, const std::string& text, SDL_Color color, int x, int y, int wrapWidth = 0) {
    SDL_Surface* surface = wrapWidth > 0
        ? TTF_RenderText_Blended_Wrapped(font, text.c_str(), color, wrapWidth)
        : TTF_RenderText_Blended(font, text.c_str(), color);
    if (!surface) {
        SDL_Log("Failed to create text surface: %s", TTF_GetError());
        return;
    }
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
    if (!texture) {
        SDL_FreeSurface(surface);
        SDL_Log("Failed to create text texture: %s", SDL_GetError());
        return;
    }
    int width = surface->w;
    int height = surface->h;
    SDL_Rect rect = { x, y, width, height };
    SDL_RenderCopy(renderer, texture, nullptr, &rect);
    SDL_DestroyTexture(texture);
    SDL_FreeSurface(surface);
}

int main(int argc, char** argv) {
    Uint32 frameStart;
    int frameTime;
    GameState gameState = MENU;

    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
        SDL_Log("Failed to initialize SDL: %s", SDL_GetError());
        return 1;
    }

    // Initialize SDL_image
    if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
        SDL_Log("Failed to initialize SDL_image: %s", IMG_GetError());
        SDL_Quit();
        return 1;
    }

    // Initialize SDL_ttf
    if (TTF_Init() == -1) {
        SDL_Log("Failed to initialize SDL_ttf: %s", TTF_GetError());
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize SDL_mixer
    if (!soundManager.init()) {
        SDL_Log("Failed to initialize SDL_mixer.");
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Initialize the game
    std::unique_ptr<Game> game = std::make_unique<Game>();
    if (!game->init()) {
        SDL_Log("Failed to initialize game.");
        soundManager.shutdown();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Get the renderer from the game
    renderer = game->getRenderer();

    // Load fonts
    defaultFont = TTF_OpenFont("assets/Font/arial.ttf", FONT_SIZE);
    storylineFont = TTF_OpenFont("assets/Font/lucida_blackletter_regular.ttf", FONT_SIZE);
    if (!defaultFont || !storylineFont) {
        SDL_Log("Failed to load fonts: %s", TTF_GetError());
        soundManager.shutdown();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load textures
    menuBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/menu_backdrop.png", renderer);
    speechBubbleTexture = TextureManager::LoadTexture("assets/speech_bubble.png", renderer);
    if (!menuBackdropTexture || !speechBubbleTexture) {
        SDL_Log("Failed to load textures.");
        TTF_CloseFont(defaultFont);
        TTF_CloseFont(storylineFont);
        soundManager.shutdown();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load music using SoundManager
    if (!soundManager.loadMusic("assets/Music/background_music.wav", "background")) {
        SDL_Log("Failed to load background music.");
    }
    if (!soundManager.loadMusic("assets/Music/story_music.wav", "story")) {
        SDL_Log("Failed to load story music.");
    }
    if (!soundManager.loadMusic("assets/Music/combat_music.wav", "combat")) {
        SDL_Log("Failed to load combat music.");
    }

    // Initialize static Story instances
    static Story storyInstance(renderer, storylineFont, storyLines, 50); // 50ms delay between characters
    static Story tutorialStoryInstance(renderer, storylineFont, tutorialStoryLines, 50); // 50ms delay between characters

    // Define constants for button dimensions, spacing, and offset
    const int BUTTON_WIDTH = 180;       // Default width of each button
    const int ABOUT_BUTTON_WIDTH = 250; // Wider width for the "About Developer" button
    const int BUTTON_HEIGHT = 70;      // Height of each button
    const int BUTTON_SPACING = 50;      // Positive spacing between buttons
    const int NUM_BUTTONS = 4;          // Number of buttons

    // Get the dimensions of the window
    int windowWidth, windowHeight;
    SDL_GetRendererOutputSize(renderer, &windowWidth, &windowHeight);

    // Calculate button positions
    int totalButtonHeight = NUM_BUTTONS * BUTTON_HEIGHT + (NUM_BUTTONS - 1) * BUTTON_SPACING;
    int startY = (windowHeight - totalButtonHeight) / 2 + 50; // Center vertically

    // Define buttons using the static renderer
    Button buttons[NUM_BUTTONS] = {
        Button((windowWidth - BUTTON_WIDTH) / 2, startY, BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/start button.png", "assets/Buttons/clicked sta2.png", "assets/Buttons/clicked sta2.png", renderer), // Start Button (Index 0)
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/tutorial button.png", "assets/Buttons/clicked tutorial2.png", "assets/Buttons/clicked tutorial2.png", renderer), // Tutorial Button (Index 1)
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + 2 * (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/option button.png", "assets/Buttons/option_clicked.png", "assets/Buttons/option_clicked.png", renderer), // Options Button (Index 2)
        Button((windowWidth - ABOUT_BUTTON_WIDTH) / 2, startY + 3 * (BUTTON_HEIGHT + BUTTON_SPACING), ABOUT_BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/about develper button.png", "assets/Buttons/clicked ab de 2.png", "assets/Buttons/clicked ab de 2.png", renderer) // About Developer Button (Index 3)
    };

    // Set onClick callbacks for each button
    buttons[0].setOnClick([&]() {
        gameState = STORY; // Change game state to STORY
        storyInstance.reset(); // Reset the story

        // Stop background music and start story music
        soundManager.stopMusic();
        soundManager.playMusic("story", -1); // Play story music in a loop
        });

    buttons[1].setOnClick([&]() {
        gameState = TUTORIAL; // Change game state to TUTORIAL
        tutorialStoryInstance.reset(); // Reset the tutorial story
        });

    buttons[2].setOnClick([&]() {
        gameState = OPTIONS; // Change game state to OPTIONS
        });

    buttons[3].setOnClick([&]() {
        gameState = ABOUT; // Change game state to ABOUT
        });

    // Variables for volume control
    int musicVolume = MIX_MAX_VOLUME; // Initial volume for music
    int soundVolume = MIX_MAX_VOLUME; // Initial volume for sound effects

    // Main Game Loop
    while (gameState != EXIT) {
        frameStart = SDL_GetTicks();
        SDL_Event event;

        // Event handling
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
                gameState = EXIT;
            }

            // Handle returning to the menu from TUTORIAL, OPTIONS, or ABOUT
            if ((gameState == TUTORIAL || gameState == OPTIONS || gameState == ABOUT) && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_b) {
                    gameState = MENU; // Transition back to the MENU state
                    for (int i = 0; i < NUM_BUTTONS; ++i) {
                        buttons[i].resetState(); // Reset button textures to their default state
                    }
                }
            }

            // Handle advancing the story
            if (gameState == STORY && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (storyInstance.isComplete()) {
                        // Stop story music
                        soundManager.stopMusic();

                        // Transition to the PLAYING state
                        gameState = PLAYING;
                        game->startCombat(); // Start combat logic

                        // Play combat music
                        soundManager.playMusic("combat", -1);

                    }
                    else {
                        storyInstance.advanceToNextLine(); // Move to the next line of the story
                    }
                }
            }

            // Handle advancing the tutorial story
            if (gameState == TUTORIAL && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_SPACE) {
                    if (tutorialStoryInstance.isComplete()) {
                        gameState = TUTORIAL_FIGHT_FAILURE; // Transition to TUTORIAL_FIGHT_FAILURE state
                    }
                    else {
                        tutorialStoryInstance.advanceToNextLine(); // Move to the next line of the story
                    }
                }
            }

            // Handle button events
            if (gameState == MENU) {
                for (int i = 0; i < NUM_BUTTONS; ++i) {
                    buttons[i].handleEvent(&event); // Call handleEvent for each button
                }
            }
        }

        // Render based on game state
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        switch (gameState) {
        case MENU:
            // Start background music if not already playing
            if (!soundManager.isPlayingMusic()) {
                soundManager.playMusic("background", -1);
            }
            SDL_RenderCopy(renderer, menuBackdropTexture, nullptr, nullptr);
            for (int i = 0; i < NUM_BUTTONS; ++i) {
                buttons[i].render(renderer);
            }
            break;

        case STORY: {
            // Render the speech bubble
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2, // Center horizontally
                windowHeight - 400,      // Y-Position
                800,                     // Width
                300                      // Height
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

            // Update and render the story
            storyInstance.update(SDL_GetTicks());
            storyInstance.render(renderer,
                speechBubbleRect.x + 50,  // Offset from left edge
                speechBubbleRect.y + 90,  // Lowered offset from top edge
                TEXT_WRAP_WIDTH);         // Maximum width for text wrapping

            // Check if the story is complete
            if (storyInstance.isComplete()) {
                const std::string continueMessage = "Press SPACE to continue";
                renderText(renderer, defaultFont, continueMessage, { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case TUTORIAL: {
            // Only render the speech bubble if the tutorial story is not complete
            if (!tutorialStoryInstance.isComplete()) {
                // Render the speech bubble
                SDL_Rect speechBubbleRect = {
                    (windowWidth - 800) / 2, // Center horizontally
                    windowHeight - 400,      // Y-Position
                    800,                     // Width
                    300                      // Height
                };
                SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

                // Update and render the tutorial story
                tutorialStoryInstance.update(SDL_GetTicks());
                tutorialStoryInstance.render(renderer,
                    speechBubbleRect.x + 50,  // Offset from left edge
                    speechBubbleRect.y + 90,  // Lowered offset from top edge
                    TEXT_WRAP_WIDTH);         // Maximum width for text wrapping
            }

            // Display a message to press B to return to the menu, but only if the story is complete
            if (tutorialStoryInstance.isComplete()) {
                const std::string returnMessage = "Press B to return to menu";
                renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 100);
            }
            break;
        }

        case TUTORIAL_FIGHT_FAILURE: {
            // Render the speech bubble
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2, // Center horizontally
                windowHeight - 400,      // Y-Position
                800,                     // Width
                300                      // Height
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);

            // Render the tutorial fight failure story
            tutorialStoryInstance.update(SDL_GetTicks());
            tutorialStoryInstance.render(renderer,
                speechBubbleRect.x + 50,  // Offset from left edge
                speechBubbleRect.y + 90,  // Lowered offset from top edge
                TEXT_WRAP_WIDTH);         // Maximum width for text wrapping

            // Display a message to press B to return to the menu
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 100);
            break;
        }

        case OPTIONS: {
            const std::string optionsMessage =
                "Options Menu:\n"
                "Here you can configure game settings such as audio volume.\n"
                "Use the Up/Down arrow keys to adjust music volume.";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, optionsMessage, { 255, 255, 255, 255 }, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 50);

            // Display current music volume level
            std::string musicVolumeText = "Music Volume: " + std::to_string(musicVolume);
            renderText(renderer, defaultFont, musicVolumeText, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 150);

            // Draw the music volume bar at the bottom of the window
            int volumeBarX = (windowWidth - VOLUME_BAR_WIDTH) / 2; // Center the bar horizontally
            int volumeBarY = windowHeight - 100;                  // Position the bar above the return message
            SDL_Rect volumeBarBackground = { volumeBarX, volumeBarY, VOLUME_BAR_WIDTH, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_BACKGROUND_COLOR.r, VOLUME_BAR_BACKGROUND_COLOR.g, VOLUME_BAR_BACKGROUND_COLOR.b, VOLUME_BAR_BACKGROUND_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarBackground);

            // Calculate the width of the filled part of the volume bar
            int filledWidth = (musicVolume * VOLUME_BAR_WIDTH) / MIX_MAX_VOLUME;
            SDL_Rect volumeBarFilled = { volumeBarX, volumeBarY, filledWidth, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_COLOR.r, VOLUME_BAR_COLOR.g, VOLUME_BAR_COLOR.b, VOLUME_BAR_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarFilled);

            // Handle volume adjustment using arrow keys
            const Uint8* keystates = SDL_GetKeyboardState(nullptr);
            if (keystates[SDL_SCANCODE_UP]) {
                if (musicVolume < MIX_MAX_VOLUME) {
                    musicVolume += 1; // Adjust volume incrementally
                    soundManager.setMusicVolume(musicVolume);
                }
            }
            if (keystates[SDL_SCANCODE_DOWN]) {
                if (musicVolume > 0) {
                    musicVolume -= 1; // Adjust volume incrementally
                    soundManager.setMusicVolume(musicVolume);
                }
            }
            break;
        }

        case ABOUT: {
            const std::string aboutMessage =
                "Knight Revenger is a story-driven RPG built using SDL in C++, where you play as a wrongfully exiled Knight on a quest to reclaim your honor and uncover the truth behind your expulsion. Featuring challenging gameplay, memorable characters, and immersive visuals and music, the game explores themes of redemption, identity, and justice in a richly crafted world.\n"
                "This project is brought to you by a diverse team of students, blending creativity and collaboration to deliver an unforgettable gaming experience.\n"
                "Thank you for supporting our journey!";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, aboutMessage, { 255, 255, 255, 255 }, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 }, (windowWidth - 300) / 2, windowHeight - 50);
            break;
        }
        case PLAYING: {
            // Play combat music if not already playing
            if (!soundManager.isPlayingMusic()) {
                soundManager.playMusic("combat", -1);
            }
            // Combat logic here
            break;
        }
        }

        // Present the rendered frame (only once per frame)
        SDL_RenderPresent(renderer);

        // Frame timing
        frameTime = SDL_GetTicks() - frameStart;
        if (FRAME_DELAY > frameTime) {
            SDL_Delay(FRAME_DELAY - frameTime);
        }
    }

    // Cleanup
    SDL_DestroyTexture(menuBackdropTexture);
    SDL_DestroyTexture(speechBubbleTexture);
    soundManager.shutdown();
    TTF_CloseFont(defaultFont);
    TTF_CloseFont(storylineFont);
    TTF_Quit();
    IMG_Quit();
    SDL_Quit();
    return 0;
}

I mean that you should not call it repeatedly while the music is playing because each call will restart the music. Only call it when you want to change to a different music.

I managed to solve the song issue by putting game->startCombat(); below play music but Forest backdrop is not loading.

 case PLAYING: {
     // Load Forest backdrop
     ForestBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/forest/forest2.jpg", renderer);
     if (!ForestBackdropTexture) {
         SDL_Log("Failed to load story backdrop: %s", SDL_GetError());
     }
     // Play combat music if not already playing
     if (!soundManager.isPlayingMusic()) {
         soundManager.playMusic("combat", -1);
     }
     // Combat logic here
     game->startCombat(); // Start combat logic
     break;
 }

In Game.cpp I have this

void Game::startCombat() {
    initCombat(1);

    bool combatRunning = true;
    TTF_Font* font = TTF_OpenFont("assets/Font/arial.ttf", 30);
    SDL_Color whiteColor = { 255, 255, 255, 255 };
    SDL_Color redColor = { 255, 0, 0, 255 };
    SDL_Color blueColor = { 0, 0, 255, 255 };

    CombatContext context = { renderer, font, whiteColor, redColor, blueColor };

    while (combatRunning) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                combatRunning = false;
                isRunning = false;
            }
            handleCombatEvent(&event, &context);
        }

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);

        renderCombat(&context);

        SDL_RenderPresent(renderer);

        if (playerHealth <= 0 || enemyHealth <= 0) {
            combatRunning = false;
        }
    }

    TTF_CloseFont(font);
}

I don’t think I completely understand your code at this moment.

Is the startCombat function called every frame or just once?

Some weird stuff is going on in your code.
You have 1 main loop (while (gameState != EXIT)).
Inside that, you’re checking for event with a while (SDL_PollEvent(&event)).
This if perfectly fine.

What’s getting weird after that is that you have another while loop inside the startCombat function (while (combatRunning)) that the code gets stuck in.
Inside that while loop, you’re once again checking for events with another while (SDL_PollEvent(&event)).

Why do you have a while loop, and checking for events with SDL_PollEvents() an extra time, inside the startCombat function?

I use while (combatRunning) to create an **event-driven and real-time combat system. The while (SDL_PollEvent(&event)) block inside the loop continuously checks for user input (e.g., mouse clicks, keyboard presses) and processes these events in real time. Without the outer while (combatRunning) loop, the program would only process a single event and then exit, which would prevent the combat system from functioning interactively. I did try to replace the while loop but then Combat does not start when Space is pressed and the Game just freezes fprcing me to restart my computer. game->startCombat(); was only called once in Case Playing.

In the while loop polling for events, you’re calling handleCombatEvent. Isn’t that already handling the combat thing?

I personally don’t think it’s necessary to have another while loop, with an event-polling loop inside it, in the startCombat function.
You should be able to just send the event into the combat from the first event loop, like you do with handleCombatEvent.

One general thing that I want to give an advice on, that doesn’t help you with bugs you’re having at the moment but will help you later on.
I think you should split up your code in multiple source file, instead of having all the code in the main file. You might have this planned later on but you should consider doing so soon, before the code gets even bigger, which will make it harder to maintain and debug.

Alright got it. Thanks for the help

Using multiple SDL_PollEvent loops is rarely a good idea because they “steal” events from each other (an event that is handled in one loop won’t get handled by the other loop). I guess it might be fine if you have two completely different “game states” that runs at different times and need to handle events completely different but that is rarely the case. Usually you have at least some events, like SDL_QUIT, SDL_WINDOWEVENT_SIZE_CHANGED or global keyboard shortcuts, that should always (or almost always) work the same regardless of the state of the game. For that reason it’s usually better to poll all events in one place, and then pass them down to other functions (which might depend on the game state) if necessary.