diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..80fe239 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}\\**", + "C:\\Users\\mruna\\Downloads\\SFML_libraries\\SFML-2.5.1\\include" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "cppStandard": "gnu++14" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b04f187 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + + { + "name": "Debug SFML", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}\\bin\\space_invaders.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/bin", + "environment": [], + "externalConsole": true, + "MIMode": "gdb", + "miDebuggerPath": "C:\\MinGW\\bin\\gdb.exe" + } + ] + } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..70e34ec --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.errorSquiggles": "disabled" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..dff34af --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build SFML Project", + "type": "shell", + "command": "g++", + "args": [ + "main.cpp", + "Menu.cpp", + "Enemy.cpp", + "-o", "${workspaceFolder}\\bin\\space_invaders.exe", + "-I", "C:\\Users\\mruna\\Downloads\\SFML_libraries\\SFML-2.5.1\\include", + "-L", "C:\\Users\\mruna\\Downloads\\SFML_libraries\\SFML-2.5.1\\lib", + "-lsfml-graphics", + "-lsfml-window", + "-lsfml-system", + "-lsfml-audio", + "-mwindows", + "-static", + "-static-libgcc", + "-static-libstdc++" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": ["$gcc"], + "detail": "Compile SFML project with g++" + } + ] + } \ No newline at end of file diff --git a/Code/bin/assets/Alien.jpg b/Code/bin/assets/Alien.jpg new file mode 100644 index 0000000..c90b7ec Binary files /dev/null and b/Code/bin/assets/Alien.jpg differ diff --git a/Code/bin/assets/Missile.jpg b/Code/bin/assets/Missile.jpg new file mode 100644 index 0000000..9d43a96 Binary files /dev/null and b/Code/bin/assets/Missile.jpg differ diff --git a/Code/bin/assets/Shield1.jpg b/Code/bin/assets/Shield1.jpg new file mode 100644 index 0000000..20e6784 Binary files /dev/null and b/Code/bin/assets/Shield1.jpg differ diff --git a/Code/bin/assets/Shield2.jpg b/Code/bin/assets/Shield2.jpg new file mode 100644 index 0000000..dd10c74 Binary files /dev/null and b/Code/bin/assets/Shield2.jpg differ diff --git a/Code/bin/assets/Shield3.jpg b/Code/bin/assets/Shield3.jpg new file mode 100644 index 0000000..ba1c6a6 Binary files /dev/null and b/Code/bin/assets/Shield3.jpg differ diff --git a/Code/bin/assets/Shield4.jpg b/Code/bin/assets/Shield4.jpg new file mode 100644 index 0000000..47f982e Binary files /dev/null and b/Code/bin/assets/Shield4.jpg differ diff --git a/Code/bin/assets/Ship.jpg b/Code/bin/assets/Ship.jpg new file mode 100644 index 0000000..3309b49 Binary files /dev/null and b/Code/bin/assets/Ship.jpg differ diff --git a/Code/bin/assets/enemyMissile.jpg b/Code/bin/assets/enemyMissile.jpg new file mode 100644 index 0000000..e4c23e1 Binary files /dev/null and b/Code/bin/assets/enemyMissile.jpg differ diff --git a/Code/bin/assets/explosion.wav b/Code/bin/assets/explosion.wav new file mode 100644 index 0000000..279e97c Binary files /dev/null and b/Code/bin/assets/explosion.wav differ diff --git a/Code/bin/assets/font.ttf b/Code/bin/assets/font.ttf new file mode 100644 index 0000000..40ca6b6 Binary files /dev/null and b/Code/bin/assets/font.ttf differ diff --git a/Code/bin/assets/invaderkilled.wav b/Code/bin/assets/invaderkilled.wav new file mode 100644 index 0000000..769f817 Binary files /dev/null and b/Code/bin/assets/invaderkilled.wav differ diff --git a/Code/bin/assets/music.ogg b/Code/bin/assets/music.ogg new file mode 100644 index 0000000..f618e49 Binary files /dev/null and b/Code/bin/assets/music.ogg differ diff --git a/Code/bin/assets/shoot.wav b/Code/bin/assets/shoot.wav new file mode 100644 index 0000000..0222b03 Binary files /dev/null and b/Code/bin/assets/shoot.wav differ diff --git a/Code/bin/openal32.dll b/Code/bin/openal32.dll new file mode 100644 index 0000000..1bb27f0 Binary files /dev/null and b/Code/bin/openal32.dll differ diff --git a/Code/bin/sfml-audio-2.dll b/Code/bin/sfml-audio-2.dll new file mode 100644 index 0000000..441c0e9 Binary files /dev/null and b/Code/bin/sfml-audio-2.dll differ diff --git a/Code/bin/sfml-audio-d-2.dll b/Code/bin/sfml-audio-d-2.dll new file mode 100644 index 0000000..5b57b05 Binary files /dev/null and b/Code/bin/sfml-audio-d-2.dll differ diff --git a/Code/bin/sfml-graphics-2.dll b/Code/bin/sfml-graphics-2.dll new file mode 100644 index 0000000..cb41d4e Binary files /dev/null and b/Code/bin/sfml-graphics-2.dll differ diff --git a/Code/bin/sfml-graphics-d-2.dll b/Code/bin/sfml-graphics-d-2.dll new file mode 100644 index 0000000..3707e57 Binary files /dev/null and b/Code/bin/sfml-graphics-d-2.dll differ diff --git a/Code/bin/sfml-network-2.dll b/Code/bin/sfml-network-2.dll new file mode 100644 index 0000000..60864df Binary files /dev/null and b/Code/bin/sfml-network-2.dll differ diff --git a/Code/bin/sfml-network-d-2.dll b/Code/bin/sfml-network-d-2.dll new file mode 100644 index 0000000..364aa7c Binary files /dev/null and b/Code/bin/sfml-network-d-2.dll differ diff --git a/Code/bin/sfml-system-2.dll b/Code/bin/sfml-system-2.dll new file mode 100644 index 0000000..8e0b3b9 Binary files /dev/null and b/Code/bin/sfml-system-2.dll differ diff --git a/Code/bin/sfml-system-d-2.dll b/Code/bin/sfml-system-d-2.dll new file mode 100644 index 0000000..c82029c Binary files /dev/null and b/Code/bin/sfml-system-d-2.dll differ diff --git a/Code/bin/sfml-window-2.dll b/Code/bin/sfml-window-2.dll new file mode 100644 index 0000000..4dace88 Binary files /dev/null and b/Code/bin/sfml-window-2.dll differ diff --git a/Code/bin/sfml-window-d-2.dll b/Code/bin/sfml-window-d-2.dll new file mode 100644 index 0000000..e55f6fe Binary files /dev/null and b/Code/bin/sfml-window-d-2.dll differ diff --git a/Code/bin/space_invaders.exe b/Code/bin/space_invaders.exe new file mode 100644 index 0000000..7bb73d2 Binary files /dev/null and b/Code/bin/space_invaders.exe differ diff --git a/Code/header.hpp b/Code/header.hpp index ca5a239..1fc6e85 100644 --- a/Code/header.hpp +++ b/Code/header.hpp @@ -13,7 +13,7 @@ #include #include -#include +#include "SFML/Graphics.hpp" #include using namespace sf; @@ -21,4 +21,4 @@ using namespace sf; using std::cout; using std::endl; using std::to_string; -using std::vector; \ No newline at end of file +using std::vector; diff --git a/Code/main.cpp b/Code/main.cpp index ae4b9dc..3a9e46f 100644 --- a/Code/main.cpp +++ b/Code/main.cpp @@ -13,6 +13,7 @@ #include "Missile.hpp" #include "Menu.hpp" +bool checkCollision(const sf::Sprite& s1, const sf::Sprite& s2); void delay(int x); void ship_movement(Character& ship, RenderWindow& window); void missile_movement(vector& missiles, vector& enemyM); @@ -27,14 +28,21 @@ int main() { int level = 1, lives = 5, score = 0, tChange1 = 1, tChange2 = 1, tChange3 = 1; Clock c, c2, c3; + sf::ContextSettings settings; + settings.antialiasingLevel = 0; //Disables anti-aliasing (might fix GPU issues) + settings.attributeFlags = sf::ContextSettings::Core; + sf::RenderWindow window(sf::VideoMode(950, 950), "Space Invaders", sf::Style::Default, settings); //Size of window - RenderWindow window(VideoMode(950, 950), "Space Invaders"); //Size of window + window.setFramerateLimit(60); - Texture t1, t2, t3, t4, t5, t6, t7, t8; //Allows different textures for items + sf::Texture texture, t1, t2, t3, t4, t5, t6, t7, t8; //Allows different textures for items + if (!texture.loadFromFile("assets/Ship.png")) { + std::cerr << "Failed to load ship.png!" << std::endl; + } //In-game music Music music; - music.openFromFile("music.ogg"); + music.openFromFile("assets/music.ogg"); music.setLoop(true); music.play(); music.setVolume(10.f); @@ -43,9 +51,9 @@ int main() { SoundBuffer buffer; SoundBuffer buffer2; SoundBuffer buffer3; - buffer.loadFromFile("shoot.wav"); - buffer2.loadFromFile("invaderkilled.wav"); - buffer3.loadFromFile("explosion.wav"); + buffer.loadFromFile("assets/shoot.wav"); + buffer2.loadFromFile("assets/invaderkilled.wav"); + buffer3.loadFromFile("assets/explosion.wav"); Sound missile_sound; Sound invader_killed; @@ -61,8 +69,11 @@ int main() { //Sets up different text in the game Text scoreT, livesT, levelT, scoreEnd, levelEnd, endMessage, testCase; - Font font; - font.loadFromFile("font.ttf"); + sf::Font font; + font.loadFromFile("assets/font.ttf"); + if (!font.loadFromFile("assets/fonts/arial.ttf")) { + std::cerr << "Failed to load font!" << std::endl; + } scoreT.setFont(font); livesT.setFont(font); @@ -98,20 +109,20 @@ int main() { endMessage.setString("Press enter to continue..."); - t1.loadFromFile("Ship.jpg"); //Loads image + t1.loadFromFile("assets/Ship.jpg"); //Loads image Character* ship = new User(Vector2f(425.f, 825.f), t1, Vector2f(0.1f, 0.1f)); //Polymorphism because uer is a character Character* enemies[10][5] = { nullptr }; //Allows for matrix of enemies - t2.loadFromFile("Alien.jpg"); + t2.loadFromFile("assets/Alien.jpg"); for (int i = 0; i < 10; i++) for (int j = 0; j < 5; j++) enemies[i][j] = new Enemy(Vector2f(175.f + 60 * i, 100.f + 50 * j), t2, Vector2f(0.75f, 0.75f)); - t3.loadFromFile("Missile.jpg"); + t3.loadFromFile("assets/Missile.jpg"); vector missiles; //Keeps track of user missiles on the screen - t4.loadFromFile("enemyMissile.jpg"); + t4.loadFromFile("assets/enemyMissile.jpg"); vector enemyM; //Keeps track of enemy missiles on the screen //Helper variables for insertion into the vectors @@ -123,10 +134,10 @@ int main() { vector shieldT2; vector shieldT3; - t5.loadFromFile("Shield1.jpg"); - t6.loadFromFile("Shield2.jpg"); - t7.loadFromFile("Shield3.jpg"); - t8.loadFromFile("Shield4.jpg"); + t5.loadFromFile("assets/Shield1.jpg"); + t6.loadFromFile("assets/Shield2.jpg"); + t7.loadFromFile("assets/Shield3.jpg"); + t8.loadFromFile("assets/Shield4.jpg"); //Inserts the cycle of textures shieldT1.push_back(t5); @@ -156,9 +167,9 @@ int main() { shieldS3.setScale(Vector2f(0.5f, 0.5f)); while (window.isOpen()) { - Event event; + sf::Event event; while (window.pollEvent(event)) { - if (event.type == Event::Closed) + if (event.type == sf::Event::Closed) window.close(); } @@ -256,6 +267,11 @@ int main() { window.clear(); + sf::CircleShape testShape(50.f); //Check the texture issue + testShape.setFillColor(sf::Color::Green); + testShape.setPosition(100,100); + window.draw(testShape); + //Draw statistics window.draw(scoreT); window.draw(livesT); @@ -280,8 +296,7 @@ int main() { for (int j = 0; j < 5; j++) { for (int k = 0; k < missiles.size(); k++) { //If user missile hits a live enemy with lots of offset and hitboxing - if (!missiles.empty() && (dynamic_cast (enemies[i][j]))->isAlive() && missiles.at(k)->getPosition().y == enemies[i][j]->getPosition().y && missiles.at(k)->getPosition().x > - enemies[i][j]->getPosition().x - 20 && missiles.at(k)->getPosition().x < enemies[i][j]->getPosition().x + 20) { + if (!missiles.empty() && (dynamic_cast (enemies[i][j]))->isAlive() && checkCollision(*missiles.at(k), *enemies[i][j])) { missiles.erase(missiles.begin() + k); //Deletes that particular missile (dynamic_cast (enemies[i][j]))->kill(); invader_killed.play(); @@ -301,7 +316,7 @@ int main() { for (int i = 0; i < enemyM.size(); i++) { //If enemy missile hits ship - if (!enemyM.empty() && enemyM.at(i)->getPosition().y == ship->getPosition().y && enemyM.at(i)->getPosition().x > ship->getPosition().x - 20 && enemyM.at(i)->getPosition().x < ship->getPosition().x + 120) { + if (!enemyM.empty() && checkCollision(*enemyM.at(i), *ship)) { enemyM.erase(enemyM.begin() + i); lives--; ship_hit.play(); @@ -309,20 +324,17 @@ int main() { //If enemy missile hits one of the shields - if (!enemyM.empty() && enemyM.at(i)->getPosition().y > shieldS1.getPosition().y && enemyM.at(i)->getPosition().x > shieldS1.getPosition().x - 20 && enemyM.at(i)->getPosition().x < shieldS1.getPosition().x + 120 && - tChange1 <= 3) { + if (!enemyM.empty() && checkCollision(*enemyM.at(i), shieldS1) && tChange1 <= 3) { enemyM.erase(enemyM.begin() + i); shieldS1.setTexture(shieldT1.at(tChange1)); tChange1++; } - if (!enemyM.empty() && enemyM.at(i)->getPosition().y > shieldS2.getPosition().y && enemyM.at(i)->getPosition().x > shieldS2.getPosition().x - 20 && enemyM.at(i)->getPosition().x < shieldS2.getPosition().x + 120 && - tChange2 <= 3) { + if (!enemyM.empty() && checkCollision(*enemyM.at(i), shieldS2) && tChange2 <= 3) { enemyM.erase(enemyM.begin() + i); shieldS2.setTexture(shieldT2.at(tChange2)); tChange2++; } - if (!enemyM.empty() && enemyM.at(i)->getPosition().y > shieldS3.getPosition().y && enemyM.at(i)->getPosition().x > shieldS3.getPosition().x - 20 && enemyM.at(i)->getPosition().x < shieldS3.getPosition().x + 120 && - tChange3 <= 3) { + if (!enemyM.empty() && checkCollision(*enemyM.at(i), shieldS3) && tChange3 <= 3) { enemyM.erase(enemyM.begin() + i); shieldS3.setTexture(shieldT3.at(tChange3)); tChange3++; @@ -341,25 +353,22 @@ int main() { for (int i = 0; i < 10; i++) { for (int j = 0; j < 5; j++) { //If enemies get to y = 900 pixels, the user automatically loses - if ((dynamic_cast (enemies[i][j]))->isAlive() && (dynamic_cast (enemies[i][j]))->getPosition().y + 50 == 900) + if ((dynamic_cast (enemies[i][j]))->isAlive() && enemies[i][j]->getPosition().y + enemies[i][j]->getGlobalBounds().height >= 900) lives = 0; //If enemy hits a shield, they die and the shield takes some damage - if ((dynamic_cast (enemies[i][j]))->isAlive() && (dynamic_cast (enemies[i][j]))->getPosition().y == shieldS1.getPosition().y && (dynamic_cast (enemies[i][j]))->getPosition().x > - shieldS1.getPosition().x - 20 && (dynamic_cast (enemies[i][j]))->getPosition().x < shieldS1.getPosition().x + 120 && tChange1 <= 3) { + if ((dynamic_cast (enemies[i][j]))->isAlive() && checkCollision(*enemies[i][j], shieldS1) && tChange1 <= 3) { (dynamic_cast (enemies[i][j]))->kill(); shieldS1.setTexture(shieldT1.at(tChange1)); tChange1++; } - if ((dynamic_cast (enemies[i][j]))->isAlive() && (dynamic_cast (enemies[i][j]))->getPosition().y == shieldS2.getPosition().y && (dynamic_cast (enemies[i][j]))->getPosition().x > - shieldS2.getPosition().x - 20 && (dynamic_cast (enemies[i][j]))->getPosition().x < shieldS2.getPosition().x + 120 && tChange2 <= 3) { + if ((dynamic_cast (enemies[i][j]))->isAlive() && checkCollision(*enemies[i][j], shieldS2) && tChange2 <= 3) { (dynamic_cast (enemies[i][j]))->kill(); shieldS2.setTexture(shieldT2.at(tChange2)); tChange2++; } - if ((dynamic_cast (enemies[i][j]))->isAlive() && (dynamic_cast (enemies[i][j]))->getPosition().y == shieldS3.getPosition().y && (dynamic_cast (enemies[i][j]))->getPosition().x > - shieldS3.getPosition().x - 20 && (dynamic_cast (enemies[i][j]))->getPosition().x < shieldS3.getPosition().x + 120 && tChange3 <= 3) { + if ((dynamic_cast (enemies[i][j]))->isAlive() && checkCollision(*enemies[i][j], shieldS3) && tChange3 <= 3) { (dynamic_cast (enemies[i][j]))->kill(); shieldS3.setTexture(shieldT3.at(tChange3)); tChange3++; @@ -729,6 +738,18 @@ void ship_movement(Character& ship, RenderWindow& window) { ship.move(-0.5, 0); } +/**************************************************************** +* Function: checkCollision() * +* Description: Checks if two sprites intersect * +* Input parameters: const Sprite&, const Sprite& * +* Returns: bool * +* Preconditions:Two valid sprites * +* Postconditions: Returns true if sprites intersect * +*****************************************************************/ +bool checkCollision(const Sprite& s1, const Sprite& s2){ + return s1.getGlobalBounds().intersects(s2.getGlobalBounds()); +} + /**************************************************************** * Function: missile_movement() * * Date Created: 4/17/2021 * @@ -773,4 +794,4 @@ void draw_missiles(vector& missiles, vector& enemyM, RenderW for (int i = 0; i < enemyM.size(); i++) window.draw(*enemyM.at(i)); } -} \ No newline at end of file +} diff --git a/presentation.md b/presentation.md new file mode 100644 index 0000000..69b2100 --- /dev/null +++ b/presentation.md @@ -0,0 +1,409 @@ +# πŸš€ Space Invaders - Project 3 + +--- + +## πŸ‘Ύ Project Overview + +- **Project Name:** Space Invaders +- **Language & Framework:** C++ +- **Graphics Library:** SFML (Simple and Fast Multimedia Library) +- **What It Does:** A classic 2D arcade-style shooter game. The player controls a spaceship, shoots incoming enemies, and tries to survive as long as possible. +- **Why This Project?** + - Simple enough to understand thoroughly. + - Demonstrates fundamental game design patterns. + - Offers clean code structure with learning opportunities. + + --- + +## 🎯 Project Objectives + +- Analyze and document a functional C++ open-source game project. +- Understand SFML's use for rendering, input, audio, and game state management. +- Learn the game’s internal structure and class-based design. +- Present technical depth with structured visuals and explanations. + + --- + +## 🧠 Breadth-wise Understanding + +### File & Module Structure + +The project follows a modular file structure, organizing game components into separate scripts for better maintainability and clarity: + +- `main.cpp`: Core game loop. Manages game state. +- `Character.cpp / .h` : Provides base functionality for all game characters. +- `User.cpp / .h`: Implements player-specific behaviour. +- `enemy.cpp / .h`: Handles alien logic and movement. +- `missile.cpp / .h`: Handles missile movement, lifespan and collision. +- `Menu.cpp / .h`: Handles user menu selections. +- `header.hpp` : Centralizes all SFML includes. + + +### Game Flow Overview + +1. Initialization +- SFML Window created using `sf::RenderWindow`. +- Textures, fonts, sounds are loaded once using AssetManager. +- Entities initialized: Player, enemy grid, shields, etc. + +2. Main Game Loop +``` cpp +while (window.isOpen()) { + // Event handling + sf::Event event; + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed) + window.close(); + } + + while (indicator && no_test) + m.displayMenu(&window, score, lives, indicator, no_test); +``` +a. Event Handling +- Polls SFML events using ```window.pollEvent()```. +- Keyboard inputs manage ship movement and missile firing. + +b. State Updates +- Player updates movement based on input flags. +- All missiles move upwards each frame. +- Enemies move downwards and may respawn if destroyed or offscreen. +- Game difficulty may scale based on score or enemy count. +- Ensures enemies are removed or respawned, bullets are deleted. + +d. Rendering +- ```window.clear()``` followed by drawing all game entities. +- UI: score, level, lives drawn using ```sf::Text```. +- ```window.display()``` to update screen. + +3. Game Over Condition +If an enemy reaches the player or bottom edge of the screen, the game ends. + +### Controls and Interaction + +| Key | Action | +|----------------|-----------------------------------------| +| ← Left Arrow | Move player spaceship left | +| β†’ Right Arrow | Move player spaceship right | +| Spacebar | Fire a bullet | +| Escape (ESC) | Optional: Exit / pause (if implemented) | + + +### Gameplay Elements + +- **Player:** Controlled by user. Can move and shoot. +- **Enemies:** Appear in a 10x5 grid, descend over time. +- **Missiles:** Fired by player and enemies. +- **Shields:** Block incoming fire; take damage in 4 stages. +- **UI Elements:** Score, lives, and level shown with sf::Text. + +--- + +## πŸ” Depth-Wise Analysis + +This section explores the internal architecture and engineering choices behind the Space Invaders project. It focuses on the approaches taken during development, the data structures implemented, and key trade-offs considered by the original developer. + +### 1️⃣ Approaches Taken + +#### a. Object-Oriented Design (OOP) + +The project is structured using **object-oriented principles**. Each core game entity is represented as a C++ class encapsulating its data and behavior: + +- **`Player`**: Handles movement, screen boundaries, shooting bullets, and drawing itself. +- **`Enemy`**: Controls movement patterns, collision behavior, and respawning. +- **`Missile`**: Manages missile direction, velocity, rendering, and off-screen cleanup. + +Benefits of OOP in this context: +- **Encapsulation**: Keeps functionality modular and isolated. +- **Reusability**: Enables use of similar logic across multiple objects. +- **Extensibility**: Easy to add new features like enemies, power-ups, or effects. + +--- + +#### b. Main Game Loop (Real-Time Frame Control) + +The game follows a continuous real-time loop pattern located in main.cpp, responsible for the full lifecycle of each frame. + +Loop Responsibilities: +- RenderWindow: A 950x950 `sf::RenderWindow` that handles display and input events. +- Texture Management: Textures for the player ship, enemies, missiles, and shields are loaded + and managed using `sf::Texture` and `sf::Sprite`. +- Text Rendering: Game UI such as score, lives, and level are displayed using `sf::Text` objects +and a loaded `sf::Font` (e.g., `font.ttf`). +- Sound Management: + - `sf::SoundBuffer` for sound effects (shoot, hit, kill). + - `sf::Sound` plays effects in response to collisions and user actions. + - Optional background music via `sf::Music`. + +#### c. Event-Driven Input Handling + +Input handling is implemented using SFML's own event system with `sf::Event`. Keyboard events are captured and used to move the player ship or trigger actions like firing missiles. + +cpp +``` +sf::Event event; +while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed) + window.close(); + + if (event.type == sf::Event::KeyPressed) { + if (event.key.code == sf::Keyboard::Left) + player.moveLeft(); + if (event.key.code == sf::Keyboard::Right) + player.moveRight(); + if (event.key.code == sf::Keyboard::Space) + player.shoot(); + } +} +``` +This approach allows for: +- Immediate and responsive interaction with the user. +- Clean separation between input processing and entity behavior. +- Easily extendable to include new actions (e.g., pause menu with Escape). + +#### d. Manual Collision Detection + SFML uses `sf::FloatRect::intersects()` for collision detection, typically done using `getGlobalBounds()` from any `sf::Sprite` or `sf::Text`. +cpp +``` +if (missiles.getBounds().intersects(enemy.getBounds())) { + // Missile hit the enemy + missiles.setActive(false); + enemy.destroy(); +} +``` +Benefits: +- Lightweight: No need for a physics engine, keeping frame rates high. +- Simple: Ideal for sprite-based 2D games with rectangular shapes. +- Flexible: Can be extended with flags for health, destruction effects, etc. + + +### 2️⃣ Data Structures Used + +#### a. Classes (OOP Abstractions) +The core gameplay revolves around three main C++ classes, each encapsulating data and behavior: +- Player Class + - Attributes: + - `sf::Sprite sprite`: The visual representation of the player ship. + - `sf::Texture texture`: Image used for the player's sprite. + - `float speed`: Movement speed (typically pixels/frame). + - Methods: + - `moveLeft()` and `moveRight()`: Updates position with boundary checks + - `shoot(std::vector& missiles)`: Spawns a missile at player's location + - `draw(sf::RenderWindow&)`: Draws player to screen + +- Enemy Class + - Attributes: + - `sf::Sprite sprite`: Visual for each enemy. + - `sf::Texture texture`: Texture shared across enemy grid. + - `float speed`: Movement rate (changes on level progression). + - Methods: + - `move(Direction dir)`: Moves the enemy downward + - `draw(sf::RenderWindow&)`: Renders enemy on screen + - `respawn()`: Repositions off-screen enemy back into formation. + +- Missile Class + - Attributes: + - `sf::RectangleShape shape`: Graphical representation of missile (or could be a sprite). + - `float velocity`: Upward speed for player missiles, downward for enemies. + - `bool active`: Used to check if missile should be updated or drawn. + - Methods: + - `update()`: Moves missile each frame according to velocity. + - `isOffScreen()`: Checks if bullet has exited screen + - `draw(sf::RenderWindow&)`: Renders Missile if active +This abstraction allows: +- Better encapsulation with `sf::Sprite` & `sf::Texture`. +- Easier to debug and maintain modular code. +- Extendable for effects like animations, scaling, rotation. + +#### b. Vectors (Collections of Game Entities) + +The game uses C++ STL containers to manage dynamic lists of objects: + +```cpp +std::vector enemies; +std::vector missiles; +``` +- enemies: Stores all active enemy objects, updated and drawn every frame. +- bullets: Stores bullets fired by the player. Each bullet is updated and drawn independently. +- Inactive/expired bullets are removed after collision or going off-screen using erase-remove idiom or a boolean flag. + +Benefits of std::vector: +- Dynamic memory allocation +- Easy iteration with range-based for loops +- Fast access by index + +#### c. SFML Bounding Boxes for Collision Detection + +- Each entity (missile, enemy, ship) uses getGlobalBounds() to get its AABB for collision detection. +``` cpp + if (missiles.getBounds().intersects(enemy.getBounds())) { + // Collision response + } + ``` +- `sf::FloatRect` returned by `getGlobalBounds()` is used with `.intersects()`. +- Ideal for fast collision checks in 2D arcade games. + +Why AABB in SFML? +- Simple and efficient. +- Works well for sprite-based axis-aligned shapes. +- Can be extended to pixel-perfect if needed later. + +#### d. Assets: Images and Textures + +Instead of using raw pointers, SFML uses RAII (Resource Acquisition Is Initialization) for managing graphical and audio resources. +``` cpp +sf::Texture playerTexture; +playerTexture.loadFromFile("assets/player.png"); + +sf::Sprite playerSprite; +playerSprite.setTexture(playerTexture); +``` +- For rendering: + `window.draw(playerSprite);` +- For sounds and music + ``` cpp + sf::SoundBuffer buffer; + buffer.loadFromFile("assets/shoot.wav"); + + sf::Sound shootSound; + shootSound.setBuffer(buffer); + shootSound.play(); + ``` +- For fonts and text + ``` cpp + sf::Font font; + font.loadFromFile("assets/font.ttf"); + + sf::Text scoreText; + scoreText.setFont(font); + scoreText.setString("Score: 0"); + scoreText.setCharacterSize(24); + scoreText.setFillColor(sf::Color::White); + ``` + +#### Analysis of all the classes +1. Player + +| Element | Description | +|-------------------|--------------------------------------------------------------------------| +| Purpose | Manages the player spaceship β€” movement, firing missiles, and rendering | +| Attributes | `sf::Sprite sprite`, `sf::Texture texture`, `float speed`, `int lives` | +| Methods |`moveLeft()`, `moveRight()`, `shoot(std::vector&)`, `draw()` | +| Behavior | Moves horizontally based on user input, can fire missiles from top-center| +| Input Handling |Controlled via `sf::Keyboard::isKeyPressed` inside event handling loop | + +2. Enemy + +| Element | Description | +|-------------------|-------------------------------------------------------------------------| +| Purpose | Represents enemy invaders falling from the top | +| Attributes | `sf::Sprite sprite`, `sf::Texture texture`, `float speed`, `bool active`| +| Methods | `move(Direction dir)`, `dropDown()`, `respawn()`, `draw()` | +| Behavior | Moves down the screen at a constant speed; respawns when destroyed | +| Collision Logic | If hit by a missile, it's either reset or marked inactive | + +3. Missile + +| Element | Description | +|-------------------|------------------------------------------------------------------------| +| Purpose | Represents missiles fired by the player | +| Attributes | `sf::RectangleShape shape`, `float velocity`, `bool active` | +| Methods | `update()`, `isOffScreen()`, `draw()`, `deactivate()` | +| Behavior | Moves vertically upward and deactivates on collision or when off-screen| +| Lifecycle | Fired from the player's position and removed when no longer needed | + +4. AssetManager + +| Element | Description | +|-------------------|-----------------------------------------------------------------------| +| Purpose | Handles centralized loading and access to textures, fonts, and sounds | +| Methods | `loadTexture()`, `getTexture()`, `loadFont()`, `getSound()` | +| Behavior | Loads assets once; allows reuse across all entities | + +## πŸ“„ Header File Conventions + +Even though each class has its own `.h` and `.cpp` file, following typical SFML-based modular structure. Here’s what the includes generally look like: : + +| Header Element | Purpose | +|----------------------------------|------------------------------------------------------------------| +| `#include ` | SFML's rendering, sprites, textures, and fonts | +| `#include ` | For sound effects and background music | +| `#include ` | STL container used for missiles, enemies, etc. | +| `#include "Player.h"` | Declares Player class | +| `#include "Enemy.h"` | Declares the Enemy class | +| `#include "Missile.h"` | Declares the Bullet class | +| `#include "Shield.h"` | Declares Shield class | +| `#include "AssetManager.h"` | Singleton or namespace for loading/retrieving textures and sounds| + +#### Main Game Loop + +| Element | Description | +|--------------------------|-----------------------------------------------------------------------------| +| `sf::RenderWindow` |Creates a 950x950 game window; handles rendering and input events | +| `sf::Texture` | Loads images for ship, alien, missiles, and shields | +| `Music` & `SoundBuffer` | Loads images for ship, alien, missiles, and shields | +| `sf::Text` | Displays score, level, and status messages (Game Over, Level Up, etc.) | +| `sf::Font` | Loads "font.ttf" used to render `sf::Text` | + + + +| Entity | Description | +|---------------|-----------------------------------------------------------------------------| +| `ship` | The player-controlled ship | +| `enemies` | 10x5 grid of alien enemies | +| `missiles` | Stores player-fired missiles | +| `enemyM` | Stores enemy-fired missiles | +| `shields` | 3 shield sprites with 4-stage damage textures | + + +| Section | Description | +|---------------------------|----------------------------------------------------------------------------| +| `Menu` | Displays instructions and gets user input (Play / Test / Exit) | +| `ship_movement()` | Moves the ship left/right with A/D or arrow keys | +| `missile_movement()` | Moves user missiles up and enemy missiles down | +| `draw_missiles()` | Renders missiles to the window | +| `Enemy Movement` | Enemies move side to side, descend when hitting screen edge | +| `Collision Detection` | Handles missile-enemy, missile-shield, and missile-ship interactions | +| `Level Progression` | Increases level when all enemies are defeated | +| `Game Over` | Triggers when lives reach 0 or enemies reach the bottom | + + +| Test Case | Description | +|---------------------------|-----------------------------------------------------------------------------| +| Test 1 - Missile Deletion | Checks if missiles are correctly removed when leaving screen | +| Test 2 - Ship Hit | Verifies life loss when enemy missile hits the player | +| Test 3 - Shield Damage | Confirms shield takes damage in stages when hit | +| Test 4 - Enemy Movement | Displays and tests enemy movement across screen | +| Test 5 - Enemy Shooting | Ensures random enemies fire projectiles correctly | + +### 3️⃣ Trade-offs Made + +| Trade-off | Benefits | Limitations | +|------------------------------|-----------------------------------------|-------------------------------------------------| +| Flat structure | Easy to follow and ideal for learning | Less scalable for large games | +| Manual collision detection | Fast and sufficient for 2D games | Not precise for complex shapes | +| Hardcoded parameters | Quick prototyping | Low flexibility; harder to balance gameplay | +| No scene manager | Simplifies main loop | Can't switch between game states | +| Single-player only | Focused logic, minimal complexity | No support for multiplayer or AI behavior | +| One-direction bullet flow | Avoids logic duplication | No enemy bullets or special attacks | + +--- + +## πŸ’‘ Key Learnings + +- Understood the game loop architecture in real-world Pygame projects. +- Gained insight into class-based design for game elements. +- Learned how Pygame handles input, sound, image rendering, and FPS control. +- Improved ability to read, analyze, and explain existing code. + +--- + +## πŸ‘¨β€πŸ’» Credits + + - Original Author: Subham, Ryan, Danielle, Manjesh + - Investigated by: + - Manya Rana (202401115) + - Mrunali Parmar (202401122) + - Krishna Parmar (202401100) + - Krisha Bhuva (202401099) + +---