Skip to content
Download

4. Enemies

  1. Create the object Enemy (Novo objeto…, new object, in the objects folder). In the Inspetor (Inspector), choose the sprite inimigo. In the Camadas (layers) panel, create one more Instâncias layer, called Inimigos, and drag it below Tiros. Then click Instancias again.

  2. Create the object Spawner and leave it without a sprite. It doesn’t show up in the game: its only job is to create enemies.

  3. Drag Spawner into the room Main. Anywhere is fine; in the editor it shows up as a little square. Enemy does not go in the room: the one that creates the enemies is Spawner.

Open Enemy.hpp, delete the examples and write:

objects/Enemy.hpp
#pragma once
#include <brasa/brasa.hpp>
// Enemy that falls from the top of the screen.
class Enemy : public brasa::Entity {
public:
BRASA_VARS(Enemy)
void onSpawn() override;
void onUpdate() override;
};

And Enemy.cpp:

objects/Enemy.cpp
#include "Enemy.hpp"
using namespace brasa;
void Enemy::onSpawn() {
animSpeed = 0.1f;
// random(a, b) picks a number between a and b: each enemy falls at a different speed.
vspeed = random(0.8f, 1.6f);
// When a Bullet touches me, the shot and I are destroyed.
// The other objects (Bullet, Player...) are already known here, with no #include.
onCollide<Bullet>([this](Bullet& bullet) {
bullet.destroy();
destroy();
});
}
void Enemy::onUpdate() {
// roomHeight() is the height of the screen; past it (with some slack), the enemy got away.
if (y > static_cast<float>(roomHeight()) + 16.0f) destroy();
}
BRASA_ENTITY(Enemy);
  • random(0.8f, 1.6f) picks a number between 0.8 and 1.6. Each enemy falls at a different speed, which makes the game less predictable.
  • onCollide<Bullet>(...) says: “when I touch a Bullet, do this”. Brasa checks the collision boxes on every step and calls the lambda. The shot that hit arrives as bullet, and both are destroyed.
  • In onUpdate, the enemy that passed the bottom of the screen is destroyed.

Open Spawner.hpp, delete the examples and write:

objects/Spawner.hpp
#pragma once
#include <brasa/brasa.hpp>
// Creates enemies at the top of the screen, faster and faster.
class Spawner : public brasa::Entity {
public:
BRASA_VARS(Spawner)
void onSpawn() override;
private:
void scheduleNext(); // schedules the next enemy
int spawned_ = 0;
};

Spawner has a function of its own, scheduleNext, and counts how many enemies it has created in spawned_.

Now Spawner.cpp:

objects/Spawner.cpp
#include "Spawner.hpp"
#include <algorithm>
using namespace brasa;
// The Spawner needs no onUpdate: it schedules the next enemy with after(),
// which calls the function after N steps (at 60 steps per second).
void Spawner::onSpawn() { scheduleNext(); }
void Spawner::scheduleNext() {
// The interval shrinks little by little (from 1 s down to a minimum of 0.25 s): the game gets harder.
const int interval = std::max(15, 60 - spawned_ * 2);
after(interval, [this] {
++spawned_;
const float margin = 16.0f;
spawn<Enemy>(random(margin, static_cast<float>(roomWidth()) - margin), -16.0f, "Inimigos");
scheduleNext(); // schedules the next one
});
}
BRASA_ENTITY(Spawner);

How it works:

  1. When Spawner is created, onSpawn calls scheduleNext().
  2. scheduleNext computes an interval: 60 steps (1 second) at the start, 2 steps fewer for each enemy, down to a minimum of 15. std::max(15, ...) guarantees that minimum.
  3. after(interval, ...) waits that long and creates an Enemy at a random x position, just above the screen (y = -16).
  4. Then it calls scheduleNext() again, and the cycle goes on forever.

Press Play. The enemies start falling, faster and faster. Shoot them: shot and enemy go away together. For now, touching the ship does nothing; that comes in the next part.

  • An object without a sprite can have an invisible job, like Spawner.
  • random(a, b) picks numbers. See Random numbers.
  • onCollide<Object>(...) reacts when two objects touch. See Collisions.
  • A function that schedules itself again with after creates a repetition with a varying interval.

Next part: 5. Score and lives →