diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index bedba23..c978a14 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -26,6 +26,7 @@ #include "core/defaults/playfield.hpp" #include "core/defaults/rendering.hpp" #include "core/defaults/ship.hpp" +#include "core/defaults/starfield_parallax.hpp" #include "core/defaults/title.hpp" #include "core/defaults/trail.hpp" #include "core/defaults/window.hpp" diff --git a/source/core/defaults/starfield_parallax.hpp b/source/core/defaults/starfield_parallax.hpp new file mode 100644 index 0000000..76fca6b --- /dev/null +++ b/source/core/defaults/starfield_parallax.hpp @@ -0,0 +1,36 @@ +// starfield_parallax.hpp - Capa de fons del playfield: estrelles 2D amb parallax +// © 2026 JailDesigner +// +// 3 capes de profunditat. Cada capa té estrelles amb brillantor, mida i +// factor parallax propis. Les més properes són més brillants i grans i es +// mouen més ràpid quan el món es desplaça; les més llunyanes són tènues i +// petites i amb prou feines es mouen. + +#pragma once + +namespace Defaults::StarfieldParallax { + + namespace Far { + constexpr int COUNT = 60; + constexpr float BRIGHTNESS = 0.15F; + constexpr float PARALLAX_FACTOR = 0.15F; // multiplicador sobre world_velocity + constexpr int SIZE_PX = 1; // 1 px (punt) + } // namespace Far + + namespace Mid { + constexpr int COUNT = 50; + constexpr float BRIGHTNESS = 0.30F; + constexpr float PARALLAX_FACTOR = 0.35F; + constexpr int SIZE_PX = 2; // creu de 3x3 (extensió ±1) + } // namespace Mid + + namespace Near { + constexpr int COUNT = 40; + constexpr float BRIGHTNESS = 0.55F; + constexpr float PARALLAX_FACTOR = 0.70F; + constexpr int SIZE_PX = 3; // creu de 5x5 (extensió ±2) + } // namespace Near + + constexpr int TOTAL_COUNT = Far::COUNT + Mid::COUNT + Near::COUNT; + +} // namespace Defaults::StarfieldParallax diff --git a/source/core/graphics/starfield_parallax.cpp b/source/core/graphics/starfield_parallax.cpp new file mode 100644 index 0000000..32b4da3 --- /dev/null +++ b/source/core/graphics/starfield_parallax.cpp @@ -0,0 +1,145 @@ +// starfield_parallax.cpp - Implementació del starfield 2D amb parallax +// © 2026 JailDesigner + +#include "core/graphics/starfield_parallax.hpp" + +#include + +#include "core/defaults.hpp" +#include "core/rendering/line_renderer.hpp" + +namespace Graphics { + + namespace { + + auto randUniform(float min_v, float max_v) -> float { + const float NORM = static_cast(std::rand()) / static_cast(RAND_MAX); + return min_v + (NORM * (max_v - min_v)); + } + + } // namespace + + StarfieldParallax::StarfieldParallax(Rendering::Renderer* renderer) + : renderer_(renderer) { + buildStars(); + } + + void StarfieldParallax::buildStars() { + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + const float MIN_X = zona.x; + const float MAX_X = zona.x + zona.w; + const float MIN_Y = zona.y; + const float MAX_Y = zona.y + zona.h; + + // Tint aleatori entre blanc (255,255,255) i cyan (0,255,255) per estrella. + // T ∈ [0,1]: 0 → blanc; 1 → cyan. R = 255·(1-T), G=B=255. + const auto FILL_LAYER = [&](int layer, int count, int& idx) { + for (int i = 0; i < count; i++) { + const float T = randUniform(0.0F, 1.0F); + stars_[idx++] = Star{ + .x = randUniform(MIN_X, MAX_X), + .y = randUniform(MIN_Y, MAX_Y), + .layer = layer, + .color = SDL_Color{ + .r = static_cast(255.0F * (1.0F - T)), + .g = 255, + .b = 255, + .a = 255}}; + } + }; + + int idx = 0; + FILL_LAYER(0, Defaults::StarfieldParallax::Far::COUNT, idx); + FILL_LAYER(1, Defaults::StarfieldParallax::Mid::COUNT, idx); + FILL_LAYER(2, Defaults::StarfieldParallax::Near::COUNT, idx); + } + + auto StarfieldParallax::layerBrightness(int layer) -> float { + switch (layer) { + case 0: + return Defaults::StarfieldParallax::Far::BRIGHTNESS; + case 1: + return Defaults::StarfieldParallax::Mid::BRIGHTNESS; + case 2: + return Defaults::StarfieldParallax::Near::BRIGHTNESS; + default: + return 0.0F; + } + } + + auto StarfieldParallax::layerParallax(int layer) -> float { + switch (layer) { + case 0: + return Defaults::StarfieldParallax::Far::PARALLAX_FACTOR; + case 1: + return Defaults::StarfieldParallax::Mid::PARALLAX_FACTOR; + case 2: + return Defaults::StarfieldParallax::Near::PARALLAX_FACTOR; + default: + return 0.0F; + } + } + + auto StarfieldParallax::layerSize(int layer) -> int { + switch (layer) { + case 0: + return Defaults::StarfieldParallax::Far::SIZE_PX; + case 1: + return Defaults::StarfieldParallax::Mid::SIZE_PX; + case 2: + return Defaults::StarfieldParallax::Near::SIZE_PX; + default: + return 1; + } + } + + void StarfieldParallax::update(float delta_time, Vec2 world_velocity) { + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + const float MIN_X = zona.x; + const float MAX_X = zona.x + zona.w; + const float MIN_Y = zona.y; + const float MAX_Y = zona.y + zona.h; + const float W = zona.w; + const float H = zona.h; + + for (auto& star : stars_) { + const float FACTOR = layerParallax(star.layer); + star.x += world_velocity.x * FACTOR * delta_time; + star.y += world_velocity.y * FACTOR * delta_time; + + // Wraparound (PLAYAREA torica). + while (star.x < MIN_X) { + star.x += W; + } + while (star.x > MAX_X) { + star.x -= W; + } + while (star.y < MIN_Y) { + star.y += H; + } + while (star.y > MAX_Y) { + star.y -= H; + } + } + } + + void StarfieldParallax::draw() const { + for (const auto& star : stars_) { + const float B = layerBrightness(star.layer); + const int SIZE = layerSize(star.layer); + const int X = static_cast(star.x); + const int Y = static_cast(star.y); + + if (SIZE <= 1) { + // Punt d'1 px: línia degenerada horitzontal de 1 px. + Rendering::linea(renderer_, X, Y, X + 1, Y, B, 0.0F, star.color); + } else { + // Creu "+" amb extensió HALF des del centre en cada direcció. + const int HALF = SIZE - 1; // SIZE=2 → ±1 (creu 3x3); SIZE=3 → ±2 (creu 5x5) + Rendering::linea(renderer_, X - HALF, Y, X + HALF + 1, Y, B, 0.0F, star.color); + Rendering::linea(renderer_, X, Y - HALF, X, Y + HALF + 1, B, 0.0F, star.color); + } + } + } + +} // namespace Graphics diff --git a/source/core/graphics/starfield_parallax.hpp b/source/core/graphics/starfield_parallax.hpp new file mode 100644 index 0000000..7378708 --- /dev/null +++ b/source/core/graphics/starfield_parallax.hpp @@ -0,0 +1,51 @@ +// starfield_parallax.hpp - Capa més profunda del fons: estrelles 2D amb parallax +// © 2026 JailDesigner +// +// Estrelles 2D distribuïdes en 3 capes de profunditat. Cada capa té el seu +// factor parallax: el "món" es desplaça amb world_velocity i les estrelles +// d'una capa es mouen amb world_velocity * parallax_factor. Les més +// properes es mouen més (factor alt) → sensació de profunditat. +// Quan una estrella surt de PLAYAREA, reapareix per la banda oposada +// (wraparound). + +#pragma once + +#include + +#include + +#include "core/defaults/starfield_parallax.hpp" +#include "core/rendering/render_context.hpp" +#include "core/types.hpp" + +namespace Graphics { + + class StarfieldParallax { + public: + explicit StarfieldParallax(Rendering::Renderer* renderer); + + // Avança el desplaçament de les estrelles segons world_velocity (vector + // del moviment del món en px/s; típicament = -ship_velocity). + // world_velocity == {0, 0} → estrelles quietes. + void update(float delta_time, Vec2 world_velocity); + + void draw() const; + + private: + struct Star { + float x{0.0F}; + float y{0.0F}; + int layer{0}; // 0=Far, 1=Mid, 2=Near + SDL_Color color{}; // tint precomputat entre blanc i cyan + }; + + void buildStars(); + static auto layerBrightness(int layer) -> float; + static auto layerParallax(int layer) -> float; + static auto layerSize(int layer) -> int; + + Rendering::Renderer* renderer_; + std::array stars_{}; + }; + +} // namespace Graphics diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index c9ea6cc..6aeeb18 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -30,6 +30,7 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context) floating_score_manager_(sdl.getRenderer()), trail_manager_(sdl.getRenderer()), text_(sdl.getRenderer()), + starfield_parallax_(sdl.getRenderer()), playfield_(sdl.getRenderer()), border_(sdl.getRenderer()) { // Recuperar configuración de match des del context @@ -213,6 +214,9 @@ void GameScene::stepPhysics(float delta_time) { bullet.postUpdate(delta_time); } trail_manager_.update(delta_time, ships_); + // De moment world_velocity = {0, 0} → estrelles quietes; al ser-hi l'estat + // del wraparound es resol normalment quan activem el moviment. + starfield_parallax_.update(delta_time, Vec2{.x = 0.0F, .y = 0.0F}); playfield_.update(delta_time); border_.update(delta_time); @@ -569,6 +573,7 @@ void GameScene::drawActiveShipsAlive() const { } void GameScene::drawContinueState() { + starfield_parallax_.draw(); border_.draw(); drawEnemies(); drawBullets(); @@ -580,6 +585,7 @@ void GameScene::drawContinueState() { } void GameScene::drawGameOverState() { + starfield_parallax_.draw(); border_.draw(); drawEnemies(); drawBullets(); @@ -625,6 +631,8 @@ void GameScene::drawInitHudState() { Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT, Defaults::Game::INIT_HUD_SHIP2_RATIO_END); + // Capa de fons més profunda: estrelles 2D (apareixen senceres des del frame 0). + starfield_parallax_.draw(); // Graella de fons al darrere (timer intern propi, cobreix tot l'INIT_HUD). playfield_.draw(); @@ -650,6 +658,7 @@ void GameScene::drawInitHudState() { } void GameScene::drawLevelStartState() { + starfield_parallax_.draw(); playfield_.draw(); border_.draw(); trail_manager_.draw(); @@ -663,6 +672,7 @@ void GameScene::drawLevelStartState() { } void GameScene::drawPlayingState() { + starfield_parallax_.draw(); playfield_.draw(); border_.draw(); trail_manager_.draw(); @@ -676,6 +686,7 @@ void GameScene::drawPlayingState() { } void GameScene::drawLevelCompletedState() { + starfield_parallax_.draw(); playfield_.draw(); border_.draw(); trail_manager_.draw(); diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 9083f37..09688d8 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -10,6 +10,7 @@ #include "core/graphics/border.hpp" #include "core/graphics/playfield.hpp" +#include "core/graphics/starfield_parallax.hpp" #include "core/graphics/vector_text.hpp" #include "core/physics/physics_world.hpp" #include "core/rendering/sdl_manager.hpp" @@ -82,6 +83,9 @@ class GameScene final : public Scene { // Text vectorial Graphics::VectorText text_; + // Capa més profunda del fons: estrelles 2D amb parallax (estàtiques de moment). + Graphics::StarfieldParallax starfield_parallax_; + // Fons del playfield (graella + futures capes) Graphics::Playfield playfield_;