Странная стреляющая ошибка SFML

Я делаю игру на C++ и SFML. У меня странный баг, когда у меня на экране ровно 1 враг и 3 выстрела и пуля сталкивается с врагом, игра вылетает. Ошибка, которую я получаю:

Expression: vector subscript out of range

Вот как я проверяю столкновение между выстрелами и врагами:

for (int i = 0; i < enemies.size(); i++)
{
    for (int s = 0; s < shots.size(); s++) {
        if (Collision::PixelPerfectTest(enemies[i].getSprite(), shots[s].getSprite())) {
            enemies[i].setHealth(enemies[i].getHealth() - player.getDamage());
            if (enemies[i].getHealth() <= 0) {
                enemies.erase(enemies.begin() + i);
            }
            shots.erase(shots.begin() + s);
        }
    }
}

«враги» и «выстрелы» — это векторы, которые я вставляю каждые x секунд в основном цикле. Вот код для этого:

    if (enemySpawner.getElapsedTime().asSeconds() >= 1.5f) {
        enemies.push_back(Enemy(spriteManager.enemySprite));
        std::cout << enemies.size() << " enemies" << std::endl;
        enemySpawner.restart();
    }

    if (shotSpawner.getElapsedTime().asSeconds() >= 0.3f &&      sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
        shots.push_back(Shot(spriteManager.shotSprite, player.getPosition(), *window));
        std::cout << shots.size() << " shots" << std::endl;
        shotSpawner.restart();
    }

Как я уже сказал, это происходит только тогда, когда у меня есть 1 враг и 3 выстрела, в остальном вроде работает нормально.

РЕДАКТИРОВАТЬ: Теперь игра зависает при появлении первого врага. Вот обновленный код:

//Shot vs enemy
for (auto eit = enemies.begin(); eit != enemies.end();)
{
    for (auto sit = shots.begin(); sit != shots.end();) {
        if (Collision::PixelPerfectTest((*eit).getSprite(), (*sit).getSprite())) {
            (*eit).setHealth((*eit).getHealth() - player.getDamage());
            if ((*eit).getHealth() <= 0) {
                enemies.erase(eit);
                shots.erase(sit);
            }
            shots.erase(sit);
            eit = eit++;
            sit = sit++;
        }
    }
}

person Wahoozel    schedule 19.12.2014    source источник


Ответы (2)


Ответ @moka объясняет основную проблему с вашим кодом, и его/ее код с использованием итераторов решит вашу проблему. Однако есть и другой способ решить эту проблему: разделить код на «проход вычисления» и «проход модификации массива». Код будет выглядеть так:

for (int i = 0; i < enemies.size(); i++)
{
    for (int s = 0; s < shots.size(); s++) {
        if (!enemies[i].alive || !shots[s].active) {
            continue;
        }
        if (Collision::PixelPerfectTest(enemies[i].sprite(), shots[s].sprite())) {
            shots[s].active = false;
            enemies[i].setHealth(enemies[i].getHealth() - player.getDamage());
            if (enemies[i].getHealth() <= 0) {
                enemies[i].alive = false;
            }
        }
    }
}

enemies.erase(std::remove_if(enemies.begin(), enemies.end(),
    [](Enemy const &e) { return !e.alive; }), enemies.end());

shots.erase(std::remove_if(shots.begin(), shots.end(),
    [](Shot const &s) { return !s.active; }), shots.end());

Этот стиль хорош еще и тем, что немного больше отделяет игровую логику от реализации. Например, если вы хотите, чтобы выстрел наносил урон более чем одному врагу, вам все равно придется использовать такой подход для массива выстрелов.

Глава о двойной буферизации в превосходной книге Game Programming Patterns содержит более подробные сведения.

person japreiss    schedule 19.12.2014

вы не можете просто стереть материал из векторов, перебирая их так, как вы это делаете, потому что это даст значения i, которые больше не будут в диапазоне вектора (после того, как его размер уменьшился вызовом Erase), который в основном приведет к попытке индексировать значение массива, которого больше не существует. Если вам нужно такое поведение, рекомендуется использовать итераторы:

auto eit = enemies.begin();
for (; eit != enemies.end(); )
{
    if((*eit).isDead())
    {
         eit = enemies.erase(eit);
    }
    else
    {
         eit++;
    }
}

вы бы сделали то же самое для массива выстрелов.

person moka    schedule 19.12.2014
comment
Спасибо, но теперь у меня другая проблема. Смотрите мой отредактированный пост - person Wahoozel; 19.12.2014