Tilemap YAML: sala piràmide renderitzada amb tiles reals de tomb.gif i caixa de debug llevada (v0.13.0)

This commit is contained in:
JailDesigner committed 2026-04-19 19:35:21 +02:00
1 parent c921833537
commit 46a578fd86
12 files changed
+501 -45

No files matched your search

-18
View File
@@ -4,9 +4,7 @@
#include <algorithm>
#include <cmath>
#include <cstdint>
#include "core/rendering/screen.hpp"
#include "core/resources/resource_cache.hpp"
#include "game/map/map.hpp"
@@ -16,10 +14,6 @@ namespace {
// es pot convertir en un membre modificable.
constexpr float SPEED_PX_PER_S = 60.0F;
// Color de debug per a la caixa de col·lisió (ARGB opac). S'elimina
// quan el joc estiga afinat.
constexpr std::uint32_t DEBUG_COLLISION_ARGB = 0xFF00FFFFU;
} // namespace
Player::Player(float x, float y, const Controls& controls)
@@ -94,18 +88,6 @@ void Player::update(float delta_time) {
setPosY(std::clamp(getPosY(), MIN_Y, MAX_Y));
}
void Player::render() {
// Debug: pinta la caixa de col·lisió (16×16) per sota del sprite perquè
// es vegen els límits del mapa de colisions alineats amb els passadissos.
Screen::get().fillRect(
static_cast<int>(getPosX()) + COLLISION_X_OFFSET,
static_cast<int>(getPosY()) + COLLISION_Y_OFFSET,
COLLISION_SIZE,
COLLISION_SIZE,
DEBUG_COLLISION_ARGB);
AnimatedSprite::render();
}
auto Player::baseBox() const -> SDL_FRect {
return {
.x = getPosX() + static_cast<float>(COLLISION_X_OFFSET),
-1
View File
@@ -37,7 +37,6 @@ class Player : public AnimatedSprite {
Player(float x, float y, const Controls& controls);
void update(float delta_time) override;
void render() override;
// Y de la base del jugador (cara inferior de la caixa de colisió). Usada
// per al Z-order contra els sarcs: peus per sobre d'un sarc → darrere;
+3 -3
View File
@@ -18,9 +18,9 @@ class Map {
// Graella portada tal qual de l'AEE (4×4 sarcòfags, pitch 65×35).
static constexpr int GRID_COLS = 4;
static constexpr int GRID_ROWS = 5;
static constexpr int SARC_W = 50;
static constexpr int SARC_W = 48;
static constexpr int SARC_H = 16;
static constexpr int PITCH_X = 66;
static constexpr int PITCH_X = 64;
static constexpr int PITCH_Y = 32;
// El pas entre sarcòfags (15×15) és exactament el que ha de cabre el
@@ -39,7 +39,7 @@ class Map {
// pas de 320×200 → 320×240 i reforçar la falsa perspectiva.
static constexpr int HUD_H = 24;
static constexpr int BACK_WALL_H = 24;
static constexpr int SIDE_WALL_W = 20;
static constexpr int SIDE_WALL_W = 24;
// Origen del tauler dins del canvas (cantó superior-esquerra del primer
// sarcòfag). Derivat: mur + passadís en cada eix.
+138
View File
@@ -0,0 +1,138 @@
#include "game/map/tilemap.hpp"
#include <algorithm>
#include <cstddef>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <unordered_map>
#include <utility>
#include "core/rendering/screen.hpp"
#include "core/rendering/surface.hpp"
#include "core/resources/resource_cache.hpp"
#include "external/fkyaml_node.hpp"
namespace {
// Cada símbol del grid es mapeja a (col, fila) dins del tileset.
using TileCoord = std::pair<int, int>;
using Legend = std::unordered_map<char, TileCoord>;
auto splitLines(const std::string& text) -> std::vector<std::string> {
std::vector<std::string> lines;
std::istringstream iss(text);
std::string line;
while (std::getline(iss, line)) {
if (!line.empty()) { lines.push_back(line); }
}
return lines;
}
// La llegenda usa índexs lineals del tileset (esq→dre, dalt→baix).
// Els convertim a (col, fila) amb el nombre de tiles per fila del tileset.
auto parseLegend(const fkyaml::node& node, int tileset_cols) -> Legend {
Legend legend;
for (auto it = node.begin(); it != node.end(); ++it) {
const auto KEY_STR = it.key().get_value<std::string>();
if (KEY_STR.empty()) { continue; }
const auto INDEX = it.value().get_value<int>();
const int COL = INDEX % tileset_cols;
const int ROW = INDEX / tileset_cols;
legend[KEY_STR.front()] = {COL, ROW};
}
return legend;
}
// Copia un tile (col, fila) del tileset al buffer ARGB a la posició de
// destí (dest_x, dest_y). Els píxels amb l'índex de transparència del
// tileset es deixen com estan al buffer (alpha=0 per defecte).
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
Tilemap::Tilemap(const std::string& yaml_path) {
std::ifstream file(yaml_path);
if (!file.is_open()) {
throw std::runtime_error("Tilemap: no s'ha pogut obrir " + yaml_path);
}
auto yaml = fkyaml::node::deserialize(file);
const auto TILESET_PATH = yaml["tileset"].get_value<std::string>();
const auto TILE_SIZE = yaml["tile_size"].get_value<int>();
if (yaml.contains("origin")) {
origin_x_ = yaml["origin"][0].get_value<int>();
origin_y_ = yaml["origin"][1].get_value<int>();
}
// Carreguem el tileset al cache (idempotent) i el pillem per accedir
// als seus píxels i paleta. La paleta del tileset ve del GIF mateix.
// Ens cal abans de la llegenda per saber quants tiles per fila té.
const auto& cache_name = TILESET_PATH;
Resource::Cache::get().loadSurface(cache_name, TILESET_PATH);
const auto TILESET = Resource::Cache::get().getSurface(cache_name);
const int TILESET_COLS = TILESET->getWidth() / TILE_SIZE;
const auto LEGEND = parseLegend(yaml["legend"], TILESET_COLS);
const auto LINES = splitLines(yaml["data"].get_value<std::string>());
const int ROWS = static_cast<int>(LINES.size());
const int COLS = ROWS > 0 ? static_cast<int>(LINES.front().size()) : 0;
width_px_ = COLS * TILE_SIZE;
height_px_ = ROWS * TILE_SIZE;
pixels_.assign(
static_cast<std::size_t>(width_px_) * static_cast<std::size_t>(height_px_),
0U);
for (int row = 0; row < ROWS; ++row) {
const auto& line = LINES[row];
for (int col = 0; col < COLS && col < static_cast<int>(line.size()); ++col) {
const char SYMBOL = line[col];
const auto IT = LEGEND.find(SYMBOL);
if (IT == LEGEND.end()) { continue; }
blitTile(*TILESET, TILE_SIZE, IT->second.first, IT->second.second, pixels_, width_px_, col * TILE_SIZE, row * TILE_SIZE);
}
}
std::cout << "Tilemap: " << COLS << "x" << ROWS << " tiles ("
<< width_px_ << "x" << height_px_ << " px) des de " << yaml_path << '\n';
}
void Tilemap::render() const {
auto& screen = Screen::get();
auto* canvas = screen.getCanvasArgb();
const int CANVAS_W = screen.getCanvasWidth();
const int CANVAS_H = screen.getCanvasHeight();
// Clipping de files: només pintem les que cauen dins del canvas.
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_);
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 & 0xFF000000U) != 0U) { dst_row[x] = PIXEL; }
}
}
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
// Tilemap basat en tiles 16×16 (o la mida que indique el yaml). El mapa es
// prerenderitza una sola vegada a un buffer ARGB propi a la construcció; a
// partir d'aquí, cada render és una simple còpia de píxels clipada sobre el
// canvas del joc. Això evita recórrer la graella de tiles cada frame.
//
// Les cel·les sense tile (p.ex. les caselles dels sarcòfags, que es faran
// actors independents) es deixen transparents (alpha=0) i el canvas
// subjacent hi queda visible.
class Tilemap {
public:
// Carrega el yaml (camí relatiu o absolut), el tileset referenciat i
// prerenderitza la graella a memòria.
explicit Tilemap(const std::string& yaml_path);
// Dibuixa la textura prerenderitzada al canvas a l'origen del yaml.
// Offsets negatius permeten overscan (textura més gran que el canvas).
void render() const;
[[nodiscard]] auto getWidth() const -> int { return width_px_; }
[[nodiscard]] auto getHeight() const -> int { return height_px_; }
[[nodiscard]] auto getOriginX() const -> int { return origin_x_; }
[[nodiscard]] auto getOriginY() const -> int { return origin_y_; }
private:
int width_px_{0};
int height_px_{0};
int origin_x_{0};
int origin_y_{0};
std::vector<std::uint32_t> pixels_;
};
+11 -22
View File
@@ -18,7 +18,8 @@
#include "utils/defaults.hpp"
#include "utils/defines.hpp"
GameScene::GameScene() {
GameScene::GameScene()
: tilemap_("data/maps/pyramid.yaml") {
Resource::Cache::get().loadSurface("player.gif", "data/player/player.gif");
Resource::Cache::get().loadAnimation("player.yaml", "data/player/player.yaml");
@@ -59,9 +60,11 @@ void GameScene::render() {
const auto BG_ARGB = Resource::Cache::get().getPalette("ui")[UiColor::CANVAS_BG];
Screen::get().fillRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, BG_ARGB);
// Fons del mapa (marcador, muros, sòl). Els sarcòfags es pinten després
// fila a fila entrellaçats amb els jugadors per al Z-order.
Map::renderBackground();
// Mapa visual: textura prerenderitzada del tilemap. Els sarcòfags
// queden "foradats" (cel·les transparents) perquè passaran a ser
// actors independents; Map::collides encara els bloqueja per a les
// col·lisions del jugador.
tilemap_.render();
// Ordenem punters a jugadors per Y dels peus (ascendent = més al fons
// primer). Usem un vector auxiliar per no tocar l'ordre d'entrada de
@@ -73,24 +76,10 @@ void GameScene::render() {
return a->getFeetY() < b->getFeetY();
});
// Interleaving: per cada fila de sarcòfags, primer pintem els jugadors
// que hi queden "darrere" (peus per sobre de la cara inferior de la fila)
// i després la fila. Un jugador amb els peus davall d'una fila es pinta
// sobre d'ella (el seu cap tapa la fila superior si s'hi encavalca).
std::size_t p = 0;
for (int row = 0; row < Map::GRID_ROWS; ++row) {
const auto ROW_BOTTOM = static_cast<float>(Map::getSarcRowBottomY(row));
while (p < sorted.size() && sorted[p]->getFeetY() <= ROW_BOTTOM) {
sorted[p]->render();
++p;
}
Map::renderSarcsRow(row);
}
// Jugadors que queden per davall de l'última fila de sarcòfags.
while (p < sorted.size()) {
sorted[p]->render();
++p;
}
// De moment els sarcòfags no es pinten (seran actors independents);
// dibuixem tots els jugadors sobre el tilemap ja ordenats per Y. Quan
// els sarcs siguen sprites, tornarem a interleave per al Z-order.
for (auto* player : sorted) { player->render(); }
Notifier::get().render();
RenderInfo::get().render();
+3
View File
@@ -4,6 +4,7 @@
#include <vector>
#include "core/scene/scene.hpp"
#include "game/map/tilemap.hpp"
class Player;
@@ -16,6 +17,8 @@ class GameScene : public Scene {
void render() override;
private:
Tilemap tilemap_;
// Vector des del principi: AEE suportarà multijugador local; cada Player
// té els seus propis Controls i entra al vector amb una crida extra.
std::vector<std::unique_ptr<Player>> players_;