Skip to content
Download

3. Shots

  1. In Recursos (Resources), right-click the objects folder › Novo objeto… (new object). Name it Bullet.

  2. In the Inspetor (Inspector), choose the sprite tiro.

  3. In the Camadas (layers) panel (on the left, below Recursos), click + Camada › Instâncias. Double-click the name and call it Tiros. Drag this layer below Instancias: the layer on top is in front, so the shots will show up behind the ship. Then click Instancias again, so it is the active layer once more. See Layers.

This time, do not place Bullet in the room: the shots will be created by the code, at the moment the ship shoots.

Open Bullet.hpp, delete the examples and write:

objects/Bullet.hpp
#pragma once
#include <brasa/brasa.hpp>
// The ship's shot: it goes up until it leaves the screen.
class Bullet : public brasa::Entity {
public:
BRASA_VARS(Bullet)
void onSpawn() override;
void onUpdate() override;
};

Then Bullet.cpp:

objects/Bullet.cpp
#include "Bullet.hpp"
using namespace brasa;
// onSpawn runs once, when the shot is created. Negative vspeed = goes up the screen.
// The engine moves the shot by itself on every step, with no code needed in onUpdate.
void Bullet::onSpawn() { vspeed = -5.0f; }
void Bullet::onUpdate() {
// Left through the top? We destroy it so invisible shots do not pile up.
if (y < -8.0f) destroy();
}
BRASA_ENTITY(Bullet);
  • vspeed is the vertical speed, in pixels per step. Brasa adds vspeed to y by itself on every step. On screen, y grows downward, so -5 makes the shot go up.
  • When the shot passes the top of the screen (y < -8), destroy() removes that instance from the game. Without it, the shots would keep existing forever, off screen.

The project already has the direction actions. One is missing for the shot:

  1. Open Projeto › Configurações do projeto… (Project › Project settings) and go to Ações de entrada (input actions).

  2. Click + Ação (add action) and name it atirar (shoot).

  3. On its row, click the +, choose Apertar a tecla… (press the key) and press the space bar.

  4. If you also want to shoot with the gamepad, click the + again and choose Botão do controle › Pad:A (gamepad button).

  5. Click Salvar (save).

Without a pause, the ship would fire 60 shots per second. Create one more variable to control that: in the Inspetor of Player, + Variável › inteiro (integer), with the name intervaloTiro (shot interval) and the value 10. That is 10 steps between one shot and the next.

In Player.hpp, add the private part, with a variable that says whether the ship can shoot. The file ends up like this:

objects/Player.hpp
#pragma once
#include <brasa/brasa.hpp>
// The ship. velocidade and intervaloTiro are variables of the object: change them in the Inspector, without touching the code.
class Player : public brasa::Entity {
public:
BRASA_VARS(Player)
void onSpawn() override;
void onUpdate() override;
private:
bool canShoot_ = true; // turns false for intervaloTiro steps after each shot
};

canShoot_ belongs to the ship. It starts true (can shoot) and will turn false for a moment after each shot. private: says that only Player itself touches it.

In Player.cpp, add the shooting block at the end of onUpdate. The whole file ends up like this:

objects/Player.cpp
#include "Player.hpp"
#include <algorithm>
using namespace brasa;
void Player::onSpawn() {
animSpeed = 0.2f; // the engine flame alternates between the 2 frames
}
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
canShoot_ = false;
after(intervaloTiro, [this] { canShoot_ = true; }); // only shoots again a few steps from now
}
}
BRASA_ENTITY(Player);

The new block, line by line:

  • held("atirar") && canShoot_: the action atirar is held and it can shoot.
  • spawn<Bullet>(x, y - 8.0f, "Tiros") creates a shot at the position of the ship, a little above it (8 pixels), in the Tiros layer.
  • canShoot_ = false blocks new shots.
  • after(intervaloTiro, ...) schedules something for intervaloTiro steps from now (10 steps, one sixth of a second). When the time comes, canShoot_ becomes true again.

Notice that Player.cpp uses Bullet with no extra #include: in Brasa, every object sees all the others.

Press Play. Hold the space bar: the ship fires a burst of shots that go up the screen.

Try changing intervaloTiro in the Inspector to 3 or to 30 and see the difference.

  • Objects can be created by the code with spawn<Object>(x, y, "Camada"). See Shoot.
  • hspeed and vspeed move the object by themselves on every step.
  • destroy() removes the instance from the game.
  • You create your own actions in the project settings and ask about them with held.
  • after(steps, ...) runs something later. See Wait and repeat.

Next part: 4. Enemies →