feat(lighting): offset del centre de la llum per emissor
This commit is contained in:
1 parent
dacc4a9909
commit
3a42c7958a
9 files changed
+110
-21
No files matched your search
@@ -22,8 +22,12 @@ animations:
|
||||
# anells). Amb `flicker: true` el radi efectiu oscil·la per frame
|
||||
# (suma de dues sinusoides amb fase derivada de la posició) per a
|
||||
# que el cercle "respiri" com una flama; els paràmetres del flicker
|
||||
# també viuen al config global. Omet el bloc per fer una antorxa
|
||||
# "apagada".
|
||||
# també viuen al config global. `offset: [x, y]` desplaça el centre
|
||||
# del cercle: la flama del sprite no està al centre del bounding box
|
||||
# sinó cap a dalt, així que pujem la llum 4 px (Y creix cap avall)
|
||||
# per a que el halo "neixi" de la flama, no del peu. Omet el bloc
|
||||
# per fer una antorxa "apagada".
|
||||
light:
|
||||
radius: 40
|
||||
flicker: true
|
||||
offset: [0, -4]
|
||||
@@ -64,12 +64,13 @@ auto Entity::getCollisionRect() const -> SDL_FRect {
|
||||
}
|
||||
|
||||
auto Entity::getLightCenter() const -> SDL_FPoint {
|
||||
// Centre del rect de render. Per a entitats que es desplacen
|
||||
// visualment sobre la seva posició lògica (p.ex. el Player saltant),
|
||||
// la subclasse override aquest mètode per restar el desplaçament.
|
||||
// Centre del rect de render + offset configurat al YAML. Per a
|
||||
// entitats que es desplacen visualment sobre la seva posició lògica
|
||||
// (p.ex. el Player saltant), la subclasse override aquest mètode
|
||||
// per restar el desplaçament — i ha d'incloure l'offset igualment.
|
||||
return {
|
||||
.x = pixel_rect_.x + (pixel_rect_.w * 0.5F),
|
||||
.y = pixel_rect_.y + (pixel_rect_.h * 0.5F),
|
||||
.x = pixel_rect_.x + (pixel_rect_.w * 0.5F) + static_cast<float>(light_offset_x_),
|
||||
.y = pixel_rect_.y + (pixel_rect_.h * 0.5F) + static_cast<float>(light_offset_y_),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -280,6 +280,22 @@ class Entity {
|
||||
light_color_b_ = b;
|
||||
}
|
||||
|
||||
// Desplaçament en píxels del centre del cercle de llum respecte el
|
||||
// centre visual del sprite (que és el que retornaria per defecte
|
||||
// `getLightCenter`). Útil quan el "punt de llum" iconogràfic no
|
||||
// coincideix amb el centre del bounding box — per exemple, la flama
|
||||
// d'una antorxa està a la part de dalt del sprite i interessa pujar
|
||||
// l'emissor 3-4 px per a que el cercle de llum surti de la flama,
|
||||
// no del peu. Y creix cap avall al canvas, així que `y: -3` puja
|
||||
// la llum 3 píxels. Cadena de resolució idèntica al color: tipus →
|
||||
// sala → spawn.
|
||||
[[nodiscard]] auto getLightOffsetX() const -> int { return light_offset_x_; }
|
||||
[[nodiscard]] auto getLightOffsetY() const -> int { return light_offset_y_; }
|
||||
void setLightOffset(int x, int y) {
|
||||
light_offset_x_ = x;
|
||||
light_offset_y_ = y;
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -288,11 +304,13 @@ class Entity {
|
||||
// un problema.
|
||||
[[nodiscard]] auto getLightSeed() const -> int { return light_seed_; }
|
||||
|
||||
// Centre de la llum en píxels del canvas. Per defecte és el centre del
|
||||
// `pixel_rect_` (que és la caixa de render: sprite top-left + mida).
|
||||
// Virtual perquè entitats amb desplaçament visual sobre la posició
|
||||
// lògica (p.ex. el Player en l'aire, que rebaixa visualment `pos_.y`
|
||||
// amb `jump_z_`) puguin retornar el centre que realment es veu.
|
||||
// Centre de la llum en píxels del canvas. Per defecte és el centre
|
||||
// del `pixel_rect_` (caixa de render) més l'offset configurat al
|
||||
// YAML (`light.offset: [x, y]`). Virtual perquè entitats amb
|
||||
// desplaçament visual sobre la posició lògica (p.ex. el Player en
|
||||
// l'aire, que rebaixa visualment `pos_.y` amb `jump_z_`) puguin
|
||||
// retornar el centre que realment es veu — l'override n'és
|
||||
// responsable d'incloure l'offset (com fa `Player::getLightCenter`).
|
||||
[[nodiscard]] virtual auto getLightCenter() const -> SDL_FPoint;
|
||||
|
||||
protected:
|
||||
@@ -319,6 +337,8 @@ class Entity {
|
||||
std::uint8_t light_color_r_{255};
|
||||
std::uint8_t light_color_g_{255};
|
||||
std::uint8_t light_color_b_{255};
|
||||
int light_offset_x_{0};
|
||||
int light_offset_y_{0};
|
||||
bool dead_{false};
|
||||
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
||||
EventBus* bus_{nullptr};
|
||||
|
||||
@@ -157,12 +157,14 @@ namespace {
|
||||
return c["height"].get_value<int>();
|
||||
}
|
||||
|
||||
// Bloc opcional `light: { radius: N, flicker: bool, color: [r,g,b] }`
|
||||
// Bloc opcional `light: { radius: N, flicker: bool, color: [r,g,b], offset: [x,y] }`
|
||||
// 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
|
||||
// efecte en mode additive — defecte blanc (255,255,255). `offset`
|
||||
// desplaça el centre del cercle de llum respecte el centre del
|
||||
// sprite (default (0, 0); Y creix cap avall). 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};
|
||||
@@ -170,6 +172,8 @@ namespace {
|
||||
std::uint8_t color_r{255};
|
||||
std::uint8_t color_g{255};
|
||||
std::uint8_t color_b{255};
|
||||
int offset_x{0};
|
||||
int offset_y{0};
|
||||
};
|
||||
|
||||
// Helper compartit entre les tres capes (tipus, sala via Map, spawn)
|
||||
@@ -182,6 +186,16 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper paral·lel a `readColorTriplet` per a `offset: [x, y]` —
|
||||
// píxels enters, sense clamp (offsets negatius són legítims; Y
|
||||
// creix cap avall).
|
||||
auto readOffsetPair(const fkyaml::node& seq, int& x, int& y) -> bool {
|
||||
if (!seq.is_sequence() || seq.size() != 2) { return false; }
|
||||
x = seq[0].get_value<int>();
|
||||
y = seq[1].get_value<int>();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto readLight(const fkyaml::node& tmpl) -> LightTemplate {
|
||||
if (!tmpl.contains("light")) { return {}; }
|
||||
const auto& l = tmpl["light"];
|
||||
@@ -189,6 +203,7 @@ namespace {
|
||||
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); }
|
||||
if (l.contains("offset")) { readOffsetPair(l["offset"], out.offset_x, out.offset_y); }
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -203,7 +218,7 @@ 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, .color_r = 255, .color_g = 255, .color_b = 255}; }
|
||||
if (!room->enabled) { return LightTemplate{}; }
|
||||
LightTemplate out = type_default;
|
||||
if (room->radius.has_value()) { out.radius = *room->radius; }
|
||||
if (room->flicker.has_value()) { out.flicker = *room->flicker; }
|
||||
@@ -212,6 +227,10 @@ namespace {
|
||||
out.color_g = (*room->color)[1];
|
||||
out.color_b = (*room->color)[2];
|
||||
}
|
||||
if (room->offset.has_value()) {
|
||||
out.offset_x = (*room->offset)[0];
|
||||
out.offset_y = (*room->offset)[1];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -220,7 +239,7 @@ namespace {
|
||||
// a l'entry del YAML de nivell:
|
||||
// `light: false` → apaga aquesta instància
|
||||
// `light: true` → informatiu (cau a capes inferiors)
|
||||
// `light: { radius, flicker, color: [r,g,b] }` → camps presents sobreescriuen;
|
||||
// `light: { radius, flicker, color: [r,g,b], offset: [x,y] }` → camps presents sobreescriuen;
|
||||
// els absents respecten el que
|
||||
// ja duia l'entitat
|
||||
void applySpawnLightOverride(Entity& entity, const fkyaml::node& spawn) {
|
||||
@@ -242,6 +261,11 @@ namespace {
|
||||
std::uint8_t b{};
|
||||
if (readColorTriplet(l["color"], r, g, b)) { entity.setLightColor(r, g, b); }
|
||||
}
|
||||
if (l.contains("offset")) {
|
||||
int x{};
|
||||
int y{};
|
||||
if (readOffsetPair(l["offset"], x, y)) { entity.setLightOffset(x, y); }
|
||||
}
|
||||
}
|
||||
|
||||
// Bloc `collision: { size: [w, h], base_size: [bw, bh]?, offset: [x, y]? }`
|
||||
@@ -374,6 +398,7 @@ namespace {
|
||||
entity->setLightRadius(LIGHT.radius);
|
||||
entity->setLightFlicker(LIGHT.flicker);
|
||||
entity->setLightColor(LIGHT.color_r, LIGHT.color_g, LIGHT.color_b);
|
||||
entity->setLightOffset(LIGHT.offset_x, LIGHT.offset_y);
|
||||
anchor_out = readAnchor(tmpl);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -153,6 +153,8 @@ void Player::readLightBlock(const fkyaml::node& yaml) {
|
||||
light_color_r = 255;
|
||||
light_color_g = 255;
|
||||
light_color_b = 255;
|
||||
light_offset_x = 0;
|
||||
light_offset_y = 0;
|
||||
if (!yaml.contains("light")) { return; }
|
||||
const auto& l = yaml["light"];
|
||||
if (l.contains("radius")) { light_radius = l["radius"].get_value<int>(); }
|
||||
@@ -167,6 +169,15 @@ void Player::readLightBlock(const fkyaml::node& yaml) {
|
||||
light_color_b = static_cast<std::uint8_t>(std::clamp(c[2].get_value<int>(), 0, 255));
|
||||
}
|
||||
}
|
||||
// `offset: [x, y]` opcional, píxels enters. Default (0, 0) → centre
|
||||
// del sprite. Y creix cap avall, així que `y: -3` puja la llum 3 px.
|
||||
if (l.contains("offset")) {
|
||||
const auto& o = l["offset"];
|
||||
if (o.is_sequence() && o.size() == 2) {
|
||||
light_offset_x = o[0].get_value<int>();
|
||||
light_offset_y = o[1].get_value<int>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
|
||||
@@ -1492,9 +1503,10 @@ auto Player::getLightCenter() const -> SDL_FPoint {
|
||||
// Sprite top-left = (pos_.x, pos_.y). El render desplaça verticalment
|
||||
// `jump_z_` píxels durant el salt; aplicar el mateix offset aquí fa
|
||||
// que la llum "salti" amb el jugador en lloc de quedar-se al sòl.
|
||||
// Sumem també l'offset estàtic configurat al YAML del personatge.
|
||||
return {
|
||||
.x = getPosX() + (static_cast<float>(sprite_w) * 0.5F),
|
||||
.y = getPosY() - jump_z_ + (static_cast<float>(sprite_h) * 0.5F),
|
||||
.x = getPosX() + (static_cast<float>(sprite_w) * 0.5F) + static_cast<float>(light_offset_x),
|
||||
.y = getPosY() - jump_z_ + (static_cast<float>(sprite_h) * 0.5F) + static_cast<float>(light_offset_y),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -125,17 +125,21 @@ 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, color: [r,g,b] }`
|
||||
// del bloc opcional `light: { radius: N, flicker: bool, color: [r,g,b], offset: [x,y] }`
|
||||
// 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).
|
||||
// additive (en subtractive s'ignora); `light_offset_*` desplaça el
|
||||
// centre del cercle respecte el centre del sprite (Y creix avall:
|
||||
// `y: -3` puja la llum 3 px).
|
||||
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;
|
||||
inline static int light_offset_x = 0;
|
||||
inline static int light_offset_y = 0;
|
||||
|
||||
// Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor.
|
||||
static void loadFrom(const std::string& yaml_path);
|
||||
@@ -154,6 +158,8 @@ class Player : public AnimatedSprite {
|
||||
[[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; }
|
||||
[[nodiscard]] static auto getLightOffsetX() -> int { return light_offset_x; }
|
||||
[[nodiscard]] static auto getLightOffsetY() -> int { return light_offset_y; }
|
||||
// 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
|
||||
|
||||
@@ -66,6 +66,18 @@ namespace Map {
|
||||
};
|
||||
}
|
||||
|
||||
// Parser comú d'una 2-tupla d'enters (offset de llum). Retorna
|
||||
// nullopt si el node no és una seqüència de 2 elements. Sense
|
||||
// clamp: els offsets negatius són legítims (Y creix cap avall,
|
||||
// així que `y: -3` puja la llum 3 px).
|
||||
auto parseIntPair(const fkyaml::node& node) -> std::optional<std::array<int, 2>> {
|
||||
if (!node.is_sequence() || node.size() != 2) { return std::nullopt; }
|
||||
return std::array<int, 2>{
|
||||
node[0].get_value<int>(),
|
||||
node[1].get_value<int>(),
|
||||
};
|
||||
}
|
||||
|
||||
// Llegeix el bloc opcional `lighting:` del YAML d'una sala i
|
||||
// actualitza `light_level`, `lighting_mode` i el mapping
|
||||
// `g_room_emitters`. Idempotent: cada `Map::loadFrom` reseteja
|
||||
@@ -102,6 +114,7 @@ namespace Map {
|
||||
if (val.contains("radius")) { ov.radius = val["radius"].get_value<int>(); }
|
||||
if (val.contains("flicker")) { ov.flicker = val["flicker"].get_value<bool>(); }
|
||||
if (val.contains("color")) { ov.color = parseColorTriplet(val["color"]); }
|
||||
if (val.contains("offset")) { ov.offset = parseIntPair(val["offset"]); }
|
||||
}
|
||||
g_room_emitters[KEY] = ov;
|
||||
}
|
||||
|
||||
@@ -95,6 +95,10 @@ namespace Map {
|
||||
// del tipus (default blanc 255,255,255 si el tipus no en declara).
|
||||
// La forma YAML és `color: [r, g, b]` (bytes 0..255).
|
||||
std::optional<std::array<std::uint8_t, 3>> color;
|
||||
// Offset del centre del cercle de llum respecte el centre del
|
||||
// sprite (píxels enters; Y creix avall). `nullopt` → conservar
|
||||
// l'offset del tipus (default [0, 0]). Forma YAML: `offset: [x, y]`.
|
||||
std::optional<std::array<int, 2>> offset;
|
||||
};
|
||||
|
||||
// Consulta l'override de sala per a un tipus d'entitat (clau singular,
|
||||
|
||||
@@ -255,6 +255,10 @@ namespace {
|
||||
Player::light_color_g = (*room->color)[1];
|
||||
Player::light_color_b = (*room->color)[2];
|
||||
}
|
||||
if (room->offset.has_value()) {
|
||||
Player::light_offset_x = (*room->offset)[0];
|
||||
Player::light_offset_y = (*room->offset)[1];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in new issue
Block a user