diff --git a/README.md b/README.md
index cab2d52..4cee6d1 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,29 @@
A classic arcade game rebuilt from the ground up using modern C++ and the Raylib library.
-**Features:**
-* Clean, Object-Oriented entity architecture.
-* Game state management (Main Menu, Gameplay, Game Over).
-* A CPU AI opponent with adjustable difficulty levels (Easy, Normal, Hard).
+---
-*This is a project developed for UTCN - ETTI Helios Additional_activity.*
\ No newline at end of file
+## Credits & Attributions
+
+### Code Base Template
+* **Original Author:** educ8s (Nick Koumaris)
+* **Original Repository:** [github.com/educ8s/Cpp-Pong-Game-Raylib](https://github.com/educ8s/Cpp-Pong-Game-Raylib)
+* **Description:** This project utilizes core architectural patterns and Raylib integrations adapted from the original open-source template repository listed above.
+
+### Asset Credits & Attribution
+The multimedia resources (graphics, sprites, and audio files) used in this game are not my personal creations. They have been curated and adapted from the open-source community for educational development.
+* **Original Project:** Moddable Pong
+* **Author / Creator:** Endless OS Foundation & Endless Studios
+* **Website:** [endlessos.org](https://endlessos.org)
+* **Source Code Repository:** [github.com/endlessm/moddable-pong](https://github.com/endlessm/moddable-pong/)
+* **Usage Notice:** These assets are integrated strictly for non-commercial, academic layout validation and development milestones. All rights, ownership, and copyrights belong entirely to the original creators at the Endless OS Foundation.
+
+---
+
+## Features
+
+* **Clean Object-Oriented Architecture:** Developed using rigorous OOP principles, utilizing a base abstract `GameObject` class with virtual override structures for specialized modular entities (`Ball`, `Paddle`, and `CpuPaddle`).
+* **Robust Game State Machine:** Features an integrated game loop managing distinct application states including `MainMenu`, `DifficultySelect`, `Multiplayer`, `Settings`, and `Playing`.
+* **Dynamic AI Opponent:** A singleplayer CPU opponent equipped with configurable movement velocities tailored across three distinct difficulty tiers: Easy, Normal, and Hard.
+* **Enhanced Visual Layout:** Fully upgraded with high-resolution texture mapping via Raylib's `DrawTexturePro`, featuring dedicated game court backdrops, localized wall segments, styled central dashed lines, and custom entity sprites.
+* **Academic Context:** This work is part of a student engineering project within the Helios Additional_activity initiative at the Technical University of Cluj-Napoca (UTCN) - Faculty of Electronics, Telecommunications and Information Technology (ETTI).
\ No newline at end of file
diff --git a/assets/textures/spaces/asteroid.png b/assets/textures/spaces/asteroid.png
deleted file mode 100644
index 2db0d29..0000000
Binary files a/assets/textures/spaces/asteroid.png and /dev/null differ
diff --git a/assets/textures/spaces/galaxy_space.png b/assets/textures/spaces/galaxy_space.png
deleted file mode 100644
index 6bec620..0000000
Binary files a/assets/textures/spaces/galaxy_space.png and /dev/null differ
diff --git a/assets/textures/spaces/trap_space.png b/assets/textures/spaces/trap_space.png
deleted file mode 100644
index 7842c0c..0000000
Binary files a/assets/textures/spaces/trap_space.png and /dev/null differ
diff --git a/assets/textures/spaces/vortex.png b/assets/textures/spaces/vortex.png
deleted file mode 100644
index 7820908..0000000
Binary files a/assets/textures/spaces/vortex.png and /dev/null differ
diff --git a/include/game.h b/include/game.h
index e1d8981..3b3c6a6 100644
--- a/include/game.h
+++ b/include/game.h
@@ -8,6 +8,8 @@
enum class GameState {
MainMenu,
DifficultySelect,
+ Multiplayer,
+ Settings,
Playing,
Paused,
GameOver
@@ -42,9 +44,22 @@ class Paddle : public GameObject {
public:
float width;
float height;
+ Texture2D texture;
- Paddle(Vector2 pos, Color c, float w, float h)
+public:
+ Paddle(Vector2 pos, Color c, float w, float h, const std::string& texturePath = "")
: GameObject(pos, c), width(w), height(h) {
+ if (!texturePath.empty()) {
+ texture = LoadTexture(texturePath.c_str());
+ } else {
+ texture = { 0 };
+ }
+ }
+
+ ~Paddle() override {
+ if (texture.id > 0) {
+ UnloadTexture(texture);
+ }
}
void Update() override;
@@ -55,9 +70,22 @@ class Ball : public GameObject {
public:
float radius;
Vector2 velocity;
+ Texture2D texture;
- Ball(Vector2 pos, Color c, float r)
+public:
+ Ball(Vector2 pos, Color c, float r, const std::string& texturePath = "")
: GameObject(pos, c), radius(r), velocity({ 5.0f, 5.0f }) {
+ if (!texturePath.empty()) {
+ texture = LoadTexture(texturePath.c_str());
+ } else {
+ texture = { 0 };
+ }
+ }
+
+ ~Ball() override {
+ if (texture.id > 0) {
+ UnloadTexture(texture);
+ }
}
void Update() override;
@@ -70,8 +98,8 @@ private:
Difficulty currentDifficulty;
public:
- CpuPaddle(Vector2 pos, Color c, float w, float h, Difficulty diff = Difficulty::Normal)
- : Paddle(pos, c, w, h) {
+ CpuPaddle(Vector2 pos, Color c, float w, float h, Difficulty diff = Difficulty::Normal, const std::string& texturePath = "")
+ : Paddle(pos, c, w, h, texturePath) {
SetDifficulty(diff);
}
@@ -100,11 +128,11 @@ public:
void Update() override {}
void LimitMovement() {
- if (position.y <= 0) {
- position.y = 0;
+ if (position.y <= 20.0f) {
+ position.y = 20.0f;
}
- if (position.y + height >= GetScreenHeight()) {
- position.y = GetScreenHeight() - height;
+ if (position.y + height >= GetScreenHeight() - 20.0f) {
+ position.y = GetScreenHeight() - 20.0f - height;
}
}
};
\ No newline at end of file
diff --git a/pong-reloaded.vcxproj b/pong-reloaded.vcxproj
index 5bfe3eb..90ac5b9 100644
--- a/pong-reloaded.vcxproj
+++ b/pong-reloaded.vcxproj
@@ -145,6 +145,14 @@
+
+
+
+
+
+
+
+
diff --git a/pong-reloaded.vcxproj.filters b/pong-reloaded.vcxproj.filters
index 03010cd..076bcbf 100644
--- a/pong-reloaded.vcxproj.filters
+++ b/pong-reloaded.vcxproj.filters
@@ -16,6 +16,12 @@
{2f179593-5e9f-4095-be57-3f12f03a9705}
+
+ {2d4ce555-d6fc-4e9c-a30e-eadb4c1336d0}
+
+
+ {5f42ff18-01e2-4266-a4e0-6dc77ba54e75}
+
@@ -45,4 +51,24 @@
Header Files
+
+
+ Resource Files\textures
+
+
+ Resource Files\textures
+
+
+ Resource Files\textures
+
+
+ Resource Files\textures
+
+
+ Resource Files\textures
+
+
+ Resource Files\textures
+
+
\ No newline at end of file
diff --git a/src/game.cpp b/src/game.cpp
index 68024d9..8515244 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -13,16 +13,27 @@ void Paddle::Update() {
}
// Limit movement
- if (position.y <= 0) {
- position.y = 0;
+ if (position.y <= 20.0f) {
+ position.y = 20.0f;
}
- if (position.y + height >= GetScreenHeight()) {
- position.y = GetScreenHeight() - height;
+ if (position.y + height >= GetScreenHeight() - 20.0f) {
+ position.y = GetScreenHeight() - 20.0f - height;
}
}
void Paddle::Draw() {
- DrawRectangleRounded(Rectangle{ position.x, position.y, width, height }, 0.8f, 0, color);
+ if (texture.id > 0) {
+ DrawTexturePro(
+ texture,
+ Rectangle{ 0.0f, 0.0f, (float)texture.width, (float)texture.height },
+ Rectangle{ position.x, position.y, width, height },
+ Vector2{ 0.0f, 0.0f },
+ 0.0f,
+ WHITE
+ );
+ } else {
+ DrawRectangleRounded(Rectangle{ position.x, position.y, width, height }, 0.8f, 0, color);
+ }
}
@@ -32,12 +43,23 @@ void Ball::Update() {
position.x += velocity.x;
position.y += velocity.y;
- if (position.y + radius >= GetScreenHeight() || position.y - radius <= 0) {
+ if (position.y + radius >= GetScreenHeight() - 20.0f || position.y - radius <= 20.0f) {
velocity.y *= -1;
}
}
void Ball::Draw() {
- // Cast to int as DrawCircle expects integers for coordinates
- DrawCircle((int)position.x, (int)position.y, radius, color);
+ if (texture.id > 0) {
+ DrawTexturePro(
+ texture,
+ Rectangle{ 0.0f, 0.0f, (float)texture.width, (float)texture.height },
+ Rectangle{ position.x - radius, position.y - radius, radius * 2.0f, radius * 2.0f },
+ Vector2{ 0.0f, 0.0f },
+ 0.0f,
+ WHITE
+ );
+ } else {
+ // Cast to int as DrawCircle expects integers for coordinates
+ DrawCircle((int)position.x, (int)position.y, radius, color);
+ }
}
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index dc67d11..97dbcd1 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -30,26 +30,39 @@ int main() {
// --- Instantiate Objects using the new Constructors ---
- Ball ball(Vector2{ screen_width / 2.0f, screen_height / 2.0f }, Yellow, 20.0f);
+ Ball ball(
+ Vector2{ screen_width / 2.0f, screen_height / 2.0f },
+ Yellow, 20.0f,
+ "assets/textures/ball/basic_ball_5.png"
+ );
ball.velocity = Vector2{ 7.0f, 7.0f };
Paddle player(
- Vector2{ screen_width - 35.0f, screen_height / 2.0f - 60.0f },
- WHITE, 25.0f, 120.0f
+ Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f },
+ WHITE, 25.0f, 120.0f,
+ "assets/textures/paddles/basic_paddle.png"
);
CpuPaddle cpu(
- Vector2{ 10.0f, screen_height / 2.0f - 60.0f },
+ Vector2{ 20.0f + 10.0f, screen_height / 2.0f - 60.0f },
WHITE, 25.0f, 120.0f,
- Difficulty::Normal
- );
+ Difficulty::Normal,
+ "assets/textures/paddles/basic_paddle_2.png"
+ );
// --- Setup Menu and Game State ---
GameState currentState = GameState::MainMenu;
- Menu mainMenu("PONG RELOADED", { "Start Game", "Quit" });
+ float sessionPlayTime = 0.0f;
+ bool isPaused = false;
+ Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" });
Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" });
+ // --- Load Background and Wall Textures ---
+ Texture2D courtBackground = LoadTexture("assets/textures/spaces/basic_space.png");
+ Texture2D wallsTexture = LoadTexture("assets/textures/spaces/walls.png");
+ Texture2D lineTexture = LoadTexture("assets/textures/hud/line.png");
+
// --- Main Game Loop ---
while (WindowShouldClose() == false && currentState != GameState::GameOver) {
@@ -63,7 +76,13 @@ int main() {
if (selected == 0) {
currentState = GameState::DifficultySelect;
}
- else if (selected == 1) {
+ if (selected == 1) {
+ currentState = GameState::Multiplayer;
+ }
+ if (selected == 2) {
+ currentState = GameState::Settings;
+ }
+ else if (selected == 3) {
currentState = GameState::GameOver;
}
mainMenu.Draw();
@@ -87,6 +106,8 @@ int main() {
// Reset game session stats
player_score = 0;
cpu_score = 0;
+ sessionPlayTime = 0.0f;
+ isPaused = false;
ResetBall(ball, screen_width, screen_height);
currentState = GameState::Playing;
}
@@ -99,38 +120,136 @@ int main() {
case GameState::Playing:
{
- ball.Update();
- player.Update();
- cpu.Update(ball.position.y);
-
- if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) {
- ball.velocity.x *= -1;
+ // Toggle pause state with 'P' key
+ if (IsKeyPressed(KEY_P)) {
+ isPaused = !isPaused;
}
- if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) {
- ball.velocity.x *= -1;
+ if (!isPaused) {
+ sessionPlayTime += GetFrameTime();
+
+ ball.Update();
+ player.Update();
+ cpu.Update(ball.position.y);
+
+ if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) {
+ ball.velocity.x *= -1;
+ }
+
+ if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) {
+ ball.velocity.x *= -1;
+ }
+
+ if (ball.position.x + ball.radius >= screen_width - 20.0f) {
+ cpu_score++;
+ ResetBall(ball, screen_width, screen_height);
+ }
+ if (ball.position.x - ball.radius <= 20.0f) {
+ player_score++;
+ ResetBall(ball, screen_width, screen_height);
+ }
}
- if (ball.position.x + ball.radius >= screen_width) {
- cpu_score++;
- ResetBall(ball, screen_width, screen_height);
- }
- if (ball.position.x - ball.radius <= 0) {
- player_score++;
- ResetBall(ball, screen_width, screen_height);
+ // --- 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, 570.0f, 760.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{ 690.0f, 20.0f, 570.0f, 760.0f },
+ Vector2{ 0.0f, 0.0f },
+ 0.0f,
+ WHITE
+ );
+
+ // Center strip (Dark Green background area) - already cleared by ClearBackground
+
+ // 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 < 780) {
+ DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE);
+ lineY += lineTexture.height;
}
- DrawRectangle(screen_width / 2, 0, screen_width / 2, screen_height, Green);
- DrawCircle(screen_width / 2, screen_height / 2, 150, Light_Green);
- DrawLine(screen_width / 2, 0, screen_width / 2, screen_height, WHITE);
+ // 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
+ );
DrawText(TextFormat("%i", cpu_score), screen_width / 4 - 20, 20, 80, WHITE);
DrawText(TextFormat("%i", 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, 715, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 });
+ DrawRectangleLines(screen_width / 2 - timeTextWidth / 2 - 15, 715, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 });
+ DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, 721, 32, YELLOW);
+
ball.Draw();
cpu.Draw();
player.Draw();
+ // Draw Pause Overlay and Text
+ if (isPaused) {
+ // Semi-transparent overlay inside the borders
+ 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);
+ }
+
break;
}
default:
@@ -140,6 +259,10 @@ int main() {
EndDrawing();
}
+ UnloadTexture(courtBackground);
+ UnloadTexture(wallsTexture);
+ UnloadTexture(lineTexture);
+
CloseWindow();
return 0;
}
\ No newline at end of file
diff --git a/src/menu.cpp b/src/menu.cpp
index 657548b..0876d78 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -30,4 +30,6 @@ void Menu::Draw() {
Color textColor = (i == selectedIndex) ? YELLOW : WHITE;
DrawText(options[i].c_str(), screenWidth / 2 - MeasureText(options[i].c_str(), 40) / 2, screenHeight / 2 + (i * 60), 40, textColor);
}
+
+ // Draw Control keys
}
\ No newline at end of file