#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;
}
I tried manually creating object of type Enemy and push them in the vector. When i run the game doing this everything works as intended. But when i use a for loop to fill the vector and i run the game only the player's sprite is rendered. I tried putting the for loop inside and outside the main loop but it didn't work. What should i do?
Note: the random header file is in one of the header files
#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
in your Enemy
constructor you bind the sprite to the texture
Enemy::Enemy() {
texture.loadFromFile("assets/images/Enemy.png");
sprite.setTexture(texture);
speed = 0.1f;
offscreen = false;
}
but when you insert it into the vector, a copy of the object is inserted into the vector, and the original texture is destroyed, so the sprite now has no texture. (and you don't/shouldn't implement a copy/move constructor)
you need the sf::Texture
to not change its location in memory, so you need to store a pointer to it inside Enemy
object instead, the best option is to use std::shared_ptr<sf::Texture>
shared_ptr link instead of storing the texture by value, this way even if Enemy
got copied/moved the texture will remain in the same location in memory.
class Enemy
{
public:
....
private:
std::shared_ptr<sf::Texture> texture_shared_ptr;
sf::Sprite sprite;
...
};
PS: don't forget to initialize the shared_ptr with std::make_shared, also you could initialize only 1 enemy texture outside of Enemy
and pass it into the constructor of Enemy
as an argument to save memory.
// 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;
}
A raw pointer will work here too, but shared_ptr
will save you from thinking about object lifetimes.
When considering what type of pointer to store in your object.
shared_ptr
has a small overhead on copy and destruction (they only happen once per object), but you will never run into lifetime issues, and it doesn't prevent your object from being copyable.You can safely use shared_ptr
for now as the performance hit is negligable, but for a large project with thousands of objects you want to use raw pointers and manage the texture's lifetime externally, as those small savings add up.