5. Puntos y vidas
Dónde guardar puntos y vidas
Sección titulada «Dónde guardar 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:
#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:
#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.
El enemigo vale puntos
Sección titulada «El enemigo vale puntos»¿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í:
#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:
#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.)
La nave pierde vidas
Sección titulada «La nave pierde vidas»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:
#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:
#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.
El marcador
Sección titulada «El marcador»-
Crea el objeto
Hud, sin sprite, y arrástralo a la roomMain. -
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;}; -
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 textosdrawText(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);
onRenderGuidibuja 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::yellowdeja 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.
Pruébalo ahora
Sección titulada «Pruébalo ahora»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).
Lo que aprendiste
Sección titulada «Lo que aprendiste»- Los datos que usan varios objetos quedan en un estado común, como el
state(). onRenderdibuja el objeto, ydrawSelf()dibuja el sprite normal.onRenderGuidibuja encima de todo, ydrawTextescribe en la pantalla. Mira Texto en pantalla.isDestroyed()dice si la instancia ya fue destruida en este paso.
Siguiente parte: 6. Sonido →
