treballant en el minimapa

This commit is contained in:
2026-04-02 18:46:28 +02:00
parent 3c3e012386
commit c14774478c
5 changed files with 419 additions and 2 deletions

View File

@@ -101,6 +101,7 @@ set(APP_SOURCES
source/game/editor/editor_statusbar.cpp source/game/editor/editor_statusbar.cpp
source/game/editor/room_saver.cpp source/game/editor/room_saver.cpp
source/game/editor/tile_picker.cpp source/game/editor/tile_picker.cpp
source/game/editor/mini_map.cpp
# Game - UI # Game - UI
source/game/ui/console.cpp source/game/ui/console.cpp

View File

@@ -100,6 +100,13 @@ auto MapEditor::showGrid(bool show) -> std::string {
return show ? "Grid ON" : "Grid OFF"; return show ? "Grid ON" : "Grid OFF";
} }
void MapEditor::toggleMiniMap() {
if (!mini_map_) {
mini_map_ = std::make_unique<MiniMap>();
}
mini_map_visible_ = !mini_map_visible_;
}
// Entra en modo editor // Entra en modo editor
void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data) { void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data) {
if (active_) { return; } if (active_) { return; }
@@ -285,9 +292,11 @@ void MapEditor::render() {
// Renderizar highlight de selección (encima de los sprites) // Renderizar highlight de selección (encima de los sprites)
renderSelectionHighlight(); renderSelectionHighlight();
// Tile picker (encima de todo en el play area) // Tile picker o mini mapa (encima de todo en el play area)
if (tile_picker_.isOpen()) { if (tile_picker_.isOpen()) {
tile_picker_.render(); tile_picker_.render();
} else if (mini_map_visible_ && mini_map_) {
mini_map_->render(room_path_);
} }
// Renderizar barra de estado del editor (reemplaza al scoreboard) // Renderizar barra de estado del editor (reemplaza al scoreboard)
@@ -304,6 +313,20 @@ void MapEditor::handleEvent(const SDL_Event& event) {
return; return;
} }
// Si el mini mapa está visible, cerrarlo con click o ESC
if (mini_map_visible_) {
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
(event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE)) {
mini_map_visible_ = false;
return;
}
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_M) {
mini_map_visible_ = false;
return;
}
return; // Bloquear otros eventos mientras el minimapa está visible
}
// ESC: desactivar brush // ESC: desactivar brush
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && brush_tile_ != NO_BRUSH) { if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && brush_tile_ != NO_BRUSH) {
brush_tile_ = NO_BRUSH; brush_tile_ = NO_BRUSH;
@@ -316,6 +339,12 @@ void MapEditor::handleEvent(const SDL_Event& event) {
return; return;
} }
// M: toggle mini mapa
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_M && static_cast<int>(event.key.repeat) == 0) {
toggleMiniMap();
return;
}
// Click derecho: abrir TilePicker del mapa // Click derecho: abrir TilePicker del mapa
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_RIGHT) { if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_RIGHT) {
// Deseleccionar entidades // Deseleccionar entidades

View File

@@ -8,6 +8,7 @@
#include <string> // Para string #include <string> // Para string
#include "external/fkyaml_node.hpp" // Para fkyaml::node #include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/editor/mini_map.hpp" // Para MiniMap
#include "game/editor/tile_picker.hpp" // Para TilePicker #include "game/editor/tile_picker.hpp" // Para TilePicker
#include "game/entities/enemy.hpp" // Para Enemy::Data #include "game/entities/enemy.hpp" // Para Enemy::Data
#include "game/entities/item.hpp" // Para Item::Data #include "game/entities/item.hpp" // Para Item::Data
@@ -47,6 +48,7 @@ class MapEditor {
auto showInfo(bool show) -> std::string; auto showInfo(bool show) -> std::string;
auto showGrid(bool show) -> std::string; auto showGrid(bool show) -> std::string;
[[nodiscard]] auto isGridEnabled() const -> bool { return settings_.grid; } [[nodiscard]] auto isGridEnabled() const -> bool { return settings_.grid; }
void toggleMiniMap();
// Comandos para items // Comandos para items
auto setItemProperty(const std::string& property, const std::string& value) -> std::string; auto setItemProperty(const std::string& property, const std::string& value) -> std::string;
@@ -130,8 +132,10 @@ class MapEditor {
// Barra de estado del editor // Barra de estado del editor
std::unique_ptr<EditorStatusBar> statusbar_; std::unique_ptr<EditorStatusBar> statusbar_;
// Tile picker (para seleccionar tiles de un tileset) // Tile picker y mini mapa
TilePicker tile_picker_; TilePicker tile_picker_;
std::unique_ptr<MiniMap> mini_map_;
bool mini_map_visible_{false};
// Estado del ratón // Estado del ratón
float mouse_game_x_{0.0F}; float mouse_game_x_{0.0F};

View File

@@ -0,0 +1,298 @@
#ifdef _DEBUG
#include "game/editor/mini_map.hpp"
#include <algorithm> // Para std::max, std::min
#include <array> // Para std::array
#include <iostream> // Para cout
#include <map> // Para std::map
#include <queue> // Para queue (BFS)
#include <set> // Para set
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/surface.hpp" // Para Surface
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
#include "game/gameplay/room.hpp" // Para Room::Data
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
#include "utils/utils.hpp" // Para stringToColor
// Constructor: construye todo el minimapa
MiniMap::MiniMap() {
buildTileColorTable("standard.gif");
layoutRooms();
buildRoomSurfaces();
composeFinalSurface();
}
// Analiza el tileset y crea tabla: tile_index → color predominante
void MiniMap::buildTileColorTable(const std::string& tileset_name) {
auto tileset = Resource::Cache::get()->getSurface(tileset_name);
if (!tileset) { return; }
tileset_width_ = static_cast<int>(tileset->getWidth()) / Tile::SIZE;
int tileset_height = static_cast<int>(tileset->getHeight()) / Tile::SIZE;
int total_tiles = tileset_width_ * tileset_height;
tile_colors_.resize(total_tiles, 0);
for (int tile = 0; tile < total_tiles; ++tile) {
int tile_x = (tile % tileset_width_) * Tile::SIZE;
int tile_y = (tile / tileset_width_) * Tile::SIZE;
// Contar frecuencia de cada color en el tile (ignorar transparente=255 y negro=0)
std::array<int, 256> freq{};
for (int y = 0; y < Tile::SIZE; ++y) {
for (int x = 0; x < Tile::SIZE; ++x) {
Uint8 pixel = tileset->getPixel(tile_x + x, tile_y + y);
if (pixel != 0 && pixel != 255) {
freq[pixel]++;
}
}
}
// Encontrar el color más frecuente
Uint8 best_color = 0;
int best_count = 0;
for (int c = 1; c < 256; ++c) {
if (freq[c] > best_count) {
best_count = freq[c];
best_color = static_cast<Uint8>(c);
}
}
tile_colors_[tile] = best_color;
}
}
// Posiciona las rooms en un grid usando BFS desde las conexiones
void MiniMap::layoutRooms() {
auto& rooms = Resource::Cache::get()->getRooms();
if (rooms.empty()) { return; }
// Mapa de nombre → Room::Data
std::unordered_map<std::string, std::shared_ptr<Room::Data>> room_map;
for (const auto& r : rooms) {
room_map[r.name] = r.room;
}
// BFS para posicionar rooms
std::set<std::string> visited;
std::queue<std::pair<std::string, GridPos>> bfs;
// Empezar por la primera room
const std::string& start = rooms[0].name;
bfs.push({start, {0, 0}});
visited.insert(start);
// Grid ocupado: posición → nombre de room
std::map<std::pair<int, int>, std::string> grid_occupied;
while (!bfs.empty()) {
auto [name, pos] = bfs.front();
bfs.pop();
auto key = std::make_pair(pos.x, pos.y);
if (grid_occupied.contains(key)) { continue; }
grid_occupied[key] = name;
room_positions_[name] = RoomMini{.surface = nullptr, .pos = pos};
auto it = room_map.find(name);
if (it == room_map.end()) { continue; }
const auto& data = it->second;
// Vecinos: up, down, left, right
struct Neighbor {
std::string room;
int dx, dy;
};
std::array<Neighbor, 4> neighbors = {{
{data->upper_room, 0, -1},
{data->lower_room, 0, 1},
{data->left_room, -1, 0},
{data->right_room, 1, 0},
}};
for (const auto& [neighbor_name, dx, dy] : neighbors) {
if (neighbor_name == "0" || neighbor_name.empty()) { continue; }
if (visited.contains(neighbor_name)) { continue; }
GridPos neighbor_pos = {pos.x + dx, pos.y + dy};
auto nkey = std::make_pair(neighbor_pos.x, neighbor_pos.y);
if (!grid_occupied.contains(nkey)) {
visited.insert(neighbor_name);
bfs.push({neighbor_name, neighbor_pos});
}
}
}
// Calcular bounds del grid
min_grid_x_ = 0;
min_grid_y_ = 0;
int max_grid_x = 0;
int max_grid_y = 0;
for (const auto& [name, mini] : room_positions_) {
min_grid_x_ = std::min(min_grid_x_, mini.pos.x);
min_grid_y_ = std::min(min_grid_y_, mini.pos.y);
max_grid_x = std::max(max_grid_x, mini.pos.x);
max_grid_y = std::max(max_grid_y, mini.pos.y);
}
int cols = max_grid_x - min_grid_x_ + 1;
int rows = max_grid_y - min_grid_y_ + 1;
map_width_ = cols * (CELL_W + GAP) - GAP + PADDING * 2;
map_height_ = rows * (CELL_H + GAP) - GAP + PADDING * 2;
std::cout << "MiniMap: " << room_positions_.size() << " rooms, grid " << cols << "x" << rows
<< "" << map_width_ << "x" << map_height_ << " px\n";
}
// Genera una mini-surface de 32x16 por room
void MiniMap::buildRoomSurfaces() {
for (auto& [name, mini] : room_positions_) {
mini.surface = getRoomMiniSurface(name);
}
}
// Genera la mini-surface de una room: 1 pixel por tile, color predominante
auto MiniMap::getRoomMiniSurface(const std::string& room_name) -> std::shared_ptr<Surface> {
auto room_data = Resource::Cache::get()->getRoom(room_name);
if (!room_data) { return nullptr; }
auto surface = std::make_shared<Surface>(ROOM_W, ROOM_H);
auto prev = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface);
surface->clear(stringToColor(room_data->bg_color));
const auto& tile_map = room_data->tile_map;
for (int y = 0; y < ROOM_H; ++y) {
for (int x = 0; x < ROOM_W; ++x) {
int index = y * ROOM_W + x;
if (index >= static_cast<int>(tile_map.size())) { continue; }
int tile = tile_map[index];
if (tile < 0 || tile >= static_cast<int>(tile_colors_.size())) { continue; }
Uint8 color = tile_colors_[tile];
if (color != 0) {
surface->putPixel(x, y, color);
}
}
}
Screen::get()->setRendererSurface(prev);
return surface;
}
// Compone la surface final con todas las rooms posicionadas
void MiniMap::composeFinalSurface() {
if (map_width_ <= 0 || map_height_ <= 0) { return; }
// Surface un poco más grande para la sombra del borde inferior/derecho
map_surface_ = std::make_shared<Surface>(map_width_ + SHADOW_OFFSET, map_height_ + SHADOW_OFFSET);
auto prev = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(map_surface_);
// 1. Fondo general
map_surface_->clear(COLOR_BACKGROUND);
// 2. Líneas de conexión entre rooms (debajo de todo)
drawConnections();
// 3. Sombras de las rooms (desplazadas 1px abajo-derecha)
for (const auto& [name, mini] : room_positions_) {
if (!mini.surface) { continue; }
int px = cellPixelX(mini.pos.x) + SHADOW_OFFSET;
int py = cellPixelY(mini.pos.y) + SHADOW_OFFSET;
SDL_FRect shadow = {.x = static_cast<float>(px), .y = static_cast<float>(py),
.w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
map_surface_->fillRect(&shadow, COLOR_SHADOW);
}
// 4. Borde negro de cada room + contenido de la room
for (const auto& [name, mini] : room_positions_) {
if (!mini.surface) { continue; }
int px = cellPixelX(mini.pos.x);
int py = cellPixelY(mini.pos.y);
// Borde negro (la celda entera)
SDL_FRect cell = {.x = static_cast<float>(px), .y = static_cast<float>(py),
.w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
map_surface_->fillRect(&cell, COLOR_ROOM_BORDER);
// Miniroom dentro del borde
SDL_FRect dst = {.x = static_cast<float>(px + BORDER), .y = static_cast<float>(py + BORDER),
.w = static_cast<float>(ROOM_W), .h = static_cast<float>(ROOM_H)};
mini.surface->render(nullptr, &dst);
}
Screen::get()->setRendererSurface(prev);
}
// Dibuja las líneas de conexión entre rooms vecinas
void MiniMap::drawConnections() {
for (const auto& [name, mini] : room_positions_) {
auto room_data = Resource::Cache::get()->getRoom(name);
if (!room_data) { continue; }
int px = cellPixelX(mini.pos.x);
int py = cellPixelY(mini.pos.y);
// Conexión derecha: línea horizontal desde el borde derecho de esta room hasta el borde izquierdo de la vecina
if (room_data->right_room != "0" && !room_data->right_room.empty() && room_positions_.contains(room_data->right_room)) {
int x1 = px + CELL_W;
int y_mid = py + CELL_H / 2;
SDL_FRect line = {.x = static_cast<float>(x1), .y = static_cast<float>(y_mid),
.w = static_cast<float>(GAP), .h = 1.0F};
map_surface_->fillRect(&line, COLOR_CONNECTION);
}
// Conexión abajo: línea vertical desde el borde inferior de esta room hasta el borde superior de la vecina
if (room_data->lower_room != "0" && !room_data->lower_room.empty() && room_positions_.contains(room_data->lower_room)) {
int x_mid = px + CELL_W / 2;
int y1 = py + CELL_H;
SDL_FRect line = {.x = static_cast<float>(x_mid), .y = static_cast<float>(y1),
.w = 1.0F, .h = static_cast<float>(GAP)};
map_surface_->fillRect(&line, COLOR_CONNECTION);
}
}
}
// Renderiza el minimapa centrado en la room actual
void MiniMap::render(const std::string& current_room) {
if (!map_surface_) { return; }
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
// Fondo negro en el play area
SDL_FRect bg = {.x = 0, .y = 0, .w = static_cast<float>(PlayArea::WIDTH), .h = static_cast<float>(PlayArea::HEIGHT)};
game_surface->fillRect(&bg, 0);
// Centrar el minimapa en el play area, centrado en la room actual
auto it = room_positions_.find(current_room);
if (it == room_positions_.end()) { return; }
const auto& current_pos = it->second.pos;
int room_cx = cellPixelX(current_pos.x) + CELL_W / 2;
int room_cy = cellPixelY(current_pos.y) + CELL_H / 2;
int offset_x = PlayArea::WIDTH / 2 - room_cx;
int offset_y = PlayArea::HEIGHT / 2 - room_cy;
// Renderizar la surface del minimapa con offset
SDL_FRect dst = {.x = static_cast<float>(offset_x), .y = static_cast<float>(offset_y),
.w = static_cast<float>(map_width_ + SHADOW_OFFSET), .h = static_cast<float>(map_height_ + SHADOW_OFFSET)};
map_surface_->render(nullptr, &dst);
// Highlight de la room actual (borde bright_white alrededor de la celda)
float cur_x = static_cast<float>(offset_x + cellPixelX(current_pos.x));
float cur_y = static_cast<float>(offset_y + cellPixelY(current_pos.y));
SDL_FRect highlight = {.x = cur_x - 1, .y = cur_y - 1,
.w = static_cast<float>(CELL_W + 2), .h = static_cast<float>(CELL_H + 2)};
game_surface->drawRectBorder(&highlight, stringToColor("bright_white"));
}
#endif // _DEBUG

View File

@@ -0,0 +1,85 @@
#pragma once
#ifdef _DEBUG
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <unordered_map> // Para unordered_map
#include <vector> // Para vector
class Surface;
/**
* @brief Minimapa global del juego para el editor
*
* Genera una vista en miniatura de todas las habitaciones del juego,
* posicionadas según sus conexiones.
* Cada tile del mapa se representa como 1 pixel del color predominante de ese tile.
* Resultado: cada room = 32x16 pixels.
*/
class MiniMap {
public:
MiniMap();
~MiniMap() = default;
void render(const std::string& current_room); // Dibuja el minimapa centrado en la room actual
[[nodiscard]] auto isReady() const -> bool { return !room_positions_.empty(); }
private:
// Posición de una room en el grid del minimapa
struct GridPos {
int x{0};
int y{0};
};
// Una room renderizada
struct RoomMini {
std::shared_ptr<Surface> surface; // 32x16 pixels
GridPos pos; // Posición en el grid
};
void buildTileColorTable(const std::string& tileset_name);
void buildRoomSurfaces();
void layoutRooms();
void composeFinalSurface();
auto getRoomMiniSurface(const std::string& room_name) -> std::shared_ptr<Surface>;
void drawConnections();
auto cellPixelX(int grid_x) const -> int { return PADDING + (grid_x - min_grid_x_) * (CELL_W + GAP); }
auto cellPixelY(int grid_y) const -> int { return PADDING + (grid_y - min_grid_y_) * (CELL_H + GAP); }
// Tabla de color predominante por tile index
std::vector<Uint8> tile_colors_; // tile_index → palette color index
int tileset_width_{0}; // Ancho del tileset en tiles
// Rooms renderizadas y posicionadas
std::unordered_map<std::string, RoomMini> room_positions_;
// Surface final compuesta
std::shared_ptr<Surface> map_surface_;
int map_width_{0}; // Ancho en pixels
int map_height_{0}; // Alto en pixels
// Offset para normalizar coordenadas
int min_grid_x_{0};
int min_grid_y_{0};
// Constantes
static constexpr int ROOM_W = 32; // Ancho de una room en pixels del minimapa
static constexpr int ROOM_H = 16; // Alto de una room en pixels del minimapa
static constexpr int BORDER = 1; // Borde alrededor de cada room
static constexpr int CELL_W = ROOM_W + BORDER * 2; // Room + borde
static constexpr int CELL_H = ROOM_H + BORDER * 2;
static constexpr int GAP = 4; // Separación entre celdas
static constexpr int SHADOW_OFFSET = 1; // Desplazamiento de la sombra
static constexpr int PADDING = 4; // Padding alrededor del minimapa
// Colores del minimapa (índices de paleta)
static constexpr Uint8 COLOR_BACKGROUND = 2; // Fondo general (blue, si border es 0 usamos 2)
static constexpr Uint8 COLOR_ROOM_BORDER = 0; // Borde de cada miniroom
static constexpr Uint8 COLOR_SHADOW = 1; // Sombra de cada miniroom
static constexpr Uint8 COLOR_CONNECTION = 14; // Líneas de conexión entre rooms
};
#endif // _DEBUG