feat(map): afegeix PaintLayer com a font única del pintat
This commit is contained in:
1 parent
00eb696859
commit
87b08a7c7c
3 files changed
+207
No files matched your search
@@ -0,0 +1,134 @@
|
||||
#include "game/map/paint_layer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <variant>
|
||||
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
#include "game/map/tilemap.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
// Blit d'un tile del tileset sobre un buffer ARGB de destí, respectant
|
||||
// l'índex transparent del GIF. Mateixa convenció que la versió a
|
||||
// `tilemap.cpp`: el tileset guarda índexs + paleta, i la destinació
|
||||
// és un buffer ARGB32 lineal. Duplicat intencionat per no acoblar
|
||||
// Tilemap i PaintLayer; si en el futur un tercer consumidor el vol
|
||||
// reutilitzar, es promou a un helper compartit.
|
||||
void blitTile(const Surface& tileset, int tile_size, int src_col, int src_row, std::vector<std::uint32_t>& dest, int dest_w, int dest_x, int dest_y) {
|
||||
const int SRC_X0 = src_col * tile_size;
|
||||
const int SRC_Y0 = src_row * tile_size;
|
||||
const auto TRANSPARENT = tileset.getTransparentColor();
|
||||
for (int y = 0; y < tile_size; ++y) {
|
||||
for (int x = 0; x < tile_size; ++x) {
|
||||
const auto IDX = tileset.getPixel(SRC_X0 + x, SRC_Y0 + y);
|
||||
if (IDX == TRANSPARENT) { continue; }
|
||||
const auto ARGB = tileset.getPaletteColor(IDX);
|
||||
const auto DST_OFFSET =
|
||||
(static_cast<std::size_t>(dest_y + y) * static_cast<std::size_t>(dest_w)) + static_cast<std::size_t>(dest_x + x);
|
||||
dest[DST_OFFSET] = ARGB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PaintLayer::PaintLayer(const Tilemap& tilemap, EventBus* bus)
|
||||
: bus_(bus),
|
||||
cols_(tilemap.getCols()),
|
||||
rows_(tilemap.getRows()),
|
||||
tile_size_(tilemap.getTileSize()),
|
||||
origin_x_(tilemap.getOriginX()),
|
||||
origin_y_(tilemap.getOriginY()),
|
||||
width_px_(tilemap.getWidth()),
|
||||
height_px_(tilemap.getHeight()),
|
||||
tileset_cols_(tilemap.getTilesetCols()),
|
||||
tileset_path_(tilemap.getTilesetPath()) {
|
||||
painted_.assign(static_cast<std::size_t>(rows_), std::vector<int>(static_cast<std::size_t>(cols_), -1));
|
||||
pixels_.assign(
|
||||
static_cast<std::size_t>(width_px_) * static_cast<std::size_t>(height_px_),
|
||||
0U);
|
||||
|
||||
if (bus_ != nullptr) {
|
||||
// Subscripció a `on_tile_entered`: el Player publica aquest event
|
||||
// amb `PaintedTile{col, row, tile_index}`. Filtrem pintabilitat
|
||||
// (sòl transitable) via `Map::isPaintable` i, si aplica, marquem
|
||||
// i re-publiquem `tile_painted` amb només la posició.
|
||||
on_tile_sub_ = bus_->subscribe("on_tile_entered", [this](const Event& ev) {
|
||||
const auto* paint = std::get_if<PaintedTile>(&ev.payload);
|
||||
if (paint != nullptr) { tryPaint(paint->col, paint->row, paint->tile_index); }
|
||||
});
|
||||
}
|
||||
|
||||
std::cout << "PaintLayer: " << cols_ << "x" << rows_ << " tiles ("
|
||||
<< width_px_ << "x" << height_px_ << " px), tileset "
|
||||
<< tileset_path_ << '\n';
|
||||
}
|
||||
|
||||
PaintLayer::~PaintLayer() {
|
||||
if (bus_ != nullptr && on_tile_sub_ != 0) { bus_->unsubscribe(on_tile_sub_); }
|
||||
}
|
||||
|
||||
auto PaintLayer::isPainted(int col, int row) const -> bool {
|
||||
if (col < 0 || col >= cols_ || row < 0 || row >= rows_) { return false; }
|
||||
return painted_[static_cast<std::size_t>(row)][static_cast<std::size_t>(col)] >= 0;
|
||||
}
|
||||
|
||||
auto PaintLayer::tryPaint(int col, int row, int tile_index) -> bool {
|
||||
if (col < 0 || col >= cols_ || row < 0 || row >= rows_) { return false; }
|
||||
if (tile_index < 0) { return false; }
|
||||
if (painted_[static_cast<std::size_t>(row)][static_cast<std::size_t>(col)] >= 0) {
|
||||
// Primer que pinta guanya: no sobreescrivim.
|
||||
return false;
|
||||
}
|
||||
if (!Map::isPaintable(col, row)) { return false; }
|
||||
|
||||
painted_[static_cast<std::size_t>(row)][static_cast<std::size_t>(col)] = tile_index;
|
||||
blitPaintTile(col, row, tile_index);
|
||||
|
||||
if (bus_ != nullptr) {
|
||||
bus_->publish("tile_painted", TilePos{.col = col, .row = row});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PaintLayer::blitPaintTile(int col, int row, int tile_index) {
|
||||
const auto TILESET = Resource::Cache::get().getSurface(tileset_path_);
|
||||
if (TILESET == nullptr) { return; }
|
||||
const int DX = col * tile_size_;
|
||||
const int DY = row * tile_size_;
|
||||
const int SRC_COL = tile_index % tileset_cols_;
|
||||
const int SRC_ROW = tile_index / tileset_cols_;
|
||||
blitTile(*TILESET, tile_size_, SRC_COL, SRC_ROW, pixels_, width_px_, DX, DY);
|
||||
}
|
||||
|
||||
void PaintLayer::render() const {
|
||||
// Còpia ARGB al canvas, anàloga a `Tilemap::render`. Píxels amb
|
||||
// alpha=0 (tiles no pintats) es salten per deixar visible el tilemap
|
||||
// base per sota.
|
||||
auto& screen = Screen::get();
|
||||
auto* canvas = screen.getCanvasArgb();
|
||||
const int CANVAS_W = screen.getCanvasWidth();
|
||||
const int CANVAS_H = screen.getCanvasHeight();
|
||||
|
||||
const int Y_START = std::max(0, -origin_y_);
|
||||
const int Y_END = std::min(height_px_, CANVAS_H - origin_y_);
|
||||
const int X_START = std::max(0, -origin_x_);
|
||||
const int X_END = std::min(width_px_, CANVAS_W - origin_x_);
|
||||
|
||||
constexpr std::uint32_t ALPHA_MASK = 0xFF000000U;
|
||||
for (int y = Y_START; y < Y_END; ++y) {
|
||||
const auto* src_row = &pixels_[static_cast<std::size_t>(y) * static_cast<std::size_t>(width_px_)];
|
||||
auto* dst_row =
|
||||
&canvas[(static_cast<std::size_t>(origin_y_ + y) * static_cast<std::size_t>(CANVAS_W)) + static_cast<std::size_t>(origin_x_)];
|
||||
for (int x = X_START; x < X_END; ++x) {
|
||||
const auto PIXEL = src_row[x];
|
||||
if ((PIXEL & ALPHA_MASK) != 0U) { dst_row[x] = PIXEL; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "game/entities/event_bus.hpp"
|
||||
|
||||
class Tilemap;
|
||||
|
||||
// Capa visual de "pintat" sobre el tilemap base. Quan el jugador (o
|
||||
// qualsevol altre actor futur) publica `on_tile_entered` amb payload
|
||||
// `PaintedTile{col, row, tile_index}`, el PaintLayer filtra per
|
||||
// `Map::isPaintable`, marca el tile i re-publica `tile_painted` amb
|
||||
// només `TilePos` — abstracció del "el tile s'ha pintat" independent de
|
||||
// qui ho ha fet. Els sarcòfags escolten aquest segon event per comptar
|
||||
// el seu perímetre.
|
||||
//
|
||||
// Font única de veritat del pintat: evita duplicar l'estat dins de cada
|
||||
// sarcòfag i permet que el render mostri l'efecte al instant.
|
||||
//
|
||||
// Geometria: hereta `tile_size`, origen i dimensions del Tilemap
|
||||
// (mateixa graella). Reutilitza el tileset del Tilemap per blitejar els
|
||||
// tiles pintats a un buffer ARGB propi, anàleg a `Tilemap::pixels_`.
|
||||
//
|
||||
// Política de repintat: el primer que pinta un tile guanya. Intents
|
||||
// posteriors (amb el mateix índex o un altre) no tenen efecte —
|
||||
// garanteix coherència visual i evita notificar dos cops al mateix sarc.
|
||||
class PaintLayer {
|
||||
public:
|
||||
PaintLayer(const Tilemap& tilemap, EventBus* bus);
|
||||
~PaintLayer();
|
||||
|
||||
PaintLayer(const PaintLayer&) = delete;
|
||||
PaintLayer(PaintLayer&&) = delete;
|
||||
auto operator=(const PaintLayer&) -> PaintLayer& = delete;
|
||||
auto operator=(PaintLayer&&) -> PaintLayer& = delete;
|
||||
|
||||
// Còpia del buffer ARGB sobre el canvas del Screen, respectant alpha
|
||||
// (píxels no pintats queden al buffer amb alpha=0 i no s'escriuen).
|
||||
// Pensat per cridar entre `Tilemap::render()` i el bloc d'actors.
|
||||
void render() const;
|
||||
|
||||
// Intent de pintat directe (exposat per a tests i actors que pinten
|
||||
// sense passar pel bus). Retorna true si el tile és pintable i no
|
||||
// estava ja pintat; en aquest cas, publica `tile_painted`.
|
||||
auto tryPaint(int col, int row, int tile_index) -> bool;
|
||||
|
||||
[[nodiscard]] auto isPainted(int col, int row) const -> bool;
|
||||
|
||||
private:
|
||||
void blitPaintTile(int col, int row, int tile_index);
|
||||
|
||||
EventBus* bus_{nullptr};
|
||||
EventBus::SubId on_tile_sub_{0};
|
||||
|
||||
int cols_{0};
|
||||
int rows_{0};
|
||||
int tile_size_{0};
|
||||
int origin_x_{0};
|
||||
int origin_y_{0};
|
||||
int width_px_{0};
|
||||
int height_px_{0};
|
||||
int tileset_cols_{0};
|
||||
std::string tileset_path_;
|
||||
|
||||
// `-1` = no pintat. Altres valors = índex de tile del tileset usat
|
||||
// per pintar. No cal per al render (el buffer `pixels_` ja el conté),
|
||||
// però facilita consultes ràpides d'estat i futures extensions.
|
||||
std::vector<std::vector<int>> painted_;
|
||||
std::vector<std::uint32_t> pixels_;
|
||||
};
|
||||
Reference in new issue
Block a user