Pular para o conteúdo
Baixar

7. Fim de jogo

  1. Clique com o botão direito na pasta rooms e escolha Nova room…. Dê o nome GameOver.

  2. Crie o objeto GameOverScreen, sem sprite, e arraste-o para a room GameOver.

  3. A room Main continua sendo a primeira do jogo. No painel Recursos, ela aparece como Main (inicial). Isso se muda em Projeto › Configurações do projeto… › Room inicial.

Em Projeto › Configurações do projeto…, em Ações de entrada, crie mais duas ações, do mesmo jeito que você criou a atirar:

Ação Tecla Botão do controle
confirmar Enter Pad:Start
sair Esc (escolha Escape na lista Tecla) Pad:Back

O Esc cancela o Apertar a tecla…, por isso ele é escolhido pela lista.

No game.hpp, acrescente duas funções de apoio: centeredX e newGame. Esta é a versão final do arquivo:

src/game.hpp
#pragma once
#include <string_view>
// Estado que vive entre as rooms: pontos e vidas.
struct GameState {
int score = 0;
int lives = 3;
};
GameState& state();
// X que centraliza um texto na tela (a fonte do drawText tem 8 pixels por letra).
float centeredX(std::string_view text, float screenWidth);
// Zera o estado e abre a room Main.
void newGame();

E o game.cpp ganha as duas funções:

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 o x que deixa um texto centralizado. Cada letra do drawText tem 8 pixels de largura, então o texto ocupa letras × 8, e o resto da tela é dividido entre os dois lados.
  • newGame zera o estado (GameState{} volta para 0 pontos e 3 vidas) e abre a room Main. Como newGame não está dentro de um objeto, ela usa brasa::goToScene, a versão “de fora” do goTo.

No Player.cpp, quando as vidas acabam, a nave vai para a room GameOver antes de sumir. Este é o Player.cpp final:

objects/Player.cpp
#include "Player.hpp"
#include "src/game.hpp"
#include <algorithm>
using namespace brasa;
void Player::onSpawn() {
animSpeed = 0.2f; // a chama do motor alterna entre os 2 frames
// Encostar num Enemy custa uma vida (e o inimigo some junto).
onCollide<Enemy>([this](Enemy& enemy) {
if (hurt()) enemy.destroy();
});
}
void Player::onUpdate() {
// As ações ficam em Configurações do projeto (teclado e controle).
// axis devolve de -1 a 1: -1 = a primeira ação, 1 = a segunda.
float dx = axis("esquerda", "direita");
float dy = axis("cima", "baixo");
if (dx != 0.0f && dy != 0.0f) { // na diagonal, anda com a mesma velocidade
dx *= 0.7071f;
dy *= 0.7071f;
}
// clamp mantém a nave dentro da tela (a nave tem 16 px, então a margem é 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(ação) é verdadeiro enquanto a tecla ou o botão estão pressionados.
if (held("atirar") && canShoot_) {
spawn<Bullet>(x, y - 8.0f, "Tiros"); // o tiro nasce na camada Tiros, atrás da nave
Audio::play("tiro", {.volume = 0.4f});
canShoot_ = false;
after(intervaloTiro, [this] { canShoot_ = true; }); // só atira de novo daqui a alguns steps
}
if (invincible_ > 0) --invincible_;
}
void Player::onRender() {
// Pisca enquanto está invencível: pula o desenho em alguns 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") troca de room: tudo da room atual some e a room GameOver começa, com o GameOverScreen dentro.

Abra o GameOverScreen.hpp, apague os exemplos e escreva:

objects/GameOverScreen.hpp
#pragma once
#include <brasa/brasa.hpp>
// Tela de fim de jogo: mostra os pontos e espera "confirmar" (jogar de novo) ou "sair".
class GameOverScreen : public brasa::Entity {
public:
BRASA_VARS(GameOverScreen)
void onUpdate() override;
void onRenderGui() override;
};

E o 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(ação) é verdadeiro só no step em que a tecla ou o botão foram apertados.
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") é verdadeiro só no passo em que a ação foi apertada, diferente do held, que vale enquanto ela está segurada. Para menus, pressed é o certo: um toque, uma ação.
  • quit() fecha o jogo.
  • No onRenderGui, quatro textos centralizados com centeredX. Color{150, 150, 150, 255} é um cinza: vermelho, verde, azul e opacidade, de 0 a 255.

Aperte Play e jogue até perder as três vidas. A tela de fim de jogo mostra os seus pontos. Enter recomeça do zero e Esc fecha.

Pronto: o seu primeiro jogo está completo! 🎉

  • Um jogo pode ter várias rooms, e goTo("Nome") troca de uma para outra. Veja Trocar de room.
  • pressed vale só no passo em que a ação foi apertada, e held enquanto ela está segurada. Veja Entrada.
  • quit() fecha o jogo.
  • Funções de apoio, como centeredX e newGame, podem ficar no game.cpp e servir para todos os objetos.
  • Mude os números no Inspetor e veja o que acontece: a velocidade da nave, o intervaloTiro, os pontos de cada inimigo.
  • Marque a caixinha de uma variável numa instância (em Valores próprios) para dar a ela um valor só dela.
  • Faça um inimigo novo que anda em zigue-zague (dica: mexa no hspeed com every).
  • Faça a câmera seguir a nave numa room maior: veja Câmera seguindo o jogador.
  • Consulte os guias de Como fazer e a Referência.