Menu Buttons behaving weirdly

Game Project Hi so I’m trying to create a main menu, but the buttons seem to behave weirdly, they are not opening the things they are assigned to any advice?

// Define constants for button dimensions, spacing, and offset
const int BUTTON_WIDTH = 400;
const int BUTTON_HEIGHT = 400;
const int BUTTON_SPACING = -290;
const int NUM_BUTTONS = 4;

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

// Calculate button positions
int totalButtonHeight = NUM_BUTTONS * BUTTON_HEIGHT + (NUM_BUTTONS - 1) * BUTTON_SPACING;
int startY = (windowHeight - totalButtonHeight) / 2 + 150;
int startX = (windowWidth - BUTTON_WIDTH) / 2 + 10;

// Define buttons using the static renderer
Button buttons[NUM_BUTTONS] = {
    Button(startX, startY, BUTTON_WIDTH, BUTTON_HEIGHT, "assets/start button.png", "assets/clicked sta2.png", "assets/clicked sta2.png", Game::renderer), // Start Button (Index 0)
    Button(startX, startY + (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/tutorial button.png", "assets/clicked tutorial2.png", "assets/clicked tutorial2.png", Game::renderer), // Tutorial Button (Index 1)
    Button(startX, startY + 2 * (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/option button.png", "assets/option_clicked.png", "assets/option_clicked.png", Game::renderer), // Options Button (Index 2)
    Button(startX, startY + 3 * (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/about develper button.png", "assets/clicked ab de 2.png", "assets/clicked ab de 2.png", Game::renderer) // About Developer Button (Index 3)
};
// Main game loop
while (gameState != EXIT) {
    frameStart = SDL_GetTicks();
    SDL_Event event;
    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 or OPTIONS
        if ((gameState == TUTORIAL || gameState == OPTIONS) && event.type == SDL_KEYDOWN) {
            gameState = MENU;
        }

        if (gameState == ABOUT && event.type == SDL_KEYDOWN) {
            gameState = MENU;
        }

        if (gameState == MENU && event.type == SDL_MOUSEBUTTONDOWN) {
            int x, y;
            SDL_GetMouseState(&x, &y);
            for (int i = 0; i < NUM_BUTTONS; ++i) {
                if (buttons[i].isClicked(x, y)) {
                    switch (i) {
                    case 0: // START button pressed
                        gameState = PLAYING;
                        Mix_HaltMusic(); // Stop background music
                        if (combatMusic) {
                            Mix_PlayMusic(combatMusic, -1); // Start combat music
                        }
                        if (game->startCombat()) { // Start combat logic
                            gameState = EXIT; // Exit if combat ends prematurely
                        }
                        break;

                    case 1: // TUTORIAL button pressed
                        gameState = TUTORIAL;
                        break;

                    case 2: // OPTIONS button pressed
                        gameState = OPTIONS;
                        break;

                    case 3: // ABOUT DEVELOPER button pressed
                        gameState = ABOUT;
                        break;
                    }
                }
            }
        }
    }

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

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

    case TUTORIAL: {
        const std::string tutorialMessage =
            "Welcome to the tutorial!\n\n"
            "In this game, you will embark on an epic quest to reclaim your honor. Use the mouse to interact with menus and buttons. During combat, click on dice to select them and press 'Attack' to deal damage to the enemy.\n\n"
            "Press any key to return to the menu.";
        renderText(Game::renderer, font, tutorialMessage, whiteColor, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
        break;
    }

    case OPTIONS: {
        const std::string optionsMessage =
            "Options Menu:\n\n"
            "Here you can configure game settings such as audio volume, resolution, and key bindings.\n\n"
            "Press any key to return to the menu.";
        renderText(Game::renderer, font, optionsMessage, whiteColor, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
        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 any key to return to menu";
        renderText(Game::renderer, font, aboutMessage, whiteColor, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
        renderText(Game::renderer, font, returnMessage, whiteColor, (windowWidth - 300) / 2, windowHeight - 50);
        break;
    }

    case PLAYING:
        game->handleEvents();
        game->update();
        game->render();
        break;
    }

    SDL_RenderPresent(Game::renderer);

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

The code that you use to calculate the position of the buttons assumes that BUTTON_SPACING is not negative (For that to work you would have to subtract BUTTON_HEIGHT instead of adding it) This causes the buttons to overlap which is why it sometimes opens the wrong menu. Also note that the buttons are much larger than what they look so you might want to make them smaller.

// 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(Game::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/start button.png", "assets/clicked sta2.png", "assets/clicked sta2.png", Game::renderer), // Start Button (Index 0)
    Button((windowWidth - BUTTON_WIDTH) / 2, startY + (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/tutorial button.png", "assets/clicked tutorial2.png", "assets/clicked tutorial2.png", Game::renderer), // Tutorial Button (Index 1)
    Button((windowWidth - BUTTON_WIDTH) / 2, startY + 2 * (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/option button.png", "assets/option_clicked.png", "assets/option_clicked.png", Game::renderer), // Options Button (Index 2)
    Button((windowWidth - ABOUT_BUTTON_WIDTH) / 2, startY + 3 * (BUTTON_HEIGHT + BUTTON_SPACING), ABOUT_BUTTON_WIDTH, BUTTON_HEIGHT, "assets/about develper button.png", "assets/clicked ab de 2.png", "assets/clicked ab de 2.png", Game::renderer) // About Developer Button (Index 3)
};

Ok I fixed the button overlapping by crop my assets, but now I’m trying to make the button change colour when the mouse hovers over it. I use two pngs for each button.

#ifndef BUTTON_H
#define BUTTON_H

#include <SDL.h>
#include <functional> // For std::function (callback support)

class Button {
public:
    // Constructor: Initializes the button with textures and dimensions
    Button(int x, int y, int w, int h, const char* normalPath, const char* hoveredPath, const char* clickedPath, SDL_Renderer* renderer);

    // Destructor: Cleans up textures
    ~Button();

    // Check if the button is clicked based on mouse coordinates
    bool isClicked(int mouseX, int mouseY);

    // Render the button based on its current state (normal, hovered, or clicked)
    void render(SDL_Renderer* renderer);

    // Set a callback function to be executed when the button is clicked
    void setOnClick(std::function<void()> onClick);

    // Handle SDL events (e.g., mouse motion, clicks) for the button
    void handleEvent(SDL_Event* event);

private:
    // Textures for different button states
    SDL_Texture* normalTexture;   // Normal state texture
    SDL_Texture* hoveredTexture;  // Hovered state texture
    SDL_Texture* clickedTexture;  // Clicked state texture

    // Button position and dimensions
    SDL_Rect rect;

    // State flags
    bool isHovered;       // True if the mouse is over the button
    bool isClickedState;  // True if the button is currently clicked

    // Callback function to execute when the button is clicked
    std::function<void()> onClick;
};

#endif // BUTTON_H
#include "Button.h"
#include <SDL_image.h>
#include <iostream>

// Constructor: Initializes the button with textures and dimensions
Button::Button(int x, int y, int w, int h, const char* normalPath, const char* hoveredPath, const char* clickedPath, SDL_Renderer* renderer)
    : rect{ x, y, w, h }, isHovered(false), isClickedState(false), onClick(nullptr) {
    // Load textures for each state
    normalTexture = IMG_LoadTexture(renderer, normalPath);
    if (!normalTexture) {
        SDL_Log("Failed to load normal texture: %s", IMG_GetError());
    }

    hoveredTexture = IMG_LoadTexture(renderer, hoveredPath);
    if (!hoveredTexture) {
        SDL_Log("Failed to load hovered texture: %s", IMG_GetError());
    }

    clickedTexture = IMG_LoadTexture(renderer, clickedPath);
    if (!clickedTexture) {
        SDL_Log("Failed to load clicked texture: %s", IMG_GetError());
    }
}

// Destructor: Cleans up textures
Button::~Button() {
    if (normalTexture) SDL_DestroyTexture(normalTexture);
    if (hoveredTexture) SDL_DestroyTexture(hoveredTexture);
    if (clickedTexture) SDL_DestroyTexture(clickedTexture);
}

// Check if the button is clicked based on mouse coordinates
bool Button::isClicked(int mouseX, int mouseY) {
    return (mouseX >= rect.x && mouseX <= rect.x + rect.w &&
        mouseY >= rect.y && mouseY <= rect.y + rect.h);
}

// Render the button based on its current state
void Button::render(SDL_Renderer* renderer) {
    SDL_Texture* texture = normalTexture;

    // Determine which texture to render based on the button's state
    if (isClickedState && clickedTexture) {
        texture = clickedTexture;
    }
    else if (isHovered && hoveredTexture) {
        texture = hoveredTexture;
    }

    // Render the selected texture
    if (texture) {
        SDL_RenderCopy(renderer, texture, nullptr, &rect);
    }
    else {
        SDL_Log("Attempted to render a null texture for button");
    }
}

// Set a callback function to be executed when the button is clicked
void Button::setOnClick(std::function<void()> onClick) {
    this->onClick = onClick;
}

// Handle SDL events to update the button's state and execute the callback
void Button::handleEvent(SDL_Event* event) {
    if (!event) return; // Ensure the event pointer is valid

    // Handle mouse motion events
    if (event->type == SDL_MOUSEMOTION) {
        int x, y;
        SDL_GetMouseState(&x, &y);
        isHovered = isClicked(x, y); // Update hover state
    }

    // Handle mouse button down events
    else if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_LEFT) {
        int x, y;
        SDL_GetMouseState(&x, &y);
        if (isClicked(x, y)) {
            isClickedState = true; // Update click state
            if (onClick) {
                onClick(); // Execute the callback function
            }
        }
    }

    // Handle mouse button up events
    else if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_LEFT) {
        isClickedState = false; // Reset click state
    }
}

LOL :saluting_face: amazig work, here is my version

Button, mouse , collision

I’m using pngs thou, not sure how to make the appearance change. It worked before I changed the button positioning.

isHovered seems to be updated in Button::handleEvent but I don’t see where this function is called from.

Updated Project

Ok I fixed the hovering issue, but now my Start button just glicthes whenever I click it, it’s supposed to startCombat.

// Main game loop
while (gameState != EXIT) {
    frameStart = SDL_GetTicks();
    SDL_Event event;

    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 or OPTIONS
        if ((gameState == TUTORIAL || gameState == OPTIONS) && event.type == SDL_KEYDOWN) {
            gameState = MENU;
        }

        if (gameState == ABOUT && event.type == SDL_KEYDOWN) {
            gameState = MENU;
        }

        // 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(Game::renderer, 0, 0, 0, 255);
    SDL_RenderClear(Game::renderer);

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

    case TUTORIAL: {
        const std::string tutorialMessage =
            "Welcome to the tutorial!\n\n"
            "In this game, you will embark on an epic quest to reclaim your honor. Use the mouse to interact with menus and buttons. During combat, click on dice to select them and press 'Attack' to deal damage to the enemy.\n\n"
            "Press any key to return to the menu.";
        renderText(Game::renderer, font, tutorialMessage, whiteColor, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
        break;
    }

    case OPTIONS: {
        const std::string optionsMessage =
            "Options Menu:\n\n"
            "Here you can configure game settings such as audio volume, resolution, and key bindings.\n\n"
            "Press any key to return to the menu.";
        renderText(Game::renderer, font, optionsMessage, whiteColor, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
        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 any key to return to menu";
        renderText(Game::renderer, font, aboutMessage, whiteColor, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
        renderText(Game::renderer, font, returnMessage, whiteColor, (windowWidth - 300) / 2, windowHeight - 50);
        break;
    }

    case PLAYING:
        game->handleEvents();
        game->update();
        game->render();
        break;
    }

    SDL_RenderPresent(Game::renderer);

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

#include <SDL.h>
#include <functional> // For std::function (callback support)

class Button {
public:
    // Constructor: Initializes the button with textures and dimensions
    Button(int x, int y, int w, int h, const char* normalPath, const char* hoveredPath, const char* clickedPath, SDL_Renderer* renderer);

    // Destructor: Cleans up textures
    ~Button();

    // Check if the button is clicked based on mouse coordinates
    bool isClicked(int mouseX, int mouseY);

    // Check if the mouse is hovering over the button
    bool isHovering(int mouseX, int mouseY);

    // Render the button based on its current state (normal, hovered, or clicked)
    void render(SDL_Renderer* renderer);

    // Set a callback function to be executed when the button is clicked
    void setOnClick(std::function<void()> onClick);

    // Handle SDL events (e.g., mouse motion, clicks) for the button
    void handleEvent(SDL_Event* event);

    // Get the button's position and dimensions
    SDL_Rect getRect() const;

private:
    // Textures for different button states
    SDL_Texture* normalTexture;   // Normal state texture
    SDL_Texture* hoveredTexture;  // Hovered state texture
    SDL_Texture* clickedTexture;  // Clicked state texture

    // Button position and dimensions
    SDL_Rect rect;

    // State flags
    bool isHovered;       // True if the mouse is over the button
    bool isClickedState;  // True if the button is currently clicked

    // Callback function to execute when the button is clicked
    std::function<void()> onClick;
};

#endif // BUTTON_H
#include "Button.h"
#include <SDL_image.h>
#include <iostream>

// Constructor: Initializes the button with textures and dimensions
Button::Button(int x, int y, int w, int h, const char* normalPath, const char* hoveredPath, const char* clickedPath, SDL_Renderer* renderer)
    : rect{ x, y, w, h }, isHovered(false), isClickedState(false), onClick(nullptr) {
    SDL_Log("Button Created: x=%d, y=%d, w=%d, h=%d", x, y, w, h); // Debug log

    // Load textures for each state
    normalTexture = IMG_LoadTexture(renderer, normalPath);
    if (!normalTexture) {
        SDL_Log("Failed to load normal texture: %s", IMG_GetError());
    }

    hoveredTexture = IMG_LoadTexture(renderer, hoveredPath);
    if (!hoveredTexture) {
        SDL_Log("Failed to load hovered texture: %s", IMG_GetError());
    }

    clickedTexture = IMG_LoadTexture(renderer, clickedPath);
    if (!clickedTexture) {
        SDL_Log("Failed to load clicked texture: %s", IMG_GetError());
    }
}

// Destructor: Cleans up textures
Button::~Button() {
    if (normalTexture) SDL_DestroyTexture(normalTexture);
    if (hoveredTexture) SDL_DestroyTexture(hoveredTexture);
    if (clickedTexture) SDL_DestroyTexture(clickedTexture);
}

// Check if the button is clicked based on mouse coordinates
bool Button::isClicked(int mouseX, int mouseY) {
    bool clicked = (mouseX >= rect.x && mouseX <= rect.x + rect.w &&
        mouseY >= rect.y && mouseY <= rect.y + rect.h);
    SDL_Log("Button Clicked: %s (Mouse: %d, %d | Button: %d, %d, %d, %d)",
        clicked ? "true" : "false", mouseX, mouseY, rect.x, rect.y, rect.w, rect.h); // Debug log
    return clicked;
}

// Check if the mouse is hovering over the button
bool Button::isHovering(int mouseX, int mouseY) {
    return (mouseX >= rect.x && mouseX <= rect.x + rect.w &&
        mouseY >= rect.y && mouseY <= rect.y + rect.h);
}

// Render the button based on its current state
void Button::render(SDL_Renderer* renderer) {
    SDL_Texture* texture = normalTexture;

    // Determine which texture to render based on the button's state
    if (isClickedState && clickedTexture) {
        texture = clickedTexture;
    }
    else if (isHovered && hoveredTexture) {
        texture = hoveredTexture;
    }

    // Render the selected texture
    if (texture) {
        SDL_RenderCopy(renderer, texture, nullptr, &rect);
    }
    else {
        SDL_Log("Attempted to render a null texture for button");
    }
}

// Set a callback function to be executed when the button is clicked
void Button::setOnClick(std::function<void()> onClick) {
    this->onClick = onClick;
}

// Handle SDL events to update the button's state and execute the callback
void Button::handleEvent(SDL_Event* event) {
    if (!event) return; // Ensure the event pointer is valid

    // Handle mouse motion events
    if (event->type == SDL_MOUSEMOTION) {
        int x, y;
        SDL_GetMouseState(&x, &y);
        isHovered = isHovering(x, y); // Update hover state
    }

    // Handle mouse button down events
    else if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_LEFT) {
        int x, y;
        SDL_GetMouseState(&x, &y);
        SDL_Log("Mouse Click: x=%d, y=%d | Button: x=%d, y=%d, w=%d, h=%d", x, y, rect.x, rect.y, rect.w, rect.h); // Debug log
        if (isClicked(x, y)) {
            isClickedState = true; // Update click state
            SDL_Log("Button Clicked: Executing callback"); // Debug log
            if (onClick) {
                onClick(); // Execute the callback function
            }
        }
        else {
            SDL_Log("Button Not Clicked: Mouse outside button bounds"); // Debug log
        }
    }

    // Handle mouse button up events
    else if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_LEFT) {
        isClickedState = false; // Reset click state
    }
}

// Get the button's position and dimensions
SDL_Rect Button::getRect() const {
    return rect;
}

Well, it seems to enter some kind of “combat mode” (game->inCombat is set to true) where I can see two bars getting shorter and shorter. When one of them runs out it leaves “combat mode” and everything turns black (because gameState is still equal to PLAYING and game->update() doesn’t do anything and game->render() doesn’t draw anything when inCombat is false). The Combat class doesn’t seems to be used at all.

Mine does not go there, the screen just glitches when I click start

I noticed that you sometimes call SDL_RenderPresent multiple times in each iteration of the game loop. You should only call it once. Maybe that is the issue.

    //Main Game Loop
    while (gameState != EXIT) {
    frameStart = SDL_GetTicks();
    SDL_Event event;
    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 or OPTIONS
        if ((gameState == TUTORIAL || gameState == OPTIONS) && event.type == SDL_KEYDOWN) {
            gameState = MENU;
        }
        if (gameState == ABOUT && event.type == SDL_KEYDOWN) {
            gameState = MENU;
        }
        // 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(Game::renderer, 0, 0, 0, 255);
    SDL_RenderClear(Game::renderer); // Clear the screen at the start of each frame

    switch (gameState) {
        case MENU:
            SDL_RenderCopy(Game::renderer, menuBackdropTexture, nullptr, nullptr);
            for (int i = 0; i < NUM_BUTTONS; ++i) {
                buttons[i].render(Game::renderer);
            }
            break;
        case TUTORIAL: {
            const std::string tutorialMessage =
                "Welcome to the tutorial!\n"
                "In this game, you will embark on an epic quest to reclaim your honor. Use the mouse to interact with menus and buttons. During combat, click on dice to select them and press 'Attack' to deal damage to the enemy.\n"
                "Press any key to return to the menu.";
            renderText(Game::renderer, font, tutorialMessage, whiteColor, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            break;
        }
        case OPTIONS: {
            const std::string optionsMessage =
                "Options Menu:\n"
                "Here you can configure game settings such as audio volume, resolution, and key bindings.\n"
                "Press any key to return to the menu.";
            renderText(Game::renderer, font, optionsMessage, whiteColor, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            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 any key to return to menu";
            renderText(Game::renderer, font, aboutMessage, whiteColor, (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(Game::renderer, font, returnMessage, whiteColor, (windowWidth - 300) / 2, windowHeight - 50);
            break;
        }
        case PLAYING:
            game->handleEvents();
            game->update();
            game->render();
            break;
    }

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

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

Still the same issue, I also tried removing all SDL_Log statements in my project.

Nevermind I removed SDL_RenderPresent from Game.cpp and now it works. Thanks a lot everyone.