feat(lighting): mode additive amb color per emissor
This commit is contained in:
1 parent
24609d6c5c
commit
771ff1f6cd
10 files changed
+352
-71
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -266,6 +267,19 @@ class Entity {
|
||||
[[nodiscard]] auto getLightFlicker() const -> bool { return light_flicker_; }
|
||||
void setLightFlicker(bool f) { light_flicker_ = f; }
|
||||
|
||||
// Color emès en mode additive (RGB 0..255 per canal). Default blanc:
|
||||
// en mode subtractive aquest valor s'ignora completament; en mode
|
||||
// additive és el tint que l'emissor afegeix al canvas. La cadena de
|
||||
// resolució (tipus → sala → spawn) el pobla via `setLightColor`.
|
||||
[[nodiscard]] auto getLightColorR() const -> std::uint8_t { return light_color_r_; }
|
||||
[[nodiscard]] auto getLightColorG() const -> std::uint8_t { return light_color_g_; }
|
||||
[[nodiscard]] auto getLightColorB() const -> std::uint8_t { return light_color_b_; }
|
||||
void setLightColor(std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
||||
light_color_r_ = r;
|
||||
light_color_g_ = g;
|
||||
light_color_b_ = b;
|
||||
}
|
||||
|
||||
// Identificador estable per al flicker. Cada Entity rep un valor
|
||||
// únic a la construcció (contador estàtic) i el conserva fins a la
|
||||
// mort — així el patró del flicker no "deriva" quan l'entitat es
|
||||
@@ -302,6 +316,9 @@ class Entity {
|
||||
static inline int s_next_light_seed{0};
|
||||
int light_seed_{++s_next_light_seed};
|
||||
bool light_flicker_{false};
|
||||
std::uint8_t light_color_r_{255};
|
||||
std::uint8_t light_color_g_{255};
|
||||
std::uint8_t light_color_b_{255};
|
||||
bool dead_{false};
|
||||
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
||||
EventBus* bus_{nullptr};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// consumidors que només volen la base del manager (p.ex. el smoke test
|
||||
// stand-alone) enllacen `entity_manager.cpp` sense arrossegar aquest fitxer.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -155,22 +157,38 @@ namespace {
|
||||
return c["height"].get_value<int>();
|
||||
}
|
||||
|
||||
// Bloc opcional `light: { radius: N, flicker: bool }` de la plantilla.
|
||||
// Si manca, el radi és 0 i l'entitat no entra a la pasada d'il·luminació.
|
||||
// `flicker` activa la modulació "tipus foc" del radi (només antorxes
|
||||
// l'haurien d'usar). La forma del gradient (3 anells) i els paràmetres
|
||||
// del flicker viuen a data/config/lighting.yaml — no cal declarar-los
|
||||
// per entitat.
|
||||
// Bloc opcional `light: { radius: N, flicker: bool, color: [r,g,b] }`
|
||||
// de la plantilla. Si manca, el radi és 0 i l'entitat no entra a la
|
||||
// pasada d'il·luminació. `flicker` activa la modulació "tipus foc"
|
||||
// del radi (només antorxes l'haurien d'usar). `color` només té
|
||||
// efecte en mode additive — defecte blanc (255,255,255). La forma
|
||||
// del gradient (3 anells) i els paràmetres del flicker viuen a
|
||||
// data/config/lighting.yaml — no cal declarar-los per entitat.
|
||||
struct LightTemplate {
|
||||
int radius{0};
|
||||
bool flicker{false};
|
||||
std::uint8_t color_r{255};
|
||||
std::uint8_t color_g{255};
|
||||
std::uint8_t color_b{255};
|
||||
};
|
||||
|
||||
// Helper compartit entre les tres capes (tipus, sala via Map, spawn)
|
||||
// per llegir `color: [r, g, b]` amb clamp defensiu a 0..255.
|
||||
auto readColorTriplet(const fkyaml::node& seq, std::uint8_t& r, std::uint8_t& g, std::uint8_t& b) -> bool {
|
||||
if (!seq.is_sequence() || seq.size() != 3) { return false; }
|
||||
r = static_cast<std::uint8_t>(std::clamp(seq[0].get_value<int>(), 0, 255));
|
||||
g = static_cast<std::uint8_t>(std::clamp(seq[1].get_value<int>(), 0, 255));
|
||||
b = static_cast<std::uint8_t>(std::clamp(seq[2].get_value<int>(), 0, 255));
|
||||
return true;
|
||||
}
|
||||
|
||||
auto readLight(const fkyaml::node& tmpl) -> LightTemplate {
|
||||
if (!tmpl.contains("light")) { return {}; }
|
||||
const auto& l = tmpl["light"];
|
||||
LightTemplate out;
|
||||
if (l.contains("radius")) { out.radius = l["radius"].get_value<int>(); }
|
||||
if (l.contains("flicker")) { out.flicker = l["flicker"].get_value<bool>(); }
|
||||
if (l.contains("color")) { readColorTriplet(l["color"], out.color_r, out.color_g, out.color_b); }
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -185,23 +203,26 @@ namespace {
|
||||
auto resolveLightWithRoom(const std::string& type, LightTemplate type_default) -> LightTemplate {
|
||||
const auto* room = Map::getRoomEmitterOverride(type);
|
||||
if (room == nullptr) { return type_default; }
|
||||
if (!room->enabled) { return {.radius = 0, .flicker = false}; }
|
||||
if (!room->enabled) { return {.radius = 0, .flicker = false, .color_r = 255, .color_g = 255, .color_b = 255}; }
|
||||
LightTemplate out = type_default;
|
||||
if (room->radius.has_value()) { out.radius = *room->radius; }
|
||||
if (room->flicker.has_value()) { out.flicker = *room->flicker; }
|
||||
if (room->color.has_value()) {
|
||||
out.color_r = (*room->color)[0];
|
||||
out.color_g = (*room->color)[1];
|
||||
out.color_b = (*room->color)[2];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Aplica l'override per-spawn (si existeix) per damunt del que ja
|
||||
// viu a l'entitat (defaults del tipus + override de sala). Tres formes
|
||||
// a l'entry del YAML de nivell:
|
||||
// `light: false` → apaga aquesta instància
|
||||
// `light: true` → no canvia res; la línia és
|
||||
// informativa (la llum ja la donen
|
||||
// les capes inferiors)
|
||||
// `light: { radius, flicker }` → els camps presents sobreescriuen;
|
||||
// els absents respecten el que ja
|
||||
// duia l'entitat
|
||||
// `light: false` → apaga aquesta instància
|
||||
// `light: true` → informatiu (cau a capes inferiors)
|
||||
// `light: { radius, flicker, color: [r,g,b] }` → camps presents sobreescriuen;
|
||||
// els absents respecten el que
|
||||
// ja duia l'entitat
|
||||
void applySpawnLightOverride(Entity& entity, const fkyaml::node& spawn) {
|
||||
if (!spawn.contains("light")) { return; }
|
||||
const auto& l = spawn["light"];
|
||||
@@ -215,6 +236,12 @@ namespace {
|
||||
if (!l.is_mapping()) { return; }
|
||||
if (l.contains("radius")) { entity.setLightRadius(l["radius"].get_value<int>()); }
|
||||
if (l.contains("flicker")) { entity.setLightFlicker(l["flicker"].get_value<bool>()); }
|
||||
if (l.contains("color")) {
|
||||
std::uint8_t r{};
|
||||
std::uint8_t g{};
|
||||
std::uint8_t b{};
|
||||
if (readColorTriplet(l["color"], r, g, b)) { entity.setLightColor(r, g, b); }
|
||||
}
|
||||
}
|
||||
|
||||
// Bloc `collision: { size: [w, h], base_size: [bw, bh]?, offset: [x, y]? }`
|
||||
@@ -346,6 +373,7 @@ namespace {
|
||||
const auto LIGHT = resolveLightWithRoom(type, readLight(tmpl));
|
||||
entity->setLightRadius(LIGHT.radius);
|
||||
entity->setLightFlicker(LIGHT.flicker);
|
||||
entity->setLightColor(LIGHT.color_r, LIGHT.color_g, LIGHT.color_b);
|
||||
anchor_out = readAnchor(tmpl);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -150,10 +150,23 @@ void Player::loadFrom(const std::string& yaml_path) {
|
||||
void Player::readLightBlock(const fkyaml::node& yaml) {
|
||||
light_radius = 0;
|
||||
light_flicker = false;
|
||||
light_color_r = 255;
|
||||
light_color_g = 255;
|
||||
light_color_b = 255;
|
||||
if (!yaml.contains("light")) { return; }
|
||||
const auto& l = yaml["light"];
|
||||
if (l.contains("radius")) { light_radius = l["radius"].get_value<int>(); }
|
||||
if (l.contains("flicker")) { light_flicker = l["flicker"].get_value<bool>(); }
|
||||
// `color: [r, g, b]` opcional, bytes 0..255. Si manca, blanc (sense
|
||||
// tint) — comportament idèntic al mode subtractive de hui.
|
||||
if (l.contains("color")) {
|
||||
const auto& c = l["color"];
|
||||
if (c.is_sequence() && c.size() == 3) {
|
||||
light_color_r = static_cast<std::uint8_t>(std::clamp(c[0].get_value<int>(), 0, 255));
|
||||
light_color_g = static_cast<std::uint8_t>(std::clamp(c[1].get_value<int>(), 0, 255));
|
||||
light_color_b = static_cast<std::uint8_t>(std::clamp(c[2].get_value<int>(), 0, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
|
||||
|
||||
@@ -125,12 +125,17 @@ class Player : public AnimatedSprite {
|
||||
inline static int body_height = 0;
|
||||
|
||||
// Radi de la llum emesa pel jugador (en píxels del canvas), llegit
|
||||
// del bloc opcional `light: { radius: N, flicker: bool }` del YAML.
|
||||
// 0 = no emet. Es declara estàtic com la resta de configs del jugador
|
||||
// (immunity, jump, ...): tots els jugadors del mateix YAML comparteixen
|
||||
// el valor. `light_flicker` activa la modulació "tipus foc" del radi.
|
||||
// del bloc opcional `light: { radius: N, flicker: bool, color: [r,g,b] }`
|
||||
// del YAML. 0 = no emet. Es declara estàtic com la resta de configs
|
||||
// del jugador (immunity, jump, ...): tots els jugadors del mateix YAML
|
||||
// comparteixen el valor. `light_flicker` activa la modulació "tipus
|
||||
// foc" del radi; `light_color_*` són el tint quan la sala usa mode
|
||||
// additive (en subtractive s'ignora).
|
||||
inline static int light_radius = 0;
|
||||
inline static bool light_flicker = false;
|
||||
inline static std::uint8_t light_color_r = 255;
|
||||
inline static std::uint8_t light_color_g = 255;
|
||||
inline static std::uint8_t light_color_b = 255;
|
||||
|
||||
// Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor.
|
||||
static void loadFrom(const std::string& yaml_path);
|
||||
@@ -146,6 +151,9 @@ class Player : public AnimatedSprite {
|
||||
// per a que el cercle "salti" amb el jugador, no es quedi al sòl.
|
||||
[[nodiscard]] static auto getLightRadius() -> int { return light_radius; }
|
||||
[[nodiscard]] static auto getLightFlicker() -> bool { return light_flicker; }
|
||||
[[nodiscard]] static auto getLightColorR() -> std::uint8_t { return light_color_r; }
|
||||
[[nodiscard]] static auto getLightColorG() -> std::uint8_t { return light_color_g; }
|
||||
[[nodiscard]] static auto getLightColorB() -> std::uint8_t { return light_color_b; }
|
||||
// Seed estable per al flicker: reusem `id_` (0 o 1 en multijugador
|
||||
// local) amb un offset gran per evitar col·lisions amb el contador
|
||||
// d'Entity (que arrenca des de 1). Així P1 i P2 oscil·len en fases
|
||||
|
||||
Reference in new issue
Block a user