feat(game-scene): spawn lligat a door_col i respawn a OUTRO->INTRO
This commit is contained in:
1 parent
59cd9b524c
commit
811c6c482d
2 files changed
+87
-18
No files matched your search
@@ -310,23 +310,12 @@ GameScene::GameScene(Config config)
|
||||
.right = Defaults::KeysGameP2::RIGHT,
|
||||
.jump = Defaults::KeysGameP2::JUMP,
|
||||
};
|
||||
// Spawn centrat al passadís exterior superior: la caixa de col·lisió
|
||||
// queda dins del passadís vertical del mig (entre columnes 1 i 2) i
|
||||
// centrada en Y al passadís exterior superior. Així no naix dins de
|
||||
// cap sarcòfag.
|
||||
constexpr auto CORRIDOR_CENTER_X = static_cast<float>(GameCanvas::CENTER_X);
|
||||
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.
|
||||
// Spawn lligat a la porta: tots els jugadors apareixen davant del
|
||||
// passadís de sortida. `computeSpawn` deriva les coordenades de
|
||||
// `Map::door_col` perquè canviar la columna de la porta al YAML
|
||||
// moga automàticament l'spawn — no hi ha cap valor hardcoded.
|
||||
const auto SPAWN = computeSpawn();
|
||||
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
|
||||
// (camps opcionals `player1_paint_tile` / `player2_paint_tile`). Si no
|
||||
@@ -338,11 +327,13 @@ GameScene::GameScene(Config config)
|
||||
constexpr int ID_P2 = 1;
|
||||
if (USE_P1) {
|
||||
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) {
|
||||
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
|
||||
@@ -417,11 +408,70 @@ GameScene::GameScene(Config config)
|
||||
|
||||
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) {
|
||||
if (new_state == state_) { return; }
|
||||
std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n';
|
||||
const auto PREV = state_;
|
||||
state_ = new_state;
|
||||
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) {
|
||||
|
||||
@@ -168,6 +168,25 @@ class GameScene : public Scene {
|
||||
void renderRoomFade();
|
||||
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
|
||||
// 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
|
||||
|
||||
Reference in new issue
Block a user