feat(starfield): capa parallax al fons del playfield amb tint blanc-cyan
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,145 @@
|
||||
// starfield_parallax.cpp - Implementació del starfield 2D amb parallax
|
||||
// © 2026 JailDesigner
|
||||
|
||||
#include "core/graphics/starfield_parallax.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#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<float>(std::rand()) / static_cast<float>(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<Uint8>(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<int>(star.x);
|
||||
const int Y = static_cast<int>(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
|
||||
@@ -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 <SDL3/SDL.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#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<Star, Defaults::StarfieldParallax::TOTAL_COUNT> stars_{};
|
||||
};
|
||||
|
||||
} // namespace Graphics
|
||||
@@ -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();
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user