7. Game over
The Game Over room
Section titled “The Game Over room”-
Right-click the
roomsfolder and choose Nova room… (new room). Name itGameOver. -
Create the object
GameOverScreen, without a sprite, and drag it into the roomGameOver. -
The room
Mainis still the first one of the game. In the Recursos (Resources) panel, it shows up as Main (inicial). You change that under Projeto › Configurações do projeto… › Room inicial (Project › Project settings › start room).
Two actions for the menu
Section titled “Two actions for the menu”Under Projeto › Configurações do projeto…, in Ações de entrada (input actions), create two more actions, the same way you created atirar:
| Action | Key | Gamepad button |
|---|---|---|
confirmar (confirm) |
Enter | Pad:Start |
sair (quit) |
Esc (choose Escape in the Tecla list) | Pad:Back |
Esc cancels Apertar a tecla… (press the key), which is why it is chosen from the list.
Helper functions
Section titled “Helper functions”In game.hpp, add two helper functions: centeredX and newGame. This is the final version of the file:
#pragma once#include <string_view>
// State that lives across rooms: score and lives.struct GameState { int score = 0; int lives = 3;};GameState& state();
// X that centers a text on screen (the drawText font is 8 pixels per letter).float centeredX(std::string_view text, float screenWidth);// Resets the state and opens the room Main.void newGame();And game.cpp gets the two functions:
#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");}centeredXcomputes thexthat centers a text. Each letter ofdrawTextis 8 pixels wide, so the text takesletters × 8, and the rest of the screen is split between the two sides.newGameresets the state (GameState{}goes back to 0 points and 3 lives) and opens the roomMain. BecausenewGameis not inside an object, it usesbrasa::goToScene, the “outside” version ofgoTo.
The ship opens the Game Over
Section titled “The ship opens the Game Over”In Player.cpp, when the lives run out, the ship goes to the room GameOver before going away. This is the final Player.cpp:
#include "Player.hpp"
#include "src/game.hpp"
#include <algorithm>
using namespace brasa;
void Player::onSpawn() { animSpeed = 0.2f; // the engine flame alternates between the 2 frames // Touching an Enemy costs one life (and the enemy goes away too). onCollide<Enemy>([this](Enemy& enemy) { if (hurt()) enemy.destroy(); });}
void Player::onUpdate() { // The actions are in the project settings (keyboard and gamepad). // axis returns from -1 to 1: -1 = the first action, 1 = the second. float dx = axis("esquerda", "direita"); float dy = axis("cima", "baixo"); if (dx != 0.0f && dy != 0.0f) { // on the diagonal, it moves at the same speed dx *= 0.7071f; dy *= 0.7071f; }
// clamp keeps the ship inside the screen (the ship is 16 px, so the margin is 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(action) is true while the key or the button is pressed. if (held("atirar") && canShoot_) { spawn<Bullet>(x, y - 8.0f, "Tiros"); // the shot is born in the Tiros layer, behind the ship Audio::play("tiro", {.volume = 0.4f}); canShoot_ = false; after(intervaloTiro, [this] { canShoot_ = true; }); // only shoots again a few steps from now } if (invincible_ > 0) --invincible_;}
void Player::onRender() { // Blinks while invincible: skips the drawing on some 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 at 60 steps per second } return true;}
BRASA_ENTITY(Player);goTo("GameOver") switches rooms: everything in the current room goes away and the room GameOver starts, with GameOverScreen in it.
The game over screen
Section titled “The game over screen”Open GameOverScreen.hpp, delete the examples and write:
#pragma once#include <brasa/brasa.hpp>
// Game over screen: shows the score and waits for "confirmar" (play again) or "sair" (quit).class GameOverScreen : public brasa::Entity {public: BRASA_VARS(GameOverScreen)
void onUpdate() override; void onRenderGui() override;};And GameOverScreen.cpp:
#include "GameOverScreen.hpp"
#include "src/game.hpp"
#include <format>#include <string>#include <string_view>
using namespace brasa;
void GameOverScreen::onUpdate() { // pressed(action) is true only on the step the key or the button was pressed. 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")is true only on the step the action was pressed, unlikeheld, which is true while it is held. For menus,pressedis the right one: one tap, one action.quit()closes the game.- In
onRenderGui, four texts centered withcenteredX.Color{150, 150, 150, 255}is a gray: red, green, blue and opacity, from 0 to 255.
Test it now
Section titled “Test it now”Press Play and play until you lose the three lives. The game over screen shows your score. Enter starts over from zero and Esc closes.
Done: your first game is complete! 🎉
What you learned
Section titled “What you learned”- A game can have several rooms, and
goTo("Name")switches from one to another. See Switch rooms. pressedis true only on the step the action was pressed, andheldwhile it is held. See Input.quit()closes the game.- Helper functions, such as
centeredXandnewGame, can live ingame.cppand serve every object.
What now?
Section titled “What now?”- Change the numbers in the Inspector and see what happens: the ship’s
velocidade, theintervaloTiro, thepontosof each enemy. - Check the small box of a variable on an instance (under Valores próprios, own values) to give it a value of its own.
- Make a new enemy that zigzags (hint: change
hspeedwithevery). - Make the camera follow the ship in a bigger room: see Camera following the player.
- Check the guides in How to and the Reference.
