What would be the best way to handle the firing rate of the ships in my game?

0

In my current code the enemy ships fire each frame of the game. How can I make those shots to be every so often, 2 seconds for example?

My code:

Bullet.h:

class Bullet {
private:

sf::Sprite sprite;

sf::Vector2f speed;


int posX, posY;

Bullet(int _posX, int _posY, sf::Texture &_texture);

public:

friend class BulletFactory;

void update(sf::Time deltaTime);
void reset();
void setSpeed(float _speed);
sf::Sprite render();
sf::FloatRect bounds(){
    return sprite.getGlobalBounds();
}
};

class BulletFactory{
private:
sf::Texture texture;
public:
BulletFactory(){
    texture.loadFromFile("SFML-Game-Development-Book-master/10_Network/Media/Textures/misil2.png");
}

Bullet create(int _posX, int _posY){
    return {_posX, _posY, texture};
}
};

Bullet.cpp:

Bullet::Bullet(int _posX, int _posY, sf::Texture &_texture) {

sprite.setTexture(_texture);
sprite.rotate(-90);
sprite.setScale(0.1f, 0.1f);

posX = _posX;
posY = _posY;

sprite.setPosition(posX, posY);

speed.y = -500.0f;
}


void Bullet::update(sf::Time deltaTime){

sprite.move(0, speed.y * deltaTime.asSeconds());
}

sf::Sprite Bullet::render(){
return sprite;
}

void Bullet::reset(){
speed.x = 0.0f;
speed.y = 0.0f;
}

void Bullet::setSpeed(float _speed){
speed.y = _speed;
}

enemy.h:

class Enemy {
private:
sf::Sprite sprite;

float speed = 200.0f ;

Enemy(int _maxX, int _maxY, sf::Texture &_texture);
public:
friend class EnemyFactory;

sf::FloatRect bounds(){
    return sprite.getGlobalBounds();
}

sf::Vector2f getPosition(){
    return sprite.getPosition();
}
sf::Sprite render();
void update(sf::Time deltaTime);
void reset();
};

class EnemyFactory{
private:
sf::Texture texture;
public:
EnemyFactory(){
    texture.loadFromFile("SFML-Game-Development-Book-master/03_World/Media/Textures/Raptor.png");
}

Enemy create(int _maxX, int _maxY){
    return {_maxX, _maxY, texture};
}
};

Enemy.cpp:

Enemy::Enemy(int _maxX, int _maxY, sf::Texture &_texture) {
sprite.setTexture(_texture);
sprite.rotate(180);
sprite.setScale(0.8f, 0.8f);

sprite.setPosition(1+rand()%640,rand() % 400 - 500); 
}

sf::Sprite Enemy::render(){
return sprite;
}

void Enemy::update(sf::Time deltaTime){
sprite.move(0.0f, speed * deltaTime.asSeconds());

if (sprite.getPosition().y > 480+51.2){
    sprite.setPosition(1+rand()%640,rand() % 400 - 500);  
}
}

void Enemy::reset(){
speed = 0;
}

game.h:

class Game {
private:
sf::RenderWindow *mWindow;

ScrollingBackground background;

Aircraft aircraft;

std::vector<Enemy> enemies;
EnemyFactory enemy;

std::vector<Bullet> bullets;
std::vector<Bullet> enemybullets;
BulletFactory bullet;
private:
void proccesEvent();
void update(sf::Time deltaTime);
void proccesCollisions();
void render();
public:
Game();
void run();
};

game.cpp:

Game::Game(){
mWindow = new sf::RenderWindow(sf::VideoMode(640,480), "ventana SFML"); 
mWindow->setKeyRepeatEnabled(false);

for (int i=0; i<7; i++){
    enemies.push_back(enemy.create(640,480));
}   
}

void Game::proccesEvent(){
sf::Event event;

while(mWindow->pollEvent(event)){
    if(event.type == sf::Event::Closed){
        mWindow->close();
    }

    if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Space){
        bullets.push_back(bullet.create((aircraft.getPosition().x+15), (aircraft.getPosition().y+40)));
    }

}
}

