feat(editor): hand pick per base, z-sort, amaga entitats en TILE, HUD 2-files centrat
This commit is contained in:
1 parent
c0362abf67
commit
a254fdd669
4 files changed
+94
-35
No files matched your search
@@ -6,11 +6,11 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#include "core/input/key_config.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/rendering/hud_constants.hpp"
|
||||
#include "core/rendering/notification_presets.hpp"
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/rendering/surface.hpp"
|
||||
@@ -180,14 +180,17 @@ namespace {
|
||||
void Editor::enter() {
|
||||
if (active_) { return; }
|
||||
active_ = true;
|
||||
// Estat de novella: tornem a Mode::TILE sense brush ni picker. Els
|
||||
// brushs (tile/entity) i la selecció de la mà no es preserven entre
|
||||
// sessions del editor: cada entrada parteix net.
|
||||
mode_ = Mode::TILE;
|
||||
// Estat de novella: comencem en mode HAND (selecció/manipulació
|
||||
// d'entitats existents) — el cas d'ús més habitual quan obres
|
||||
// l'editor és inspeccionar/moure/borrar el que ja hi ha. Els
|
||||
// brushs i la selecció no es preserven entre sessions: cada
|
||||
// entrada parteix net.
|
||||
mode_ = Mode::HAND;
|
||||
picker_ = Picker::NONE;
|
||||
brush_tile_ = -1;
|
||||
brush_entity_.clear();
|
||||
hand_selected_idx_ = -1;
|
||||
hand_hover_idx_ = -1;
|
||||
ensureEditableEntities();
|
||||
ensureHudFont();
|
||||
Notification::notify(Locale::get().text("input.editor_on"));
|
||||
@@ -544,15 +547,20 @@ void Editor::updateEntityBrush(EntityManager& entities, int /*canvas_x*/, int /*
|
||||
|
||||
namespace {
|
||||
|
||||
// Index de l'entitat sota el píxel donat. -1 si cap. Usem el rect de
|
||||
// pintat (`getPixelRect`) i no el de col·lisió perquè el dissenyador
|
||||
// espera "fer clic on es veu", encara que el cos físic sigui més
|
||||
// estret (típic en sarc/porta amb falsa perspectiva).
|
||||
// Index de l'entitat sota el píxel donat. -1 si cap. Usem el rect
|
||||
// de col·lisió (caixa base alineada al tilemap) i NO el sprite
|
||||
// sencer: a entitats amb falsa perspectiva (mòmia 16×32, sarc
|
||||
// 48×32) la part alta del sprite és "altura visual" que tapa
|
||||
// cel·les transitables darrere. Si el hit-test usés el sprite
|
||||
// sencer, no es podria seleccionar/col·locar res a aquelles
|
||||
// cel·les. Per a entitats sense caixa explícita (torch, hole),
|
||||
// `getCollisionRect` retorna el `pixel_rect` complet, així que el
|
||||
// criteri segueix sent intuïtiu ("clic on es veu").
|
||||
auto entityAtPixel(const std::vector<Entity*>& list, int px, int py) -> int {
|
||||
const auto FX = static_cast<float>(px);
|
||||
const auto FY = static_cast<float>(py);
|
||||
for (int i = static_cast<int>(list.size()) - 1; i >= 0; --i) {
|
||||
const auto R = list[i]->getPixelRect();
|
||||
const auto R = list[i]->getCollisionRect();
|
||||
if (FX >= R.x && FX < R.x + R.w && FY >= R.y && FY < R.y + R.h) { return i; }
|
||||
}
|
||||
return -1;
|
||||
@@ -662,8 +670,13 @@ void Editor::deleteSelected(EntityManager& entities) {
|
||||
void Editor::renderEditorHud(const EntityManager& entities) const {
|
||||
if (hud_font_ == nullptr || Map::hud_h <= 0) { return; }
|
||||
auto& screen = Screen::get();
|
||||
const auto& pal = Resource::Cache::get().getPalette("ui");
|
||||
screen.fillRect(0, 0, GameCanvas::WIDTH, Map::hud_h, pal[UiConfig::get().hudBg()]);
|
||||
|
||||
// Fons opcional: respectem el flag global del HUD del joc per a
|
||||
// coherència visual quan el dissenyador alterna entre joc i editor.
|
||||
if (UiConfig::get().hudBgEnabled()) {
|
||||
const auto& pal = Resource::Cache::get().getPalette("ui");
|
||||
screen.fillRect(0, 0, GameCanvas::WIDTH, Map::hud_h, pal[UiConfig::get().hudBg()]);
|
||||
}
|
||||
|
||||
std::string mode_str;
|
||||
switch (mode_) {
|
||||
@@ -700,28 +713,55 @@ void Editor::renderEditorHud(const EntityManager& entities) const {
|
||||
|
||||
const auto LBL = UiConfig::get().hudLabel();
|
||||
const auto VAL = UiConfig::get().hudValue();
|
||||
const auto FLASH = UiConfig::get().hudFlash();
|
||||
const int KERN = UiConfig::get().hudFont().kerning;
|
||||
const int Y = (Map::hud_h - Rendering::Hud::FONT_SIZE_PX) / 2;
|
||||
int x = 2;
|
||||
// Mateixos Y que el HUD del joc per simetria visual: línia 1 = 4,
|
||||
// línia 2 = 13. La font és proporcional, així que mesurem cada línia
|
||||
// sencera abans de centrar-la.
|
||||
constexpr int LINE1_Y = 4;
|
||||
constexpr int LINE2_Y = 13;
|
||||
constexpr int W = GameCanvas::WIDTH;
|
||||
|
||||
auto draw = [&](const std::string& s, std::uint8_t color) {
|
||||
hud_font_->writeColored(x, Y, s, color, KERN);
|
||||
x += hud_font_->length(s, KERN);
|
||||
struct Segment {
|
||||
std::string text;
|
||||
std::uint8_t color;
|
||||
};
|
||||
auto draw_line = [&](const std::vector<Segment>& segs, int y) {
|
||||
const int TOTAL = std::accumulate(segs.begin(), segs.end(), 0, [&](int acc, const Segment& s) { return acc + hud_font_->length(s.text, KERN); });
|
||||
int x = (W - TOTAL) / 2;
|
||||
for (const auto& s : segs) {
|
||||
hud_font_->writeColored(x, y, s.text, s.color, KERN);
|
||||
x += hud_font_->length(s.text, KERN);
|
||||
}
|
||||
};
|
||||
|
||||
draw("MODE:", LBL);
|
||||
draw(" " + mode_str, VAL);
|
||||
draw(" " + brush_label, LBL);
|
||||
draw(" " + brush_value, VAL);
|
||||
draw(" " + COORD, VAL);
|
||||
draw(" DOOR:", LBL);
|
||||
draw(" " + std::to_string(countByName(entities, "door")), VAL);
|
||||
draw(" SARCS:", LBL);
|
||||
draw(" " + std::to_string(countByName(entities, "sarcophagus")), VAL);
|
||||
// Línia 1: estat actiu (mode + brush/selecció + cursor).
|
||||
draw_line({{.text = "MODE:", .color = LBL},
|
||||
{.text = " " + mode_str, .color = VAL},
|
||||
{.text = " " + brush_label, .color = LBL},
|
||||
{.text = " " + brush_value, .color = VAL},
|
||||
{.text = " " + COORD, .color = VAL}},
|
||||
LINE1_Y);
|
||||
|
||||
// Línia 2: comptadors de constraints. Color "flash" del HUD del
|
||||
// joc quan no es compleixen — mateix vocabulari visual que les
|
||||
// alertes del marcador.
|
||||
const int DOOR_C = countByName(entities, "door");
|
||||
const int SARC_C = countByName(entities, "sarcophagus");
|
||||
const auto DOOR_COLOR = (DOOR_C == 1) ? VAL : FLASH;
|
||||
const auto SARC_COLOR = (SARC_C >= 1) ? VAL : FLASH;
|
||||
draw_line({{.text = "DOOR:", .color = LBL},
|
||||
{.text = " " + std::to_string(DOOR_C), .color = DOOR_COLOR},
|
||||
{.text = " SARCS:", .color = LBL},
|
||||
{.text = " " + std::to_string(SARC_C), .color = SARC_COLOR}},
|
||||
LINE2_Y);
|
||||
}
|
||||
|
||||
void Editor::renderEntityFrame(const Entity& e, std::uint32_t argb) {
|
||||
const auto R = e.getPixelRect();
|
||||
// El marc l'alimenta el rect de col·lisió perquè coincideixi amb
|
||||
// el que `entityAtPixel` considera "clicable". Així el dissenyador
|
||||
// veu exactament la zona sensible al hover/selecció.
|
||||
const auto R = e.getCollisionRect();
|
||||
auto& screen = Screen::get();
|
||||
const auto X = static_cast<int>(R.x);
|
||||
const auto Y = static_cast<int>(R.y);
|
||||
|
||||
Reference in new issue
Block a user