feat(game-scene): spawn lligat a door_col i respawn a OUTRO->INTRO

This commit is contained in:
JailDesigner committed 2026-04-26 10:30:31 +02:00
1 parent 59cd9b524c
commit 811c6c482d
2 files changed
+87 -18

No files matched your search

+68 -18
View File
@@ -310,23 +310,12 @@ GameScene::GameScene(Config config)
.right = Defaults::KeysGameP2::RIGHT, .right = Defaults::KeysGameP2::RIGHT,
.jump = Defaults::KeysGameP2::JUMP, .jump = Defaults::KeysGameP2::JUMP,
}; };
// Spawn centrat al passadís exterior superior: la caixa de col·lisió // Spawn lligat a la porta: tots els jugadors apareixen davant del
// queda dins del passadís vertical del mig (entre columnes 1 i 2) i // passadís de sortida. `computeSpawn` deriva les coordenades de
// centrada en Y al passadís exterior superior. Així no naix dins de // `Map::door_col` perquè canviar la columna de la porta al YAML
// cap sarcòfag. // moga automàticament l'spawn — no hi ha cap valor hardcoded.
constexpr auto CORRIDOR_CENTER_X = static_cast<float>(GameCanvas::CENTER_X); const auto SPAWN = computeSpawn();
const auto CORRIDOR_CENTER_Y =
static_cast<float>(Map::playfield_y) + (static_cast<float>(Map::outer_corridor) / 2.0F);
const auto SPAWN_X = CORRIDOR_CENTER_X - (static_cast<float>(Player::sprite_w) / 2.0F);
const auto SPAWN_Y = CORRIDOR_CENTER_Y - static_cast<float>(Player::collision_y_offset) - (static_cast<float>(Player::collision_size) / 2.0F);
// En mode BOTH separem els jugadors un tile a banda i banda del
// centre perquè no naixen superposats. En mode single-player
// (P1 o P2 aïllats per a proves de dev) l'únic jugador arrenca al
// centre exacte.
const bool BOTH = (USE_P1 && USE_P2); const bool BOTH = (USE_P1 && USE_P2);
const auto P1_SPAWN_X = BOTH ? SPAWN_X - static_cast<float>(Map::tile_size) : SPAWN_X;
const auto P2_SPAWN_X = BOTH ? SPAWN_X + static_cast<float>(Map::tile_size) : SPAWN_X;
// Índex de tile amb què pinta cada jugador — llegit del YAML del mapa // Índex de tile amb què pinta cada jugador — llegit del YAML del mapa
// (camps opcionals `player1_paint_tile` / `player2_paint_tile`). Si no // (camps opcionals `player1_paint_tile` / `player2_paint_tile`). Si no
@@ -338,11 +327,13 @@ GameScene::GameScene(Config config)
constexpr int ID_P2 = 1; constexpr int ID_P2 = 1;
if (USE_P1) { if (USE_P1) {
const int P1_PAINT = readOptionalInt(config_.map_yaml, "player1_paint_tile", -1); const int P1_PAINT = readOptionalInt(config_.map_yaml, "player1_paint_tile", -1);
players_.emplace_back(std::make_unique<Player>(anim_p1, ID_P1, P1_SPAWN_X, SPAWN_Y, CONTROLS_P1, Map::makeInfo(), P1_PAINT)); const float X = BOTH ? SPAWN.p1_x : SPAWN.p1_x + static_cast<float>(Map::tile_size);
players_.emplace_back(std::make_unique<Player>(anim_p1, ID_P1, X, SPAWN.y, CONTROLS_P1, Map::makeInfo(), P1_PAINT));
} }
if (USE_P2) { if (USE_P2) {
const int P2_PAINT = readOptionalInt(config_.map_yaml, "player2_paint_tile", -1); const int P2_PAINT = readOptionalInt(config_.map_yaml, "player2_paint_tile", -1);
players_.emplace_back(std::make_unique<Player>(anim_p2, ID_P2, P2_SPAWN_X, SPAWN_Y, CONTROLS_P2, Map::makeInfo(), P2_PAINT)); const float X = BOTH ? SPAWN.p2_x : SPAWN.p2_x - static_cast<float>(Map::tile_size);
players_.emplace_back(std::make_unique<Player>(anim_p2, ID_P2, X, SPAWN.y, CONTROLS_P2, Map::makeInfo(), P2_PAINT));
} }
// PaintLayer: font única del pintat del nivell. Es crea després de // PaintLayer: font única del pintat del nivell. Es crea després de
@@ -417,11 +408,70 @@ GameScene::GameScene(Config config)
GameScene::~GameScene() = default; GameScene::~GameScene() = default;
auto GameScene::computeSpawn() -> PlayerSpawn {
// Centre de la columna del passadís en píxels canvas. La caixa de
// col·lisió del jugador (12 px) cap dins del passadís (16 px) amb 2
// px de marge a banda i banda; alinear el sprite al centre de la
// columna garanteix que la base del jugador i la del passadís
// coincideixin verticalment.
const float DOOR_CENTER_X = static_cast<float>((Map::door_col * Map::tile_size) + Map::tilemap_origin_x) + (static_cast<float>(Map::tile_size) / 2.0F);
const float CENTER_TL_X = DOOR_CENTER_X - (static_cast<float>(Player::sprite_w) / 2.0F);
// Y: centre del corredor exterior superior (mateix valor que abans;
// el corredor és simètric respecte a `playfield_y`).
const float CORRIDOR_CENTER_Y = static_cast<float>(Map::playfield_y) + (static_cast<float>(Map::outer_corridor) / 2.0F);
const float SPAWN_Y = CORRIDOR_CENTER_Y - static_cast<float>(Player::collision_y_offset) - (static_cast<float>(Player::collision_size) / 2.0F);
// Mode BOTH: P1 a -tile_size, P2 a +tile_size respecte al centre.
// Els callsites individuals decideixen si usar aquests valors o si
// estem en mode single-player (un sol jugador → centrat exactament).
return {
.p1_x = CENTER_TL_X - static_cast<float>(Map::tile_size),
.p2_x = CENTER_TL_X + static_cast<float>(Map::tile_size),
.y = SPAWN_Y,
};
}
void GameScene::resetRoom() {
// Recarregar les entitats des del YAML del mapa: la porta torna a
// CLOSED (amb l'open=false a Map al loadFrom), els sarcs tornen a
// CLOSED amb visited buit, momies i altres actors a la posició inicial.
Map::loadFrom(config_.map_yaml);
entity_manager_.reload(config_.map_yaml);
entity_manager_.setExpectedPlayerCount(static_cast<int>(players_.size()));
if (paint_layer_) { paint_layer_->clear(); }
// Respawn dels jugadors. Recalcular spawn per si door_col canvia
// (avui el mapa no canvia entre cicles, però la lògica ja queda
// preparada per a l'itinerari futur).
const auto SPAWN = computeSpawn();
const bool BOTH = (players_.size() >= 2);
// Mode single-player: l'únic jugador queda centrat (= SPAWN.p1_x +
// tile_size, perquè SPAWN.p1_x és el centre menys un tile).
const float SINGLE_X = SPAWN.p1_x + static_cast<float>(Map::tile_size);
for (std::size_t i = 0; i < players_.size(); ++i) {
float x = SINGLE_X;
if (BOTH) { x = (i == 0) ? SPAWN.p1_x : SPAWN.p2_x; }
players_[i]->respawn(x, SPAWN.y);
}
std::cout << "[ROOM] reset: entities reloaded, paint cleared, "
<< players_.size() << " player(s) respawned\n";
}
void GameScene::setState(RoomFsm::State new_state) { void GameScene::setState(RoomFsm::State new_state) {
if (new_state == state_) { return; } if (new_state == state_) { return; }
std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n'; std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n';
const auto PREV = state_;
state_ = new_state; state_ = new_state;
transition_ = {}; transition_ = {};
// En tornar a ROOM_INTRO des de ROOM_OUTRO (placeholder de
// "torna a la mateixa sala"), reseteja completament l'estat del
// nivell. Si la transició és la inicial (PREV també és ROOM_INTRO,
// imposible amb el guard de dalt) o ve d'un altre estat, no fa res.
if (new_state == RoomFsm::State::ROOM_INTRO && PREV == RoomFsm::State::ROOM_OUTRO) {
resetRoom();
}
} }
void GameScene::updateRoomTransition(int delta_ms) { void GameScene::updateRoomTransition(int delta_ms) {
+19
View File
@@ -168,6 +168,25 @@ class GameScene : public Scene {
void renderRoomFade(); void renderRoomFade();
void renderRoomText(); void renderRoomText();
// Posició inicial dels jugadors derivada de `Map::door_col`. Tots
// els jugadors comparteixen Y (centre del corredor exterior superior);
// X depèn del nombre actiu: amb un jugador queda centrat sota la
// porta; amb dos, separats ±tile_size respecte al centre. Estructura
// exposada perquè els tests puguen verificar-la sense entrar al ctor
// sencer.
struct PlayerSpawn {
float p1_x{0.0F};
float p2_x{0.0F};
float y{0.0F};
};
[[nodiscard]] static auto computeSpawn() -> PlayerSpawn;
// Reset complet de la sala: reload de l'EntityManager (sarcs/porta/
// momies des de zero), neteja del PaintLayer i respawn dels jugadors
// a la posició calculada per `computeSpawn`. Cridat des de
// `setState(ROOM_INTRO)` quan la transició anterior era OUTRO.
void resetRoom();
#ifdef _DEBUG #ifdef _DEBUG
// Editor modal del tilemap. Mútuament excloent amb la pausa: si la // Editor modal del tilemap. Mútuament excloent amb la pausa: si la
// pausa no està IDLE la tecla de toggle de l'editor s'ignora, i // pausa no està IDLE la tecla de toggle de l'editor s'ignora, i