Add settings menu, dynamic scaling, and sound effects

This commit is contained in:
2026-05-20 19:57:36 +03:00
parent 2672a54b4c
commit f042226dcf
3 changed files with 241 additions and 38 deletions
+16 -11
View File
@@ -21,9 +21,11 @@ enum class Difficulty {
Hard Hard
}; };
constexpr float CPU_SPEED_EASY = 3.0f; constexpr float CPU_SPEED_EASY = 180.0f;
constexpr float CPU_SPEED_NORMAL = 5.5f; constexpr float CPU_SPEED_NORMAL = 330.0f;
constexpr float CPU_SPEED_HARD = 8.0f; constexpr float CPU_SPEED_HARD = 480.0f;
constexpr float PLAYER_SPEED = 360.0f;
constexpr float BALL_SPEED = 420.0f;
// --- Base Entities --- // --- Base Entities ---
@@ -34,7 +36,7 @@ public:
GameObject(Vector2 pos, Color c) : position(pos), color(c) {} GameObject(Vector2 pos, Color c) : position(pos), color(c) {}
virtual ~GameObject() = default; virtual ~GameObject() = default;
virtual void Update() = 0; virtual bool Update() = 0;
virtual void Draw() = 0; virtual void Draw() = 0;
}; };
@@ -62,7 +64,7 @@ public:
} }
} }
void Update() override; bool Update() override;
void Draw() override; void Draw() override;
}; };
@@ -88,7 +90,7 @@ public:
} }
} }
void Update() override; bool Update() override;
void Draw() override; void Draw() override;
}; };
@@ -114,18 +116,21 @@ public:
void Update(float ball_y) { void Update(float ball_y) {
float paddleCenter = position.y + (height / 2.0f); float paddleCenter = position.y + (height / 2.0f);
float dt = GetFrameTime();
float moveAmount = speed * dt;
if (paddleCenter > ball_y + speed) { if (paddleCenter > ball_y + moveAmount) {
position.y -= speed; position.y -= moveAmount;
} }
else if (paddleCenter < ball_y - speed) { else if (paddleCenter < ball_y - moveAmount) {
position.y += speed; position.y += moveAmount;
} }
LimitMovement(); LimitMovement();
position.x = 20.0f + 10.0f;
} }
void Update() override {} bool Update() override { return false; }
void LimitMovement() { void LimitMovement() {
if (position.y <= 20.0f) { if (position.y <= 20.0f) {
+15 -7
View File
@@ -2,14 +2,14 @@
// --- Paddle Implementation --- // --- Paddle Implementation ---
void Paddle::Update() { bool Paddle::Update() {
float speed = 6.0f; float dt = GetFrameTime();
if (IsKeyDown(KEY_UP)) { if (IsKeyDown(KEY_UP)) {
position.y -= speed; position.y -= PLAYER_SPEED * dt;
} }
if (IsKeyDown(KEY_DOWN)) { if (IsKeyDown(KEY_DOWN)) {
position.y += speed; position.y += PLAYER_SPEED * dt;
} }
// Limit movement // Limit movement
@@ -19,6 +19,11 @@ void Paddle::Update() {
if (position.y + height >= GetScreenHeight() - 20.0f) { if (position.y + height >= GetScreenHeight() - 20.0f) {
position.y = GetScreenHeight() - 20.0f - height; position.y = GetScreenHeight() - 20.0f - height;
} }
// Dynamic X position
position.x = GetScreenWidth() - 20.0f - 10.0f - width;
return false;
} }
void Paddle::Draw() { void Paddle::Draw() {
@@ -39,13 +44,16 @@ void Paddle::Draw() {
// --- Ball Implementation --- // --- Ball Implementation ---
void Ball::Update() { bool Ball::Update() {
position.x += velocity.x; float dt = GetFrameTime();
position.y += velocity.y; position.x += velocity.x * dt;
position.y += velocity.y * dt;
if (position.y + radius >= GetScreenHeight() - 20.0f || position.y - radius <= 20.0f) { if (position.y + radius >= GetScreenHeight() - 20.0f || position.y - radius <= 20.0f) {
velocity.y *= -1; velocity.y *= -1;
return true;
} }
return false;
} }
void Ball::Draw() { void Ball::Draw() {
+210 -20
View File
@@ -16,22 +16,54 @@ struct ScoreBoard
}; };
void ApplyResolution(int width, int height) {
int monitor = GetCurrentMonitor();
int monitorWidth = GetMonitorWidth(monitor);
int monitorHeight = GetMonitorHeight(monitor);
if (width >= monitorWidth || height >= monitorHeight) {
SetWindowState(FLAG_WINDOW_MAXIMIZED | FLAG_WINDOW_RESIZABLE);
}
else {
ClearWindowState(FLAG_WINDOW_MAXIMIZED);
SetWindowSize(width, height);
// Center the window on the active monitor
SetWindowPosition(monitorWidth / 2 - width / 2, monitorHeight / 2 - height / 2);
}
}
void ApplyFramerate(int option) {
if (option == 0) {
ClearWindowState(FLAG_VSYNC_HINT);
SetTargetFPS(60);
}
else if (option == 1) {
ClearWindowState(FLAG_VSYNC_HINT);
SetTargetFPS(144);
}
else if (option == 2) {
SetWindowState(FLAG_VSYNC_HINT);
SetTargetFPS(0);
}
}
// Helper function to reset the ball // Helper function to reset the ball
void ResetBall(Ball& ball, int screenWidth, int screenHeight) { void ResetBall(Ball& ball) {
ball.position.x = screenWidth / 2.0f; ball.position.x = GetScreenWidth() / 2.0f;
ball.position.y = screenHeight / 2.0f; ball.position.y = GetScreenHeight() / 2.0f;
int speed_choices[2] = { -1, 1 }; int speed_choices[2] = { -1, 1 };
ball.velocity.x *= speed_choices[GetRandomValue(0, 1)]; ball.velocity.x = BALL_SPEED * speed_choices[GetRandomValue(0, 1)];
ball.velocity.y *= speed_choices[GetRandomValue(0, 1)]; ball.velocity.y = BALL_SPEED * speed_choices[GetRandomValue(0, 1)];
} }
int main() { int main() {
std::cout << "Starting the game" << std::endl; std::cout << "Starting the game" << std::endl;
const int screen_width = 1280; int screen_width = 1280;
const int screen_height = 800; int screen_height = 800;
InitWindow(screen_width, screen_height, "Pong Reloaded"); InitWindow(screen_width, screen_height, "Pong Reloaded");
SetTargetFPS(60); SetTargetFPS(60);
InitAudioDevice();
// --- Instantiate Objects using the new Constructors --- // --- Instantiate Objects using the new Constructors ---
@@ -40,7 +72,7 @@ int main() {
Yellow, 20.0f, Yellow, 20.0f,
"assets/textures/ball/basic_ball_5.png" "assets/textures/ball/basic_ball_5.png"
); );
ball.velocity = Vector2{ 7.0f, 7.0f }; ball.velocity = Vector2{ BALL_SPEED, BALL_SPEED };
Paddle player( Paddle player(
Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f }, Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f },
@@ -61,6 +93,15 @@ int main() {
float sessionPlayTime = 0.0f; float sessionPlayTime = 0.0f;
bool isPaused = false; bool isPaused = false;
bool shouldQuit = false; bool shouldQuit = false;
int resolutionOption = 0; // 0 = 1280x800, 1 = 1600x900, 2 = 1920x1080
int framerateOption = 0; // 0 = 60 FPS, 1 = 144 FPS, 2 = VSync
bool isFullscreen = false;
int maxScoreOption = 0; // 0 = 5, 1 = 11, 2 = 15, 3 = 21
int maxScore = 5;
bool sfxEnabled = true;
int selectedSettingLine = 0; // 0 = Resolution, 1 = Framerate, 2 = Screen Mode, 3 = Score Limit, 4 = Sound, 5 = Back
Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" }); Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" });
Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" }); Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" });
ScoreBoard score; ScoreBoard score;
@@ -70,9 +111,17 @@ int main() {
Texture2D wallsTexture = LoadTexture("assets/textures/spaces/walls.png"); Texture2D wallsTexture = LoadTexture("assets/textures/spaces/walls.png");
Texture2D lineTexture = LoadTexture("assets/textures/hud/line.png"); Texture2D lineTexture = LoadTexture("assets/textures/hud/line.png");
// --- Load Sound Effects ---
Sound paddleHitSound = LoadSound("assets/audio/paddle_hit.ogg");
Sound wallHitSound = LoadSound("assets/audio/wall_hit.ogg");
Sound scoreSound = LoadSound("assets/audio/score.ogg");
// --- Main Game Loop --- // --- Main Game Loop ---
while (WindowShouldClose() == false && !shouldQuit) { while (WindowShouldClose() == false && !shouldQuit) {
screen_width = GetScreenWidth();
screen_height = GetScreenHeight();
BeginDrawing(); BeginDrawing();
ClearBackground(Dark_Green); ClearBackground(Dark_Green);
@@ -115,7 +164,7 @@ int main() {
score.cpu_score = 0; score.cpu_score = 0;
sessionPlayTime = 0.0f; sessionPlayTime = 0.0f;
isPaused = false; isPaused = false;
ResetBall(ball, screen_width, screen_height); ResetBall(ball);
currentState = GameState::Playing; currentState = GameState::Playing;
} }
else if (selected == 3) { else if (selected == 3) {
@@ -125,6 +174,135 @@ int main() {
break; break;
} }
case GameState::Settings:
{
// Navigation
if (IsKeyPressed(KEY_UP)) {
selectedSettingLine--;
if (selectedSettingLine < 0) selectedSettingLine = 5;
}
if (IsKeyPressed(KEY_DOWN)) {
selectedSettingLine++;
if (selectedSettingLine > 5) selectedSettingLine = 0;
}
// Option selection (LEFT/RIGHT or ENTER)
if (selectedSettingLine == 0) {
// Resolution
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
resolutionOption = (resolutionOption + 1) % 3;
if (resolutionOption == 0) ApplyResolution(1280, 800);
else if (resolutionOption == 1) ApplyResolution(1600, 900);
else if (resolutionOption == 2) ApplyResolution(1920, 1080);
}
else if (IsKeyPressed(KEY_LEFT)) {
resolutionOption = (resolutionOption - 1 + 3) % 3;
if (resolutionOption == 0) ApplyResolution(1280, 800);
else if (resolutionOption == 1) ApplyResolution(1600, 900);
else if (resolutionOption == 2) ApplyResolution(1920, 1080);
}
}
else if (selectedSettingLine == 1) {
// Framerate
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
framerateOption = (framerateOption + 1) % 3;
ApplyFramerate(framerateOption);
}
else if (IsKeyPressed(KEY_LEFT)) {
framerateOption = (framerateOption - 1 + 3) % 3;
ApplyFramerate(framerateOption);
}
}
else if (selectedSettingLine == 2) {
// Screen Mode
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_ENTER)) {
ToggleFullscreen();
isFullscreen = !isFullscreen;
}
}
else if (selectedSettingLine == 3) {
// Score Limit
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
maxScoreOption = (maxScoreOption + 1) % 4;
}
else if (IsKeyPressed(KEY_LEFT)) {
maxScoreOption = (maxScoreOption - 1 + 4) % 4;
}
if (maxScoreOption == 0) maxScore = 5;
else if (maxScoreOption == 1) maxScore = 11;
else if (maxScoreOption == 2) maxScore = 15;
else if (maxScoreOption == 3) maxScore = 21;
}
else if (selectedSettingLine == 4) {
// Sound Effects
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_ENTER)) {
sfxEnabled = !sfxEnabled;
}
}
else if (selectedSettingLine == 5) {
// Back
if (IsKeyPressed(KEY_ENTER)) {
currentState = GameState::MainMenu;
}
}
// Draw Settings Menu
int sw = GetScreenWidth();
int sh = GetScreenHeight();
// Title
int titleWidth = MeasureText("SETTINGS", 60);
DrawText("SETTINGS", sw / 2 - titleWidth / 2, sh / 4 - 40, 60, WHITE);
// Construct resolution string
std::string resStr = "Resolution: ";
if (resolutionOption == 0) resStr += "1280x800";
else if (resolutionOption == 1) resStr += "1600x900";
else if (resolutionOption == 2) resStr += "1920x1080 (Windowed)";
// Construct framerate string
std::string fpsStr = "Framerate: ";
if (framerateOption == 0) fpsStr += "60 FPS";
else if (framerateOption == 1) fpsStr += "144 FPS";
else if (framerateOption == 2) fpsStr += "VSync";
// Construct screen mode string
std::string modeStr = "Screen Mode: ";
if (isFullscreen) modeStr += "Fullscreen";
else modeStr += "Windowed";
// Construct score limit string
std::string scoreLimitStr = "Score Limit: " + std::to_string(maxScore);
// Construct sound effects string
std::string sfxStr = "Sound Effects: ";
if (sfxEnabled) sfxStr += "ON";
else sfxStr += "OFF";
// Draw option lines
Color resColor = (selectedSettingLine == 0) ? YELLOW : WHITE;
Color fpsColor = (selectedSettingLine == 1) ? YELLOW : WHITE;
Color modeColor = (selectedSettingLine == 2) ? YELLOW : WHITE;
Color scoreColor = (selectedSettingLine == 3) ? YELLOW : WHITE;
Color sfxColor = (selectedSettingLine == 4) ? YELLOW : WHITE;
Color backColor = (selectedSettingLine == 5) ? YELLOW : WHITE;
int startY = sh / 2 - 90;
DrawText(resStr.c_str(), sw / 2 - MeasureText(resStr.c_str(), 30) / 2, startY, 30, resColor);
DrawText(fpsStr.c_str(), sw / 2 - MeasureText(fpsStr.c_str(), 30) / 2, startY + 45, 30, fpsColor);
DrawText(modeStr.c_str(), sw / 2 - MeasureText(modeStr.c_str(), 30) / 2, startY + 90, 30, modeColor);
DrawText(scoreLimitStr.c_str(), sw / 2 - MeasureText(scoreLimitStr.c_str(), 30) / 2, startY + 135, 30, scoreColor);
DrawText(sfxStr.c_str(), sw / 2 - MeasureText(sfxStr.c_str(), 30) / 2, startY + 180, 30, sfxColor);
DrawText("Back", sw / 2 - MeasureText("Back", 30) / 2, startY + 225, 30, backColor);
// Controls helper
int helperWidth = MeasureText("Use UP/DOWN to navigate, LEFT/RIGHT to change settings, ENTER to select", 20);
DrawText("Use UP/DOWN to navigate, LEFT/RIGHT to change settings, ENTER to select", sw / 2 - helperWidth / 2, sh - 60, 20, GRAY);
break;
}
case GameState::Playing: case GameState::Playing:
{ {
// Toggle pause state with 'P' key // Toggle pause state with 'P' key
@@ -140,34 +318,40 @@ int main() {
if (!isPaused) { if (!isPaused) {
sessionPlayTime += GetFrameTime(); sessionPlayTime += GetFrameTime();
ball.Update(); if (ball.Update() && sfxEnabled) {
PlaySound(wallHitSound);
}
player.Update(); player.Update();
cpu.Update(ball.position.y); cpu.Update(ball.position.y);
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) { if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) {
ball.velocity.x *= -1; ball.velocity.x *= -1;
if (sfxEnabled) PlaySound(paddleHitSound);
} }
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) { if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) {
ball.velocity.x *= -1; ball.velocity.x *= -1;
if (sfxEnabled) PlaySound(paddleHitSound);
} }
if (ball.position.x + ball.radius >= screen_width - 20.0f) { if (ball.position.x + ball.radius >= screen_width - 20.0f) {
score.cpu_score++; score.cpu_score++;
if (score.cpu_score >= 5) { if (sfxEnabled) PlaySound(scoreSound);
if (score.cpu_score >= maxScore) {
currentState = GameState::GameOver; currentState = GameState::GameOver;
} }
else { else {
ResetBall(ball, screen_width, screen_height); ResetBall(ball);
} }
} }
if (ball.position.x - ball.radius <= 20.0f) { if (ball.position.x - ball.radius <= 20.0f) {
score.player_score++; score.player_score++;
if (score.player_score >= 5) { if (sfxEnabled) PlaySound(scoreSound);
if (score.player_score >= maxScore) {
currentState = GameState::GameOver; currentState = GameState::GameOver;
} }
else { else {
ResetBall(ball, screen_width, screen_height); ResetBall(ball);
} }
} }
} }
@@ -177,7 +361,7 @@ int main() {
DrawTexturePro( DrawTexturePro(
courtBackground, courtBackground,
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height }, Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
Rectangle{ 20.0f, 20.0f, 570.0f, 760.0f }, Rectangle{ 20.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
Vector2{ 0.0f, 0.0f }, Vector2{ 0.0f, 0.0f },
0.0f, 0.0f,
WHITE WHITE
@@ -187,7 +371,7 @@ int main() {
DrawTexturePro( DrawTexturePro(
courtBackground, courtBackground,
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height }, Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
Rectangle{ 690.0f, 20.0f, 570.0f, 760.0f }, Rectangle{ (float)screen_width / 2.0f + 50.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
Vector2{ 0.0f, 0.0f }, Vector2{ 0.0f, 0.0f },
0.0f, 0.0f,
WHITE WHITE
@@ -201,7 +385,7 @@ int main() {
// Tiled Center Dashed Line (line.png) // Tiled Center Dashed Line (line.png)
int lineY = 20; int lineY = 20;
while (lineY < 780) { while (lineY < screen_height - 20) {
DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE); DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE);
lineY += lineTexture.height; lineY += lineTexture.height;
} }
@@ -255,9 +439,9 @@ int main() {
int timeTextWidth = MeasureText(TextFormat("%02i:%02i", minutes, seconds), 32); int timeTextWidth = MeasureText(TextFormat("%02i:%02i", minutes, seconds), 32);
// Draw background box to block out the center line and increase contrast // Draw background box to block out the center line and increase contrast
DrawRectangle(screen_width / 2 - timeTextWidth / 2 - 15, 715, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 }); DrawRectangle(screen_width / 2 - timeTextWidth / 2 - 15, screen_height - 85, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 });
DrawRectangleLines(screen_width / 2 - timeTextWidth / 2 - 15, 715, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 }); DrawRectangleLines(screen_width / 2 - timeTextWidth / 2 - 15, screen_height - 85, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 });
DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, 721, 32, YELLOW); DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, screen_height - 79, 32, YELLOW);
ball.Draw(); ball.Draw();
cpu.Draw(); cpu.Draw();
@@ -342,6 +526,12 @@ int main() {
UnloadTexture(wallsTexture); UnloadTexture(wallsTexture);
UnloadTexture(lineTexture); UnloadTexture(lineTexture);
UnloadSound(paddleHitSound);
UnloadSound(wallHitSound);
UnloadSound(scoreSound);
CloseAudioDevice();
CloseWindow(); CloseWindow();
return 0; return 0;
} }