Ir al contenido
Descargar

7. Fin del juego

  1. Haz clic derecho en la carpeta rooms y elige Nova room… (nueva room). Ponle el nombre GameOver.

  2. Crea el objeto GameOverScreen, sin sprite, y arrástralo a la room GameOver.

  3. La room Main sigue siendo la primera del juego. En el panel Recursos, aparece como Main (inicial). Eso se cambia en Projeto › Configurações do projeto… › Room inicial (Proyecto › Configuración del proyecto).

En Projeto › Configurações do projeto…, en Ações de entrada (acciones de entrada), crea dos acciones más, igual que creaste atirar:

Acción Tecla Botón del mando
confirmar Enter Pad:Start
sair (salir) Esc (elige Escape en la lista Tecla) Pad:Back

El Esc cancela Apertar a tecla… (pulsar la tecla), por eso se elige en la lista.

En el game.hpp, agrega dos funciones de apoyo: centeredX y newGame. Esta es la versión final del archivo:

src/game.hpp
#pragma once
#include <string_view>
// Estado que vive entre las rooms: puntos y vidas.
struct GameState {
int score = 0;
int lives = 3;
};
GameState& state();
// X que centra un texto en pantalla (la fuente del drawText tiene 8 píxeles por letra).
float centeredX(std::string_view text, float screenWidth);
// Pone el estado en cero y abre la room Main.
void newGame();

Y el game.cpp gana las dos funciones:

src/game.cpp
#include "game.hpp"
#include <brasa/brasa.hpp>
GameState& state() {
static GameState current;
return current;
}
float centeredX(std::string_view text, float screenWidth) {
return (screenWidth - static_cast<float>(text.size()) * 8.0f) / 2.0f;
}
void newGame() {
state() = GameState{};
brasa::goToScene("Main");
}
  • centeredX calcula la x que deja un texto centrado. Cada letra del drawText tiene 8 píxeles de ancho, así que el texto ocupa letras × 8, y el resto de la pantalla se reparte entre los dos lados.
  • newGame pone el estado en cero (GameState{} vuelve a 0 puntos y 3 vidas) y abre la room Main. Como newGame no está dentro de un objeto, usa brasa::goToScene, la versión “de fuera” del goTo.

En el Player.cpp, cuando se acaban las vidas, la nave va a la room GameOver antes de desaparecer. Este es el Player.cpp final:

objects/Player.cpp
#include "Player.hpp"
#include "src/game.hpp"
#include <algorithm>
using namespace brasa;
void Player::onSpawn() {
animSpeed = 0.2f; // la llama del motor alterna entre los 2 frames
// Tocar un Enemy cuesta una vida (y el enemigo desaparece también).
onCollide<Enemy>([this](Enemy& enemy) {
if (hurt()) enemy.destroy();
});
}
void Player::onUpdate() {
// Las acciones están en la configuración del proyecto (teclado y mando).
// axis devuelve de -1 a 1: -1 = la primera acción, 1 = la segunda.
float dx = axis("esquerda", "direita");
float dy = axis("cima", "baixo");
if (dx != 0.0f && dy != 0.0f) { // en diagonal, avanza a la misma velocidad
dx *= 0.7071f;
dy *= 0.7071f;
}
// clamp mantiene la nave dentro de la pantalla (la nave mide 16 px, así que el margen es 8).
const float maxX = static_cast<float>(roomWidth()) - 8.0f;
const float maxY = static_cast<float>(roomHeight()) - 8.0f;
x = std::clamp(x + dx * velocidade, 8.0f, maxX);
y = std::clamp(y + dy * velocidade, 8.0f, maxY);
// held(acción) es verdadero mientras la tecla o el botón están pulsados.
if (held("atirar") && canShoot_) {
spawn<Bullet>(x, y - 8.0f, "Tiros"); // el disparo nace en la capa Tiros, detrás de la nave
Audio::play("tiro", {.volume = 0.4f});
canShoot_ = false;
after(intervaloTiro, [this] { canShoot_ = true; }); // solo dispara de nuevo dentro de algunos steps
}
if (invincible_ > 0) --invincible_;
}
void Player::onRender() {
// Parpadea mientras es invencible: se salta el dibujo en algunos steps.
if (invincible_ > 0 && (invincible_ / 4) % 2 == 0) return;
drawSelf();
}
bool Player::hurt() {
if (invincible_ > 0 || isDestroyed()) return false;
--state().lives;
Audio::play("dano");
if (state().lives <= 0) {
goTo("GameOver");
destroy();
} else {
invincible_ = 90; // 1,5 s a 60 steps por segundo
}
return true;
}
BRASA_ENTITY(Player);