void Game::update(sf::Time deltaTime){

background.update(mWindow, deltaTime);

if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)){
    aircraft.Up();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)){
    aircraft.Down(mWindow);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)){
    aircraft.Left();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)){
    aircraft.Right(mWindow);
}

for (int j=0; j<enemies.size(); j++){
    enemies[j].update(deltaTime);
    enemybullets.push_back(bullet.create((enemies[j].getPosition().x), (enemies[j].getPosition().y)));
}

for (int j=0; j<enemybullets.size(); j++){
    enemybullets[j].setSpeed(500.0f);
    enemybullets[j].update(deltaTime);
}

for (int i=0; i<bullets.size(); i++){
    bullets[i].update(deltaTime);
}

aircraft.update(deltaTime);
}

void Game::render(){
mWindow->clear();
mWindow->draw(background.render());
for (int j=0; j<enemies.size(); j++){
    mWindow->draw(enemies[j].render());
}
for (int i=0; i<enemybullets.size(); i++){
    mWindow->draw(enemybullets[i].render());
}
for (int i=0; i<bullets.size(); i++){
    mWindow->draw(bullets[i].render());
}
mWindow->draw(aircraft.Render());

mWindow->display();
}

void Game::run(){
sf::Clock clock;
while(mWindow->isOpen()){
    sf::Time deltaTime = clock.restart();
    proccesEvent();
    proccesCollisions();
    update(deltaTime);
    render();
}
}

Do not add the proccesCollisions function in game.cpp to not extend the code unnecessarily.

    
asked by facundo rotger 04.05.2017 в 21:25
source

2 answers

1

The way in which structures events ends up complicating the delegation of decisions from a ship to the main loop, which is not a good idea.

