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;
}