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,
|
.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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user