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

+1
View File
@@ -56,6 +56,7 @@ set(APP_SOURCES
source/game/scenes/game_scene.cpp
source/game/entities/player.cpp
source/game/map/map.cpp
source/game/map/tilemap.cpp
)
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
+49
View File
@@ -0,0 +1,49 @@
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles
# en el tilemap però només 1.5 visibles al canvas, així que la textura es
# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240.
#
# Símbols del grid:
# W = paret (tile 99 del tileset, o (3,3) en 0-indexed)
# F = sòl (llista = un tile aleatori entre aquests per cada casella)
# . = cap tile (els sarcòfags seran actors, ací deixem transparent)
#
# Índexs lineals: els tiles van d'esquerra a dreta, de dalt a baix. Amb un
# tileset de 512×256 i tiles de 16×16 hi ha 32 tiles per fila.
tileset: data/tilesets/tomb.gif
tile_size: 16
origin: [-8, 0]
legend:
W: 292
X: 169
Y: 1
Z: 202
T: 200
R: 262
G: 263
H: 230
I: 231
J: 233
A: 2
B: 34
C: 33
D: 32
data: |
YYYYYYYYYYYYYYYYYYYYY
YHJJJJJJJJJJJJJJJJJIY
YZWWWWWWWWWWWWWWWWWTY
YZDDADDBBDDADDDDBDDTY
YZD...D...D...B...DTY
YZDDDCCDDDDDADDDBDDTY
YZD...D...A...D...DTY
YZDDCCDDDDDADDBDDDDTY
YZD...D...D...D...DTY
YZDDDDCCDDDDADDDDDDTY
YZD...D...D...D...DTY
YZDDDABDDDDADCDDBDDTY
YZD...D...D...D...DTY
YZDDBBDDADDDBCDDDDDTY
YRXXXXXXXXXXXXXXXXXGY
+259
View File
@@ -0,0 +1,259 @@
JASC-PAL
0100
256
255 255 255
247 247 247
239 239 239
231 231 231
223 223 223
215 215 215
207 207 207
199 199 199
191 191 191
183 183 183
175 175 175
167 167 167
159 159 159
151 151 151
143 143 143
135 135 135
127 127 127
119 119 119
111 111 111
103 103 103
95 95 95
87 87 87
79 79 79
71 71 71
63 63 63
55 55 55
47 47 47
39 39 39
31 31 31
23 23 23
15 15 15
7 7 7
0 0 0
191 167 143
183 160 136
175 152 128
167 144 120
159 137 113
150 129 105
142 121 97
134 114 90
126 106 82
117 98 74
109 90 66
101 83 59
93 75 51
84 67 43
76 60 36
67 51 27
191 123 75
179 115 71
171 111 67
163 107 63
155 99 59
143 95 55
135 87 51
127 83 47
119 79 43
107 71 39
95 67 35
83 63 31
75 55 27
63 47 23
51 43 19
43 35 15
255 235 223
255 227 211
255 219 199
255 211 187
255 207 179
255 199 167
255 191 155
255 187 147
255 179 131
247 171 123
239 163 115
231 155 107
223 147 99
215 139 91
207 131 83
203 127 79
255 238 220
255 220 185
255 203 151
255 185 117
255 168 85
255 151 54
255 134 25
255 117 0
243 109 0
229 101 0
216 93 0
203 85 0
190 77 0
177 69 0
164 61 0
151 54 0
255 255 239
255 255 207
255 255 175
255 255 143
255 255 111
255 255 79
255 255 47
255 255 15
255 255 0
207 207 0
175 175 0
143 143 0
111 111 0
79 79 0
47 47 0
15 15 0
255 255 115
235 219 87
215 187 67
195 155 47
175 123 31
155 91 19
135 67 7
115 43 0
255 223 223
255 191 191
255 159 159
255 127 127
255 95 95
255 63 63
255 31 31
255 0 0
239 0 0
223 0 0
207 0 0
191 0 0
175 0 0
159 0 0
143 0 0
127 0 0
111 0 0
95 0 0
79 0 0
63 0 0
47 0 0
31 0 0
15 0 0
255 183 183
243 163 163
231 143 143
219 123 123
203 107 107
191 91 91
179 79 79
167 63 63
142 46 0
134 39 0
126 32 0
117 25 0
109 18 0
101 11 0
93 5 0
85 0 0
119 255 79
112 240 75
105 224 70
97 208 65
90 192 60
82 176 55
75 160 50
67 144 45
60 128 40
53 112 35
45 96 30
38 80 25
30 64 20
23 48 15
15 32 10
7 15 4
222 255 168
199 228 148
173 200 128
149 173 107
124 146 88
100 119 68
74 90 48
50 63 29
0 255 0
0 223 0
0 191 0
0 159 0
0 127 0
0 95 0
0 63 0
0 31 0
255 111 255
255 0 255
223 0 223
191 0 191
159 0 159
127 0 127
95 0 95
63 0 63
233 233 243
196 196 225
157 157 206
119 119 187
84 84 167
65 65 131
46 46 92
27 27 52
213 241 255
191 235 255
170 227 255
149 221 255
128 214 255
106 207 255
85 200 255
63 191 255
55 157 223
47 143 191
39 119 159
31 95 127
0 191 191
0 127 127
0 95 95
0 63 63
231 231 255
198 198 255
173 173 255
140 140 255
115 115 255
82 82 255
49 49 255
24 24 255
0 0 255
0 0 231
0 0 206
0 0 181
0 0 156
0 0 132
0 0 107
0 0 82
0 0 79
0 0 63
0 0 55
0 0 39
0 0 31
0 0 15
0 0 7
0 255 255
207 127 207
183 111 183
159 95 159
135 79 135
111 63 111
87 47 87
63 31 63
39 15 39
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

-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_;
+1 -1
View File
@@ -3,7 +3,7 @@
namespace Texts {
constexpr const char* WINDOW_TITLE = "© 2026 Aventures en Egipte — JailDesigner";
constexpr const char* COPYRIGHT = "© 2026 JailDesigner";
constexpr const char* VERSION = "0.12.0"; // Single source of truth — Makefile el grep
constexpr const char* VERSION = "0.13.0"; // Single source of truth — Makefile el grep
} // namespace Texts
namespace GameCanvas {