#include <iostream>
#include <SFML/Graphics.hpp>
#include <vector>
#include "Player.hpp"
#include "Global.hpp"
#include "Enemy.hpp"
float randint(float min, float max) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(min, max);
return dis(gen);
}
int main() {
Player player;
player.setPos({ gbl::winWidth / 2.f, gbl::winHeight / 2.f });
std::vector<Enemy> enemies;
sf::RenderWindow window(sf::VideoMode(gbl::winWidth, gbl::winHeight), gbl::title);
for (int i = 0; i < 5; i++) {
Enemy newEnemy;
newEnemy.setPos({ randint(10 + newEnemy.getSize().x, gbl::winWidth - newEnemy.getSize().x), 100 });
enemies.push_back(newEnemy);
}
sf::Event ev;
while (window.isOpen()) {
while (window.pollEvent(ev)) {
switch (ev.type) {
case sf::Event::KeyPressed:
if (ev.key.code == sf::Keyboard::Escape) window.close();
break;
case sf::Event::Closed:
window.close();
break;
case sf::Event::KeyReleased:
if (ev.key.code == sf::Keyboard::R) {
player.reset();
for (auto& enemy : enemies) {
enemy.setPos({ randint(0, gbl::winWidth), 100 });
}
}
}
}
window.clear(gbl::bgColor);
player.draw(window);
for (Enemy& enemy : enemies) {
enemy.update();
if (enemy.getPos().y + enemy.getSize().y > gbl::winHeight + enemy.getSize().y) {
enemy.setOffscreen();
}
enemy.draw(window);
}
player.move();
window.display();
}
return 0;
}
Я попробовал вручную создать объект типа Enemy и вставить его в вектор. Когда я запускаю игру, все работает так, как задумано. Но когда я использую цикл for для заполнения вектора и запускаю игру, отображается только спрайт игрока. Я попробовал поместить цикл for внутри и снаружи основного цикла, но это не сработало. Что я должен делать?
Примечание. Случайный файл заголовка находится в одном из файлов заголовков.
#include "Enemy.hpp"
#include <random>
static inline float randint(float min, float max) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(min, max);
return dis(gen);
}
Enemy::Enemy() {
texture.loadFromFile("assets/images/Enemy.png");
sprite.setTexture(texture);
speed = 0.1f;
offscreen = false;
}
sf::Sprite Enemy::getSprite() const {
return sprite;
}
sf::Vector2f Enemy::getPos() const {
return sprite.getPosition();
}
sf::Texture Enemy::getTexture() const {
return texture;
}
float Enemy::getSpeed() const {
return speed;
}
bool Enemy::getOffscreen() const {
return offscreen;
}
sf::Vector2u Enemy::getSize() const {
return texture.getSize();
}
void Enemy::setPos(sf::Vector2f e_pos) {
sprite.setPosition(e_pos);
}
void Enemy::setOffscreen() {
offscreen = true;
}
void Enemy::draw(sf::RenderWindow& d_window){
d_window.draw(sprite);
}
void Enemy::update() {
if (offscreen == false)
sprite.move({ 0, speed });
else {
setPos({ randint(getSize().x, 800.f - getSize().x), 0 });
offscreen = false;
}
}
This is the code for the Enemy class
Совершенно не связано: randint
полностью перестраивает генератор случайных чисел каждый раз, когда он генерирует число. Это очень дорого. Вы хотите создать gen
один раз и продолжать использовать его повторно. Установите его в main
и раздавайте или сделайте static
, float randint(float min, float max) { static std::mt19937 gen(std::random_device{}()); std::uniform_int_distribution<> dis(min, max); return dis(gen); }
, чтобы он создавался только один раз.
не могли бы вы хотя бы показать нам содержимое класса Enemy
... ошибка, скорее всего, там...
Несвязано: std::for_each(enemies.begin(), enemies.end(), [&window](Enemy& enemy)
можно упростить до for(Enemy& enemy : enemies)
в вашем конструкторе Enemy
вы привязываете спрайт к текстуре
Enemy::Enemy() {
texture.loadFromFile("assets/images/Enemy.png");
sprite.setTexture(texture);
speed = 0.1f;
offscreen = false;
}
но когда вы вставляете его в вектор, копия объекта вставляется в вектор, а исходная текстура уничтожается, поэтому спрайт теперь не имеет текстуры. (и вам не следует/не следует реализовывать конструктор копирования/перемещения)
вам нужен sf::Texture
, чтобы не менять его местоположение в памяти, поэтому вместо этого вам нужно хранить указатель на него внутри Enemy
объекта, лучший вариант - использовать std::shared_ptr<sf::Texture>
ссылкуshared_ptr вместо хранения текстуры по значению, таким образом, даже если Enemy
скопированная/перемещенная текстура останется в том же месте в памяти.
PS: не забудьте инициализироватьshared_ptr с помощью std::make_shared, также вы можете инициализировать только 1 текстуру противника за пределами Enemy
и передать ее в конструктор Enemy
в качестве аргумента для экономии памяти.
// in main
auto enemyTexture = std::make_shared<sf::Texture>();
enemyTexture->loadFromFile("assets/images/Enemy.png");
for (int i = 0; i < 5; i++) {
Enemy newEnemy{enemyTexture};
...
}
// in Enemy
Enemy::Enemy(std::shared_ptr<sf::Texture> texture) {
texture_shared_ptr = std::move(texture);
sprite.setTexture(*texture_shared_ptr);
speed = 0.1f;
offscreen = false;
}
Здесь тоже подойдет необработанный указатель, но shared_ptr
избавит вас от необходимости думать о времени жизни объекта.
При выборе типа указателя для хранения в объекте.
shared_ptr
имеет небольшие накладные расходы на копирование и уничтожение (они происходят только один раз для каждого объекта), но вы никогда не столкнетесь с проблемами в течение всего срока службы, и это не мешает копированию вашего объекта.На данный момент вы можете смело использовать shared_ptr
, так как снижение производительности незначительно, но для большого проекта с тысячами объектов вы хотите использовать необработанные указатели и управлять временем жизни текстуры извне, поскольку эта небольшая экономия суммируется.
Спасибо большое, теперь все работает!
Рассматривали ли вы возможность добавить точку останова в цикл for_each и посмотреть, что произойдет и что будет вызвано? Кроме того, вам, вероятно, следует добавить к вопросу тег SFML.