Ir al contenido
Descargar

5. Puntos y vidas

Los puntos y las vidas no pertenecen a un solo objeto: el enemigo suma puntos, la nave pierde vidas y el marcador muestra los dos. Por eso quedan en un lugar común, el estado del juego, en dos archivos de código sueltos.

Haz clic derecho en la carpeta src y elige Novo script… (nuevo script). Escribe game.hpp y confirma. Escribe:

src/game.hpp
#pragma once
// Estado que vive entre las rooms: puntos y vidas.
struct GameState {
int score = 0;
int lives = 3;
};
GameState& state();

Haz lo mismo para crear el game.cpp, en la misma carpeta:

src/game.cpp
#include "game.hpp"
GameState& state() {
static GameState current;
return current;
}

Desde cualquier objeto que tenga #include "src/game.hpp", state().score y state().lives dan acceso a los puntos y a las vidas.

¿Cuántos puntos vale un enemigo? Es un buen valor para ajustar en el editor. En el Inspetor (Inspector) del Enemy, crea la variable pontos (puntos), del tipo inteiro (entero), con el valor 100.

En el Enemy.hpp, agrega la función kill. El archivo queda así:

objects/Enemy.hpp
#pragma once
#include <brasa/brasa.hpp>
// Enemigo que cae desde arriba de la pantalla. pontos es una variable del objeto (Inspector).
class Enemy : public brasa::Entity {
public:
BRASA_VARS(Enemy)
void onSpawn() override;
void onUpdate() override;
// Suma puntos, reproduce el sonido y se destruye.
void kill();
};

En el Enemy.cpp, el disparo ahora llama a kill(), que suma los puntos:

objects/Enemy.cpp
#include "Enemy.hpp"
#include "src/game.hpp"
using namespace brasa;
void Enemy::onSpawn() {
animSpeed = 0.1f;
// random(a, b) sortea un número entre a y b: cada enemigo cae a una velocidad distinta.
vspeed = random(0.8f, 1.6f);
// Cuando un Bullet me toca, el disparo desaparece y yo muero.
// Los demás objetos (Bullet, Player...) ya son conocidos aquí, sin #include.
onCollide<Bullet>([this](Bullet& bullet) {
bullet.destroy();
kill();
});
}
void Enemy::onUpdate() {
// roomHeight() es la altura de la pantalla; si la pasó (con margen), el enemigo escapó.
if (y > static_cast<float>(roomHeight()) + 16.0f) destroy();
}
void Enemy::kill() {
if (isDestroyed()) return; // evita puntuar dos veces en el mismo step
state().score += pontos;
destroy();
}
BRASA_ENTITY(Enemy);

El isDestroyed() evita un caso molesto: si dos disparos alcanzan al mismo enemigo en el mismo paso, solo cuenta puntos una vez. (El sonido del que habla el comentario del .hpp viene en la siguiente parte.)

En el Player.hpp, la nave gana el evento onRender, la función hurt y un contador de invencibilidad. Esta es la versión final del archivo:

objects/Player.hpp
#pragma once
#include <brasa/brasa.hpp>
// La nave. velocidade e intervaloTiro son variables del objeto: cámbialas en el Inspector, sin tocar el código.
class Player : public brasa::Entity {
public:
BRASA_VARS(Player)
void onSpawn() override;
void onUpdate() override;
void onRender() override;
// Quita una vida. Devuelve false si era invencible (el golpe no contó).
bool hurt();
private:
bool canShoot_ = true; // pasa a false durante intervaloTiro steps después de cada disparo
int invincible_ = 0; // steps de invencibilidad que quedan
};

En el Player.cpp, la nave pasa a reaccionar a los enemigos y a parpadear mientras es invencible:

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
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;
if (state().lives <= 0) {
destroy();
} else {
invincible_ = 90; // 1,5 s a 60 steps por segundo
}
return true;
}
BRASA_ENTITY(Player);

Las novedades:

  • En el onSpawn: cuando la nave toca un enemigo, hurt() intenta quitar una vida. Si el golpe contó, el enemigo es destruido.
  • hurt(): si la nave es invencible, no pasa nada. Si no, pierde una vida. Sin vidas, la nave es destruida (en la parte 7 eso va a abrir la pantalla de fin del juego). Con vidas, queda invencible durante 90 pasos, un segundo y medio.
  • Al final del onUpdate: la invencibilidad baja un paso cada vez.
  • onRender: es el evento que dibuja el objeto. Normalmente Brasa dibuja el sprite sola. Aquí, mientras la nave es invencible, se salta el dibujo en algunos pasos, lo que hace parpadear la nave. En los demás pasos, drawSelf() dibuja el sprite normalmente.
  1. Crea el objeto Hud, sin sprite, y arrástralo a la room Main.

  2. Abre el Hud.hpp, borra los ejemplos y escribe:

    objects/Hud.hpp
    #pragma once
    #include <brasa/brasa.hpp>
    // Marcador: puntos y vidas en la esquina de la pantalla.
    class Hud : public brasa::Entity {
    public:
    BRASA_VARS(Hud)
    void onRenderGui() override;
    };
  3. Abre el Hud.cpp, borra los ejemplos y escribe:

    objects/Hud.cpp
    #include "Hud.hpp"
    #include "src/game.hpp"
    #include <format>
    using namespace brasa;
    void Hud::onRenderGui() {
    // drawText solo dibuja ASCII: nada de acentos en los textos
    drawText(std::format("PONTOS {:05}", state().score), 4.0f, 4.0f);
    drawText(std::format("VIDAS {}", state().lives), 4.0f, 14.0f, colors::yellow);
    }
    BRASA_ENTITY(Hud);
  • onRenderGui dibuja encima de todo, en coordenadas de la pantalla, ideal para marcador y menús.
  • drawText(texto, x, y) escribe un texto. El color es opcional: colors::yellow deja las vidas en amarillo.
  • std::format("PONTOS {:05}", ...) arma el texto. El {:05} muestra el número con 5 dígitos, completando con ceros: 00300.

Pulsa Play. Cada enemigo destruido suma 100 puntos en el marcador. Deja que un enemigo choque con la nave: las vidas bajan y la nave parpadea. Sin vidas, la nave desaparece (por ahora el juego sigue sin ella).

  • Los datos que usan varios objetos quedan en un estado común, como el state().
  • onRender dibuja el objeto, y drawSelf() dibuja el sprite normal.
  • onRenderGui dibuja encima de todo, y drawText escribe en la pantalla. Mira Texto en pantalla.
  • isDestroyed() dice si la instancia ya fue destruida en este paso.

Siguiente parte: 6. Sonido →