feat(decals): tiles de decal de sòl compten com a pintats des del load
This commit is contained in:
1 parent
7cd7b4dc4f
commit
7d0aca506a
6 files changed
+74
-12
No files matched your search
@@ -37,5 +37,4 @@ class Hole : public StaticHazard {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// TODO(hole-feature): afegir reset de `falling_through_hole_` i `hole_clip_bottom_y_` a `Player::disable()` per simetria.
|
// TODO(hole-feature): afegir reset de `falling_through_hole_` i `hole_clip_bottom_y_` a `Player::disable()` per simetria.
|
||||||
// TODO(hole-feature): si apareix un quart callsite del càlcul "pixel→tile" o "tileRect", factoritzar a `Map::` (avui: Mummy::reactToCurrentTile, Mummy::isDecalAheadInDir, Player::integrateJump).
|
|
||||||
// TODO(hole-feature): afegir tests dedicats del comportament de la mòmia amb forats (no requereixen Player real).
|
// TODO(hole-feature): afegir tests dedicats del comportament de la mòmia amb forats (no requereixen Player real).
|
||||||
@@ -291,13 +291,9 @@ void Mummy::reactToCurrentTile() {
|
|||||||
// per a una direcció negativa, el menor (`x`); per a zero, qualsevol
|
// per a una direcció negativa, el menor (`x`); per a zero, qualsevol
|
||||||
// (no hi ha avenç en aquest eix).
|
// (no hi ha avenç en aquest eix).
|
||||||
const auto BOX = getCollisionRect();
|
const auto BOX = getCollisionRect();
|
||||||
const auto TS = static_cast<float>(Map::tile_size);
|
|
||||||
const auto ORIG_X = static_cast<float>(Map::tilemap_origin_x);
|
|
||||||
const auto ORIG_Y = static_cast<float>(Map::tilemap_origin_y);
|
|
||||||
const float LEAD_X = (dir_x_ > 0.0F) ? (BOX.x + BOX.w - 1.0F) : BOX.x;
|
const float LEAD_X = (dir_x_ > 0.0F) ? (BOX.x + BOX.w - 1.0F) : BOX.x;
|
||||||
const float LEAD_Y = (dir_y_ > 0.0F) ? (BOX.y + BOX.h - 1.0F) : BOX.y;
|
const float LEAD_Y = (dir_y_ > 0.0F) ? (BOX.y + BOX.h - 1.0F) : BOX.y;
|
||||||
const int CUR_COL = static_cast<int>(std::floor((LEAD_X - ORIG_X) / TS));
|
const auto [CUR_COL, CUR_ROW] = Map::pixelToTile(LEAD_X, LEAD_Y);
|
||||||
const int CUR_ROW = static_cast<int>(std::floor((LEAD_Y - ORIG_Y) / TS));
|
|
||||||
|
|
||||||
// Sense canvi de tile no hi ha res a fer — això filtra també tots els
|
// Sense canvi de tile no hi ha res a fer — això filtra també tots els
|
||||||
// ticks intra-tile (la majoria, ~16 frames per tile a velocitats típiques).
|
// ticks intra-tile (la majoria, ~16 frames per tile a velocitats típiques).
|
||||||
@@ -318,9 +314,10 @@ void Mummy::reactToCurrentTile() {
|
|||||||
// Si el tile nou conté un decal trigger (Hole o futur pinchos plans,
|
// Si el tile nou conté un decal trigger (Hole o futur pinchos plans,
|
||||||
// plaques, etc.), mitja volta immediata. `changeDirection` també
|
// plaques, etc.), mitja volta immediata. `changeDirection` també
|
||||||
// selecciona l'animació cardinal corresponent.
|
// selecciona l'animació cardinal corresponent.
|
||||||
|
const auto TS = static_cast<float>(Map::tile_size);
|
||||||
const SDL_FRect TILE_RECT{
|
const SDL_FRect TILE_RECT{
|
||||||
.x = (static_cast<float>(CUR_COL) * TS) + ORIG_X,
|
.x = (static_cast<float>(CUR_COL) * TS) + static_cast<float>(Map::tilemap_origin_x),
|
||||||
.y = (static_cast<float>(CUR_ROW) * TS) + ORIG_Y,
|
.y = (static_cast<float>(CUR_ROW) * TS) + static_cast<float>(Map::tilemap_origin_y),
|
||||||
.w = TS,
|
.w = TS,
|
||||||
.h = TS,
|
.h = TS,
|
||||||
};
|
};
|
||||||
@@ -444,8 +441,6 @@ auto Mummy::isDecalAheadInDir(float dx_signed, float dy_signed) const -> bool {
|
|||||||
// tile abans del veritablement adjacent al decal.
|
// tile abans del veritablement adjacent al decal.
|
||||||
const auto BOX = getCollisionRect();
|
const auto BOX = getCollisionRect();
|
||||||
const auto TS = static_cast<float>(Map::tile_size);
|
const auto TS = static_cast<float>(Map::tile_size);
|
||||||
const auto ORIG_X = static_cast<float>(Map::tilemap_origin_x);
|
|
||||||
const auto ORIG_Y = static_cast<float>(Map::tilemap_origin_y);
|
|
||||||
const auto SIGN_OF = [](float v) -> float {
|
const auto SIGN_OF = [](float v) -> float {
|
||||||
if (v > 0.0F) { return 1.0F; }
|
if (v > 0.0F) { return 1.0F; }
|
||||||
if (v < 0.0F) { return -1.0F; }
|
if (v < 0.0F) { return -1.0F; }
|
||||||
@@ -453,8 +448,9 @@ auto Mummy::isDecalAheadInDir(float dx_signed, float dy_signed) const -> bool {
|
|||||||
};
|
};
|
||||||
const float CENTER_X = BOX.x + (BOX.w * 0.5F);
|
const float CENTER_X = BOX.x + (BOX.w * 0.5F);
|
||||||
const float CENTER_Y = BOX.y + (BOX.h * 0.5F);
|
const float CENTER_Y = BOX.y + (BOX.h * 0.5F);
|
||||||
const float TILE_X = (std::floor((CENTER_X - ORIG_X) / TS) * TS) + ORIG_X;
|
const auto [CUR_COL, CUR_ROW] = Map::pixelToTile(CENTER_X, CENTER_Y);
|
||||||
const float TILE_Y = (std::floor((CENTER_Y - ORIG_Y) / TS) * TS) + ORIG_Y;
|
const float TILE_X = (static_cast<float>(CUR_COL) * TS) + static_cast<float>(Map::tilemap_origin_x);
|
||||||
|
const float TILE_Y = (static_cast<float>(CUR_ROW) * TS) + static_cast<float>(Map::tilemap_origin_y);
|
||||||
const SDL_FRect NEXT{
|
const SDL_FRect NEXT{
|
||||||
.x = TILE_X + (SIGN_OF(dx_signed) * TS),
|
.x = TILE_X + (SIGN_OF(dx_signed) * TS),
|
||||||
.y = TILE_Y + (SIGN_OF(dy_signed) * TS),
|
.y = TILE_Y + (SIGN_OF(dy_signed) * TS),
|
||||||
|
|||||||
@@ -420,6 +420,32 @@ void EntityManager::loadFromLevel(const std::string& level_yaml_path) {
|
|||||||
if (OK) { ++loaded; }
|
if (OK) { ++loaded; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Broadcast inicial: marca com a "pintats" tots els tiles ocupats per
|
||||||
|
// decals de sòl (Hole, i futurs pinchos plans, plaques, charcos...).
|
||||||
|
// Imprescindible perquè el jugador no pot trepitjar aquests tiles
|
||||||
|
// (cauria al forat) i els sarcòfags vecins no podrien completar el
|
||||||
|
// perímetre — la sala seria incompletable. Usa el canal semàntic
|
||||||
|
// `tile_painted` (lògica de joc), no `on_tile_entered` (presència
|
||||||
|
// física del jugador). Ho fem aquí, després del bucle de spawns,
|
||||||
|
// perquè tots els sarcòfags ja estan registrats al fan-out (la
|
||||||
|
// subscripció del manager és al ctor, anterior a qualsevol load).
|
||||||
|
int decal_broadcast = 0;
|
||||||
|
for (const auto& entity : entities_) {
|
||||||
|
if (!entity->isFloorDecal()) { continue; }
|
||||||
|
const auto BOX = entity->getCollisionRect();
|
||||||
|
const auto [COL, ROW] = Map::pixelToTile(BOX.x, BOX.y);
|
||||||
|
bus_.publish(Event{
|
||||||
|
.name = "tile_painted",
|
||||||
|
.payload = TilePos{.col = COL, .row = ROW},
|
||||||
|
.player_id = -1,
|
||||||
|
});
|
||||||
|
++decal_broadcast;
|
||||||
|
}
|
||||||
|
if (decal_broadcast > 0) {
|
||||||
|
std::cout << "EntityManager: " << decal_broadcast
|
||||||
|
<< " tile_painted broadcast inicial (decals de sòl)\n";
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << "EntityManager: " << loaded << " entitats carregades des de "
|
std::cout << "EntityManager: " << loaded << " entitats carregades des de "
|
||||||
<< level_yaml_path << '\n';
|
<< level_yaml_path << '\n';
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "game/map/map.hpp"
|
#include "game/map/map.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
@@ -150,6 +151,13 @@ namespace Map {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto pixelToTile(float px, float py) -> std::pair<int, int> {
|
||||||
|
const auto TS = static_cast<float>(tile_size);
|
||||||
|
const int COL = static_cast<int>(std::floor((px - static_cast<float>(tilemap_origin_x)) / TS));
|
||||||
|
const int ROW = static_cast<int>(std::floor((py - static_cast<float>(tilemap_origin_y)) / TS));
|
||||||
|
return {COL, ROW};
|
||||||
|
}
|
||||||
|
|
||||||
auto makeInfo() -> Info {
|
auto makeInfo() -> Info {
|
||||||
return {
|
return {
|
||||||
.playfield_x = playfield_x,
|
.playfield_x = playfield_x,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
// Mapa d'una piràmide: sala rectangular amb marcador superior, muros pels
|
// Mapa d'una piràmide: sala rectangular amb marcador superior, muros pels
|
||||||
// quatre costats i graella de sarcòfags envoltada per un passadís a cada
|
// quatre costats i graella de sarcòfags envoltada per un passadís a cada
|
||||||
@@ -105,6 +106,17 @@ namespace Map {
|
|||||||
// usa el `PaintLayer` per filtrar quines cel·les accepten pintat.
|
// usa el `PaintLayer` per filtrar quines cel·les accepten pintat.
|
||||||
[[nodiscard]] auto isPaintable(int col, int row) -> bool;
|
[[nodiscard]] auto isPaintable(int col, int row) -> bool;
|
||||||
|
|
||||||
|
// Converteix una coordenada píxel del canvas en (col, row) del
|
||||||
|
// tilemap. Assumeix `tile_size > 0`; si la coordenada cau fora del
|
||||||
|
// playfield, retorna el tile encara que sigui negatiu o estigui fora
|
||||||
|
// de rang — el caller valida si li cal. El càlcul és el típic
|
||||||
|
// `floor((p - origin) / tile_size)` que apareixia en diversos llocs;
|
||||||
|
// factoritzat aquí per coherència. Consumidors que tinguen un
|
||||||
|
// snapshot de `Map::Info` (p.ex. el Player, decoupling COUP-02) no
|
||||||
|
// han d'usar aquesta funció — han de seguir treballant amb el
|
||||||
|
// snapshot per no re-acoblar-se al namespace global.
|
||||||
|
[[nodiscard]] auto pixelToTile(float px, float py) -> std::pair<int, int>;
|
||||||
|
|
||||||
// Snapshot plà de la geometria del mapa + punters a `collides` /
|
// Snapshot plà de la geometria del mapa + punters a `collides` /
|
||||||
// `insidePlayfield`. Pensat per ser passat per valor (POD petit,
|
// `insidePlayfield`. Pensat per ser passat per valor (POD petit,
|
||||||
// trivialment copiable). Un cop construïda, llegeix els valors
|
// trivialment copiable). Un cop construïda, llegeix els valors
|
||||||
|
|||||||
@@ -12,6 +12,27 @@
|
|||||||
#include "game/map/map.hpp"
|
#include "game/map/map.hpp"
|
||||||
#include "game/map/tilemap.hpp"
|
#include "game/map/tilemap.hpp"
|
||||||
|
|
||||||
|
// CONTRACTE BUS DE PINTAT
|
||||||
|
// -----------------------
|
||||||
|
// `on_tile_entered` — senyal FÍSICA: un jugador acaba d'entrar a un tile.
|
||||||
|
// La publica `Player::publishTileEnteredIfChanged`. La consumeix aquest
|
||||||
|
// `PaintLayer` per decidir si renderitzar el tile com a pintat (filtra
|
||||||
|
// per `Map::isPaintable` i aplica la regla "primer que pinta guanya").
|
||||||
|
//
|
||||||
|
// `tile_painted` — senyal LÒGICA: un tile compta com a pintat per a la
|
||||||
|
// lògica de joc (sarcòfags i, en el futur, HUD %completat, events
|
||||||
|
// derivats). La publica el `PaintLayer` quan confirma un pintat físic,
|
||||||
|
// i també la publica `EntityManager::loadFromLevel` com a broadcast
|
||||||
|
// inicial per als tiles ocupats per decals de sòl (Hole, etc.) — que
|
||||||
|
// el jugador mai podrà trepitjar perquè cauria al forat i els
|
||||||
|
// sarcòfags vecins necessiten que aquests tiles comptin com a pintats
|
||||||
|
// per completar el perímetre.
|
||||||
|
//
|
||||||
|
// REGLA: NO connectar `PaintLayer` al canal `tile_painted` per
|
||||||
|
// renderitzar. Si ho féssim, els broadcasts inicials de decals pintarien
|
||||||
|
// visualment els tiles d'agujero, incoherent amb la representació
|
||||||
|
// gràfica del decal.
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Blit d'un tile del tileset sobre un buffer ARGB de destí, respectant
|
// Blit d'un tile del tileset sobre un buffer ARGB de destí, respectant
|
||||||
|
|||||||
Reference in new issue
Block a user