Add multiplayer lobby and gameplay enhancements

This commit is contained in:
2026-05-21 00:40:54 +03:00
parent f042226dcf
commit fad1ed6496
2 changed files with 394 additions and 29 deletions
+1
View File
@@ -8,6 +8,7 @@
enum class GameState {
MainMenu,
DifficultySelect,
MultiplayerLobby,
Multiplayer,
Settings,
Playing,
+387 -23
View File
@@ -93,6 +93,9 @@ int main() {
float sessionPlayTime = 0.0f;
bool isPaused = false;
bool shouldQuit = false;
bool isMultiplayer = false;
bool p1Ready = false;
bool p2Ready = false;
int resolutionOption = 0; // 0 = 1280x800, 1 = 1600x900, 2 = 1920x1080
int framerateOption = 0; // 0 = 60 FPS, 1 = 144 FPS, 2 = VSync
@@ -130,10 +133,20 @@ int main() {
{
int selected = mainMenu.Update();
if (selected == 0) {
isMultiplayer = false;
currentState = GameState::DifficultySelect;
}
else if (selected == 1) {
currentState = GameState::Multiplayer;
isMultiplayer = true;
score.player_score = 0;
score.player2_score = 0;
score.cpu_score = 0;
sessionPlayTime = 0.0f;
isPaused = false;
p1Ready = false;
p2Ready = false;
ResetBall(ball);
currentState = GameState::MultiplayerLobby;
}
else if (selected == 2) {
currentState = GameState::Settings;
@@ -142,6 +155,12 @@ int main() {
shouldQuit = true;
}
mainMenu.Draw();
// Controls helper
int menuHintWidth = MeasureText("Use UP/DOWN to navigate | ENTER to select", 20);
DrawText("Use UP/DOWN to navigate | ENTER to select",
screen_width / 2 - menuHintWidth / 2,
screen_height - 80, 20, WHITE);
break;
}
@@ -161,6 +180,7 @@ int main() {
// Reset game session stats
score.player_score = 0;
score.player2_score = 0;
score.cpu_score = 0;
sessionPlayTime = 0.0f;
isPaused = false;
@@ -171,6 +191,332 @@ int main() {
currentState = GameState::MainMenu;
}
difficultyMenu.Draw();
// Controls helper
int gameHintWidth = MeasureText("Game: UP/DOWN Arrows to move | P to Pause | SPACE to Force Game Over", 20);
DrawText("Player: UP/DOWN Arrows to move | P to Pause | SPACE to Force Game Over",
screen_width / 2 - gameHintWidth / 2,
screen_height - 50, 20, WHITE);
break;
}
case GameState::MultiplayerLobby:
{
// Input checks
if (IsKeyPressed(KEY_W)) {
p1Ready = !p1Ready;
if (sfxEnabled) PlaySound(paddleHitSound);
}
if (IsKeyPressed(KEY_UP)) {
p2Ready = !p2Ready;
if (sfxEnabled) PlaySound(paddleHitSound);
}
if (IsKeyPressed(KEY_B)) {
currentState = GameState::MainMenu;
}
if (p1Ready && p2Ready) {
currentState = GameState::Multiplayer;
}
// --- Draw Court Background Behind Panel ---
// Left Court (basic_space.png)
DrawTexturePro(
courtBackground,
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
Rectangle{ 20.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Right Court (basic_space.png)
DrawTexturePro(
courtBackground,
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
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 },
0.0f,
WHITE
);
// Center Circle
DrawCircle(screen_width / 2, screen_height / 2, 50.0f, Color{ 102, 51, 153, 100 });
DrawCircleLines(screen_width / 2, screen_height / 2, 50.0f, Color{ 50, 25, 75, 250 });
// Tiled Center Dashed Line (line.png)
int lineY = 20;
while (lineY < screen_height - 20) {
DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE);
lineY += lineTexture.height;
}
// Top Wall (walls.png)
DrawTexturePro(
wallsTexture,
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
Rectangle{ 0.0f, 0.0f, (float)screen_width, 20.0f },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Bottom Wall (walls.png)
DrawTexturePro(
wallsTexture,
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
Rectangle{ 0.0f, (float)screen_height - 20.0f, (float)screen_width, 20.0f },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Left Wall (walls.png)
DrawTexturePro(
wallsTexture,
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
Rectangle{ 0.0f, 0.0f, 20.0f, (float)screen_height },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Right Wall (walls.png)
DrawTexturePro(
wallsTexture,
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
Rectangle{ (float)screen_width - 20.0f, 0.0f, 20.0f, (float)screen_height },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Draw Lobby Panel
int panelWidth = 800;
int panelHeight = 450;
int panelX = screen_width / 2 - panelWidth / 2;
int panelY = screen_height / 2 - panelHeight / 2;
// Semi-transparent background panel
DrawRectangle(panelX, panelY, panelWidth, panelHeight, Color{ 15, 15, 15, 230 });
DrawRectangleLines(panelX, panelY, panelWidth, panelHeight, Color{ 120, 120, 120, 255 });
// Title
int titleWidth = MeasureText("MULTIPLAYER LOBBY", 40);
DrawText("MULTIPLAYER LOBBY", screen_width / 2 - titleWidth / 2, panelY + 30, 40, WHITE);
// Player 1 Details (Left Paddle controlled by W/S keys)
int p1X = panelX + 50;
int p1Y = panelY + 110;
DrawText("PLAYER 1 (LEFT)", p1X, p1Y, 24, LIGHTGRAY);
DrawText("Controls: W / S", p1X, p1Y + 45, 20, WHITE);
DrawText("Press 'W' to toggle ready", p1X, p1Y + 75, 18, GRAY);
if (p1Ready) {
DrawRectangle(p1X, p1Y + 115, 220, 50, GREEN);
DrawText("READY", p1X + 110 - MeasureText("READY", 20)/2, p1Y + 130, 20, BLACK);
} else {
DrawRectangle(p1X, p1Y + 115, 220, 50, RED);
DrawText("NOT READY", p1X + 110 - MeasureText("NOT READY", 20)/2, p1Y + 130, 20, WHITE);
}
// Player 2 Details (Right Paddle controlled by UP/DOWN arrow keys)
int p2X = panelX + panelWidth - 270;
int p2Y = panelY + 110;
DrawText("PLAYER 2 (RIGHT)", p2X, p2Y, 24, LIGHTGRAY);
DrawText("Controls: UP / DOWN", p2X, p2Y + 45, 20, WHITE);
DrawText("Press 'UP' to toggle ready", p2X, p2Y + 75, 18, GRAY);
if (p2Ready) {
DrawRectangle(p2X, p2Y + 115, 220, 50, GREEN);
DrawText("READY", p2X + 110 - MeasureText("READY", 20)/2, p2Y + 130, 20, BLACK);
} else {
DrawRectangle(p2X, p2Y + 115, 220, 50, RED);
DrawText("NOT READY", p2X + 110 - MeasureText("NOT READY", 20)/2, p2Y + 130, 20, WHITE);
}
// Lobby Status Message
if (p1Ready && p2Ready) {
int startTextWidth = MeasureText("BOTH READY! STARTING GAME...", 24);
DrawText("BOTH READY! STARTING GAME...", screen_width / 2 - startTextWidth / 2, panelY + 310, 24, YELLOW);
} else {
int waitTextWidth = MeasureText("Waiting for both players to be ready...", 20);
DrawText("Waiting for both players to be ready...", screen_width / 2 - waitTextWidth / 2, panelY + 315, 20, LIGHTGRAY);
}
// Back Instruction
int backTextWidth = MeasureText("Press 'B' to return to Main Menu | P: Pause | SPACE: Force Game Over", 20);
DrawText("Press 'B' to return to Main Menu | P: Pause | SPACE: Force Game Over", screen_width / 2 - backTextWidth / 2, panelY + 385, 20, YELLOW);
break;
}
case GameState::Multiplayer:
{
// Toggle pause state with 'P' key
if (IsKeyPressed(KEY_P)) {
isPaused = !isPaused;
}
// Force GameOver with SPACEBAR key
if (IsKeyPressed(KEY_SPACE)) {
currentState = GameState::GameOver;
}
if (!isPaused) {
sessionPlayTime += GetFrameTime();
if (ball.Update() && sfxEnabled) {
PlaySound(wallHitSound);
}
player.Update();
// Player 2 controls (Left Paddle - Player 2 paddle)
float dt = GetFrameTime();
if (IsKeyDown(KEY_W)) {
cpu.position.y -= PLAYER_SPEED * dt;
}
if (IsKeyDown(KEY_S)) {
cpu.position.y += PLAYER_SPEED * dt;
}
cpu.LimitMovement();
cpu.position.x = 20.0f + 10.0f; // Keep on left track
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) {
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 })) {
ball.velocity.x *= -1;
if (sfxEnabled) PlaySound(paddleHitSound);
}
if (ball.position.x + ball.radius >= screen_width - 20.0f) {
score.player2_score++;
if (sfxEnabled) PlaySound(scoreSound);
if (score.player2_score >= maxScore) {
currentState = GameState::GameOver;
}
else {
ResetBall(ball);
}
}
if (ball.position.x - ball.radius <= 20.0f) {
score.player_score++;
if (sfxEnabled) PlaySound(scoreSound);
if (score.player_score >= maxScore) {
currentState = GameState::GameOver;
}
else {
ResetBall(ball);
}
}
}
// --- Draw Textured Court Background ---
// Left Court (basic_space.png)
DrawTexturePro(
courtBackground,
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
Rectangle{ 20.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Right Court (basic_space.png)
DrawTexturePro(
courtBackground,
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
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 },
0.0f,
WHITE
);
// Center Circle
DrawCircle(screen_width / 2, screen_height / 2, 50.0f, Color{ 102, 51, 153, 100 });
DrawCircleLines(screen_width / 2, screen_height / 2, 50.0f, Color{ 50, 25, 75, 250 });
// Tiled Center Dashed Line (line.png)
int lineY = 20;
while (lineY < screen_height - 20) {
DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE);
lineY += lineTexture.height;
}
// Top Wall (walls.png)
DrawTexturePro(
wallsTexture,
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
Rectangle{ 0.0f, 0.0f, (float)screen_width, 20.0f },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Bottom Wall (walls.png)
DrawTexturePro(
wallsTexture,
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
Rectangle{ 0.0f, (float)screen_height - 20.0f, (float)screen_width, 20.0f },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Left Wall (walls.png)
DrawTexturePro(
wallsTexture,
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
Rectangle{ 0.0f, 0.0f, 20.0f, (float)screen_height },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Right Wall (walls.png)
DrawTexturePro(
wallsTexture,
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
Rectangle{ (float)screen_width - 20.0f, 0.0f, 20.0f, (float)screen_height },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
// Scores: Left shows Player 2 score, Right shows Player 1 score
DrawText(TextFormat("%i", score.player2_score), screen_width / 4 - 20, 20, 80, WHITE);
DrawText(TextFormat("%i", score.player_score), 3 * screen_width / 4 - 20, 20, 80, WHITE);
// Draw Playtime Counter
int minutes = (int)sessionPlayTime / 60;
int seconds = (int)sessionPlayTime % 60;
int timeTextWidth = MeasureText(TextFormat("%02i:%02i", minutes, seconds), 32);
// Draw background box to block out the center line and increase contrast
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, screen_height - 85, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 });
DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, screen_height - 79, 32, YELLOW);
ball.Draw();
cpu.Draw(); // Left paddle (reused cpu object)
player.Draw(); // Right paddle
// Draw Pause Overlay and Text
if (isPaused) {
DrawRectangle(20, 20, screen_width - 40, screen_height - 40, Color{ 0, 0, 0, 150 });
int pausedTextWidth = MeasureText("PAUSED", 60);
DrawText("PAUSED", screen_width / 2 - pausedTextWidth / 2, screen_height / 2 - 60, 60, YELLOW);
int hintTextWidth = MeasureText("P1 (Right): UP/DOWN | P2 (Left): W/S | SPACE: Force Game Over", 20);
DrawText("P1 (Right): UP/DOWN | P2 (Left): W/S | SPACE: Force Game Over",
screen_width / 2 - hintTextWidth / 2, screen_height / 2 + 20, 20, WHITE);
}
break;
}
@@ -247,13 +593,9 @@ int main() {
}
}
// 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);
DrawText("SETTINGS", screen_width / 2 - titleWidth / 2, screen_height / 4 - 40, 60, WHITE);
// Construct resolution string
std::string resStr = "Resolution: ";
@@ -288,17 +630,19 @@ int main() {
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);
int startY = screen_height / 2 - 90;
DrawText(resStr.c_str(), screen_width / 2 - MeasureText(resStr.c_str(), 30) / 2, startY, 30, resColor);
DrawText(fpsStr.c_str(), screen_width / 2 - MeasureText(fpsStr.c_str(), 30) / 2, startY + 45, 30, fpsColor);
DrawText(modeStr.c_str(), screen_width / 2 - MeasureText(modeStr.c_str(), 30) / 2, startY + 90, 30, modeColor);
DrawText(scoreLimitStr.c_str(), screen_width / 2 - MeasureText(scoreLimitStr.c_str(), 30) / 2, startY + 135, 30, scoreColor);
DrawText(sfxStr.c_str(), screen_width / 2 - MeasureText(sfxStr.c_str(), 30) / 2, startY + 180, 30, sfxColor);
DrawText("Back", screen_width / 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);
int settingsHintWidth = MeasureText("UP/DOWN to navigate | LEFT/RIGHT to change settings | ENTER to select", 20);
DrawText("UP/DOWN to navigate | LEFT/RIGHT to change settings | ENTER to select",
screen_width / 2 - settingsHintWidth / 2,
screen_height - 50, 20, WHITE);
break;
}
@@ -453,7 +797,11 @@ int main() {
DrawRectangle(20, 20, screen_width - 40, screen_height - 40, Color{ 0, 0, 0, 150 });
int pausedTextWidth = MeasureText("PAUSED", 60);
DrawText("PAUSED", screen_width / 2 - pausedTextWidth / 2, screen_height / 2 - 30, 60, YELLOW);
DrawText("PAUSED", screen_width / 2 - pausedTextWidth / 2, screen_height / 2 - 60, 60, YELLOW);
int hintTextWidth = MeasureText("UP/DOWN Arrows to move | SPACE: Force Game Over", 20);
DrawText("UP/DOWN Arrows to move | SPACE: Force Game Over",
screen_width / 2 - hintTextWidth / 2, screen_height / 2 + 20, 20, WHITE);
}
break;
@@ -481,6 +829,21 @@ int main() {
// Determine winner text and color
std::string winnerText = "GAME ENDED";
Color winnerColor = YELLOW;
std::string score1Str, score2Str;
if (isMultiplayer) {
if (score.player_score > score.player2_score) {
winnerText = "PLAYER 1 WINS!";
winnerColor = Green;
}
else if (score.player2_score > score.player_score) {
winnerText = "PLAYER 2 WINS!";
winnerColor = RED;
}
score1Str = "Player 1 Score: " + std::to_string(score.player_score);
score2Str = "Player 2 Score: " + std::to_string(score.player2_score);
}
else {
if (score.player_score > score.cpu_score) {
winnerText = "YOU WIN!";
winnerColor = Green;
@@ -489,18 +852,19 @@ int main() {
winnerText = "CPU WINS!";
winnerColor = RED;
}
score1Str = "Player Score: " + std::to_string(score.player_score);
score2Str = "CPU Score: " + std::to_string(score.cpu_score);
}
int winnerWidth = MeasureText(winnerText.c_str(), 40);
DrawText(winnerText.c_str(), screen_width / 2 - winnerWidth / 2, panelY + 120, 40, winnerColor);
// Draw final scores
std::string playerScoreStr = "Player Score: " + std::to_string(score.player_score);
std::string cpuScoreStr = "CPU Score: " + std::to_string(score.cpu_score);
int playerTextWidth = MeasureText(score1Str.c_str(), 30);
int cpuTextWidth = MeasureText(score2Str.c_str(), 30);
int playerTextWidth = MeasureText(playerScoreStr.c_str(), 30);
int cpuTextWidth = MeasureText(cpuScoreStr.c_str(), 30);
DrawText(playerScoreStr.c_str(), screen_width / 2 - playerTextWidth / 2, panelY + 200, 30, WHITE);
DrawText(cpuScoreStr.c_str(), screen_width / 2 - cpuTextWidth / 2, panelY + 250, 30, WHITE);
DrawText(score1Str.c_str(), screen_width / 2 - playerTextWidth / 2, panelY + 200, 30, WHITE);
DrawText(score2Str.c_str(), screen_width / 2 - cpuTextWidth / 2, panelY + 250, 30, WHITE);
// Draw playtime
int minutes = (int)sessionPlayTime / 60;