Without making profound changes to your programming logic, I would recommend making the following modifications:

  • During the update of the enemies check if they should fire (this decision could also be made in the function update() and there, for example, modify a flag that is read later.
  • Implement the decision logic in class Enemy . In my example I do it in a function of my own, if I knew the class Enemy I could maybe implement the logic in the function update() .

Here is an example of implementation using an additional function:

game.cpp

for (int j = 0; j < enemies.size(); j++){
    enemies[j].update(deltaTime);
    /* Llamamos a la función de la clase 'Enemy' a la que delegamos
      la decisión de realizar el disparo o no */
    if (enemies[j].fireEvent(deltaTime)) {
        /* Si devuelve true entonces efectuamos el disparo */
        enemybullets.push_back(
            bullet.create(
                enemies[j].getPosition().x,
                enemies[j].getPosition().y
            )
        );
    }
}

In this way we delegate the decision to shoot the enemies' implementation, so we must add to their class:

class Enemy {
    sf::Time shootTime, delayShoot;
    bool Enemy::fireEvent(sf::Time deltaTime) {
        /* Incrementamos delayShoot el tiempo que ha transcurrido */
        delayShoot += deltaTime;
        /* ¿Hemos superado el tiempo mínimo entre disparos? */
        if (delayShoot >= shootTime) {
          /* Restamos el tiempo entre disparos, en caso de una pausa muy
            elevada eliminamos el resto de eventos pasados */
          while (delayShoot >= shootTime) {
            delayShoot -= shootTime;
          }
          /* Indicamos que debemos disparar */
          return true;
       }
       /* Indicamos que en este momento no debe generarse un disparo */
       return false;
    }
}

Since you do not show the class code Enemy I can not make modifications of the constructor to initialize these variables ( delayShoot = sf::seconds(0f) and shootTime = sf::seconds(2f) ), but the idea is that you use shootTime to define the cadence (the interval of minimum time between two shots).

Edit:

We can add these definitions within enemy.h in the public and private part:

public:
    bool isShooting = false;

private:
    sf::Time shootTime, delayShoot;

In Enemy.cpp we modify the constructor to include the following initializations and the function update to modify the value of isShooting :

Enemy::Enemy(int _maxX, int _maxY, sf::Texture &_texture) {
    delayShoot = sf::seconds(0f);
    shootTime = sf::seconds(2f);
    /* ... */
}

void Enemy::update(sf::Time deltaTime) {
    /* Incrementamos delayShoot el tiempo que ha transcurrido */
    delayShoot += deltaTime;
    /* ¿Hemos superado el tiempo mínimo entre disparos? */
    if (delayShoot >= shootTime) {
        /* Restamos el tiempo entre disparos, en caso de una pausa muy
        elevada eliminamos el resto de eventos pasados */
        while (delayShoot >= shootTime) {
            delayShoot -= shootTime;
        }
        /* Indicamos que debemos disparar */
        isShooting = true;
    } else {
        /* Indicamos que en este momento no debe generarse un disparo */
        isShooting = false;
    }
    sprite.move(0.0f, speed * deltaTime.asSeconds());
    if (sprite.getPosition().y > 480+51.2){
        sprite.setPosition(1+rand()%640,rand() % 400 - 500);  
    }
}

Now come the changes in game.cpp :

for (int j = 0; j < enemies.size(); j++) {
    enemies[j].update(deltaTime);
    /* Si el enemigo está disparando agregamos una nueva bala */
    if (enemies[j].isShooting) {
        /* Si devuelve true entonces efectuamos el disparo */
        enemybullets.push_back(
            bullet.create(
                enemies[j].getPosition().x,
                enemies[j].getPosition().y
            )
        );
    }
}

With these modifications your enemies will shoot at intervals of 2 seconds, although you could add a random component so that they do not fire all at the same time.

    
answered by 05.05.2017 / 11:10
source
1

You do not need to share hundreds of lines of code to ask this question:

How to make an event happen periodically?

So that an event (the firing of a projectile, consult the ports of a board, open a task in a thread, download data from an external resource ...) happens periodically you only need to know:

  • The time since the last event.
  • The periodicity of the event.

Proposal.

C ++ 11 modernized all time measurement operations with the header <chrono> , this header offers objects of type time_point which (as the name suggests) mark a moment in time.

So, you should do the following:

  • When launching the event, verify that subtracting the moment prior to the current moment gives a greater amount of time than the period.
  • If true, store the current moment as a previous moment.
  • Go back to point 1.
  • std::chrono::high_resolution_clock::time_point momento_previo = std::chrono::high_resolution_clock::now();
    double tiempo;
    std::cout << "Esperando 0.666 segundos...\n";
    
    do
    {
        tiempo = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - momento_previo).count() / 1000.;
        std::cout << "\than pasado " << tiempo << " segundos...\n";
    }
    while(tiempo <= 2. / 3.);
    
    std::cout << "Han pasado 2/3 de segundo\n";
    

    The above example waits two thirds of a second in a do - while loop before moving on, you can see the code working in Wandbox 三 へ (へ ਊ) へ ハ ッ ハ ッ 1 .

    SFML.

    Since you are using SFML , you have the alternative to use the class sf::Time , the equivalent to the previous code in SFML would be the following:

    sf::Clock reloj;
    double tiempo;
    std::cout << "Esperando 0.666 segundos...\n";
    
    do
    {
        tiempo = reloj.getElapsedTime().asSeconds();
        std::cout << "\than pasado " << tiempo << " segundos...\n";
    }
    while(tiempo <= 2. / 3.);
    
    std::cout << "Han pasado 2/3 de segundo\n";
    

    Applied to your case.

    Each of the characters capable of shooting with a certain cadence will need to have their own watch , which you should reset each time you shoot.

    1 To the code in Wandbox 三 へ (へ ਊ) へ ハ ッ ハ ッ I added a wait by putting the main thread to sleep, otherwise the system would throw me out to saturate the standard output.

        
    answered by 05.05.2017 в 09:50