I'm trying to learn C++, and I'm making this little space-invader-like game to get better at it. Currently everything works just fine but for some reason the performance of the game is kind of terrible considering how simple the graphics are (the game runs smoothly for a while and then stops for like half a second, constantly). I'm pretty sure the lag is due to the amount of bullets I'm removing and creating from the vector containing them (I'm removing the bullets that go outside the screen because there's no point in updating them or rendering them). So how could I fix this? I checked the game loop and the problem isn't there.
Code below
player.h:
#pragma once
#include <vector>
#include <iostream>
#include "entity.h"
#include "SFML/Graphics.hpp"
#include "vector.h"
class Player : public Entity {
private:
int f_lastshot = 0;
const double shoot_delay = 0.5;
const float bullet_speed = 1, speed = 0.75;
std::vector<Entity> bullets;
sf::Clock *c;
sf::Sprite bullet_sprite;
public:
Player(Vector &pos, int w, int h, sf::Sprite &spr);
~Player();
void init();
void update();
void render(Window &win);
void shoot();
};
player.cpp:
#include "player.h"
Player::Player(Vector &pos, int w, int h, sf::Sprite &spr) : Entity() {
this->pos = pos;
this->sprite = spr;
this->width = w;
this->height = h;
float scale_x = (float)w / (float)spr.getTexture()->getSize().x;
float scale_y = (float)h / (float)spr.getTexture()->getSize().y;
sprite.setScale(scale_x, scale_y);
}
void Player::update() {
if (sf::Mouse::isButtonPressed(sf::Mouse::Left) || sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
if ((double)(f_lastshot / 60) > shoot_delay) // shoot only if enough time has passed
shoot();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) dir.setX(-speed); // move left
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) dir.setX(speed); // move right
else dir.setX(0); // stop if no button is being pressed
f_lastshot++;
pos = pos + dir; // add the direction to the position
for (Entity &e : bullets) {
e.update(); // update each bullet
if (e.getPos().getY() < 0) // if the bullet is outside the screen delete it
bullets.erase(bullets.begin());
}
}
void Player::render(Window &win) {
sprite.setPosition(pos.getX(), pos.getY()); // update the position of the sprite
win.render(sprite);
for (Entity &bullet : bullets)
bullet.render(win);
}
void Player::shoot() {
f_lastshot = 0;
bullets.push_back(Entity::Entity(Vector::Vector(pos.getX() + width / 2 - 8, pos.getY()), 16, 32, bullet_sprite, Vector::Vector(0,-bullet_speed)));
}
void Player::init() {
if (c == nullptr) c = new sf::Clock();
else c->restart();
dir.setX(0); dir.setY(0);
sf::Texture *bullet_texture = new sf::Texture();
if (!bullet_texture->loadFromFile("bullet.png"))
std::cerr << "Could not load bullet image" << std::endl;
bullet_sprite.setTexture(*bullet_texture);
}
Player::~Player() {
delete bullet_sprite.getTexture();
delete c;
}
entity.h:
#pragma once
#include <SFML/Graphics.hpp>
#include <iostream>
#include "vector.h"
#include "window.h"
class Entity {
protected:
Vector pos;
sf::Sprite sprite;
int width, height;
Vector dir; // the direction
public:
Entity();
Entity(Vector &pos, int w, int h, sf::Sprite &spr);
Entity(Vector pos, int w, int h, sf::Sprite &spr);
Entity(Vector pos, int w, int h, sf::Sprite &spr, Vector dir);
void update();
void render(Window &win);
bool collides(Entity &other) const;
public:
Vector getPos() const;
Vector getDirection() const;
sf::Sprite getSprite() const;
int getWidth() const;
int getHeight() const;
void setPos(Vector &pos);
void setWidth(int width);
void setHeight(int height);
void setSize(int width, int height);
void setSprite(sf::Sprite &sprite);
void setDirection(Vector dir);
};
entity.cpp:
#include "entity.h"
Entity::Entity() {}
Entity::Entity(Vector &pos, int w, int h, sf::Sprite &spr)
: pos(pos), width(w), height(h) {
this->sprite = spr;
float scale_x = (float)w/(float)spr.getTexture()->getSize().x;
float scale_y = (float)h/(float)spr.getTexture()->getSize().y;
sprite.setPosition(pos.getX(), pos.getY());
sprite.setScale(scale_x, scale_y);
}
Entity::Entity(Vector pos, int w, int h, sf::Sprite &spr)
: pos(pos), width(w), height(h) {
this->sprite = spr;
float scale_x = (float)w / (float)spr.getTexture()->getSize().x;
float scale_y = (float)h / (float)spr.getTexture()->getSize().y;
sprite.setPosition(pos.getX(), pos.getY());
}
Entity::Entity(Vector pos, int w, int h, sf::Sprite &spr, Vector dir)
: pos(pos), width(w), height(h) {
this->sprite = spr;
this->dir = dir;
float scale_x = (float)w / (float)spr.getTexture()->getSize().x;
float scale_y = (float)h / (float)spr.getTexture()->getSize().y;
sprite.setPosition(pos.getX(), pos.getY());
}
bool Entity::collides(Entity &other) const {
bool x_coll = (pos.getX() + width> other.getPos().getX() && pos.getX()< other.getPos().getX())||(pos.getX() < other.getPos().getX() + other.getWidth() && pos.getX() > other.getPos().getX());
bool y_coll = (pos.getY() + height > other.getPos().getY() && pos.getY() < other.getPos().getY()||(pos.getY() < other.getPos().getY() + other.getHeight() && pos.getY() > other.getPos().getY()));
return (x_coll && y_coll);
}
void Entity::render(Window &win) {
sprite.setPosition(pos.getX(), pos.getY());
win.render(sprite);
}
void Entity::update() {
pos = pos + dir;
}
Vector Entity::getPos() const { return pos; }
int Entity::getWidth() const { return width; }
int Entity::getHeight() const { return height; }
sf::Sprite Entity::getSprite() const { return sprite; }
Vector Entity::getDirection() const { return dir; }
void Entity::setPos(Vector &pos) { this->pos = pos; }
void Entity::setWidth(int width) { this->width = width; }
void Entity::setHeight(int height) { this->height = height; }
void Entity::setSprite(sf::Sprite &sprite) { this->sprite = sprite; }
void Entity::setDirection(Vector dir) { this->dir = dir; }
void Entity::setSize(int width, int height) {
this->width = width;
this->height = height;
}
To identify bottlenecks and other performance issues you should use a profiler. Only by looking the code it's hard if not impossible to find the correct places that need optimisation. But one problem is indeed the way you remove bullets:
for (Entity &e : bullets) {
e.update(); // update each bullet
if (e.getPos().getY() < 0) // if the bullet is outside the screen delete it
bullets.erase(bullets.begin());
}
There is a logic error. You check if the current bullet e
is outside the screen, but then delete the first one anyway. This can't be right.
It's extremely inefficient to delete the first element of a std::vector
, because all the following elements will have to be moved one place left. This happens for every bullet you remove!
It may cause undefined behavior. The reference says that erase
Invalidates iterators and references at or after the point of the erase, including the end() iterator.
Changing the size of a vector while iterating over it is usually a bad idea.
To overcome those issues you could use the Erase-Remove-Idiom like this:
// Update as usual
for (Entity &e : bullets) {
e.update();
}
// Erase-Remove bullets that are out of screen
bullets.erase(std::remove_if(bullets.begin(), bullets.end(),
[](const Entity &e){ return e.getY() < 0;}), bullets.end());