goTo("GameOver") cambia de room: todo lo de la room actual desaparece y empieza la room GameOver, con el GameOverScreen dentro.

Abre el GameOverScreen.hpp, borra los ejemplos y escribe:

objects/GameOverScreen.hpp
#pragma once
#include <brasa/brasa.hpp>
// Pantalla de fin del juego: muestra los puntos y espera "confirmar" (jugar de nuevo) o "sair" (salir).
class GameOverScreen : public brasa::Entity {
public:
BRASA_VARS(GameOverScreen)
void onUpdate() override;
void onRenderGui() override;
};

Y el GameOverScreen.cpp:

objects/GameOverScreen.cpp
#include "GameOverScreen.hpp"
#include "src/game.hpp"
#include <format>
#include <string>
#include <string_view>
using namespace brasa;
void GameOverScreen::onUpdate() {
// pressed(acción) es verdadero solo en el step en que se pulsó la tecla o el botón.
if (pressed("confirmar")) newGame();
if (pressed("sair")) quit();
}
void GameOverScreen::onRenderGui() {
const float screenW = static_cast<float>(roomWidth());
constexpr std::string_view kTitle = "FIM DE JOGO";
constexpr std::string_view kRetry = "ENTER JOGAR DE NOVO";
constexpr std::string_view kQuit = "ESC SAIR";
const std::string score = std::format("PONTOS {}", state().score);
drawText(kTitle, centeredX(kTitle, screenW), 60.0f, colors::red);
drawText(score, centeredX(score, screenW), 85.0f);
drawText(kRetry, centeredX(kRetry, screenW), 120.0f);
drawText(kQuit, centeredX(kQuit, screenW), 135.0f, Color{150, 150, 150, 255});
}
BRASA_ENTITY(GameOverScreen);
  • pressed("confirmar") es verdadero solo en el paso en que la acción se pulsó, a diferencia del held, que vale mientras está mantenida. Para menús, pressed es lo correcto: un toque, una acción.
  • quit() cierra el juego.
  • En el onRenderGui, cuatro textos centrados con centeredX. Color{150, 150, 150, 255} es un gris: rojo, verde, azul y opacidad, de 0 a 255.

Pulsa Play y juega hasta perder las tres vidas. La pantalla de fin del juego muestra tus puntos. Enter vuelve a empezar desde cero y Esc cierra.

¡Listo: tu primer juego está completo! 🎉

  • Un juego puede tener varias rooms, y goTo("Nombre") cambia de una a otra. Mira Cambiar de room.
  • pressed vale solo en el paso en que se pulsó la acción, y held mientras está mantenida. Mira Entrada.
  • quit() cierra el juego.
  • Las funciones de apoyo, como centeredX y newGame, pueden quedar en el game.cpp y servir a todos los objetos.
  • Cambia los números en el Inspector y mira qué pasa: la velocidade de la nave, el intervaloTiro, los pontos de cada enemigo.
  • Marca la casilla de una variable en una instancia (en Valores próprios, valores propios) para darle un valor solo suyo.
  • Haz un enemigo nuevo que se mueva en zigzag (pista: cambia el hspeed con every).
  • Haz que la cámara siga a la nave en una room más grande: mira Cámara siguiendo al jugador.
  • Consulta las guías de Cómo hacer y la Referencia.