3
\$\begingroup\$

My Game class does not satisfy me. I use some cheat-code to delete bonuses from their list, I think this is not a better way to do it. Any other requests will be greatly appreciated.

Latest version is on GitHub. It was inspired by Asteroids on RealPython.

//game.h
#pragma once
#include "const.h"
#include "SFML/Graphics.hpp"
#include "player.h"
#include "meteor.h"
#include <vector>
#include "splash.h"
#include <list>
#include "laser.h"
#include "text.h"
#include "bonus.h"
#include "explosion.h"

class Game {
public:
    enum GameState {INTRO, PLAY, PAUSE, GAME_OVER};
    Game();
    void play();
private:
    void check_events();
    void update();
    void draw();
    void check_collisions();

    sf::RenderWindow window;
    Player player;
    GameState game_state = PLAY;
    Splash game_over;
    std::vector<Meteor*> meteor_sprites;
    std::list<Laser*> laser_sprites;
    TextObj hp_text;
    sf::Clock clock;
    std::list<Bonus*> bonus_sprites;
    std::list<Explosion*> exp_sprites;
};
//game.cpp
#include "game.h"

Game::Game() :
    window( sf::VideoMode(
                static_cast<size_t> (WINDOW_WIDTH), 
                static_cast<size_t> (WINDOW_HEIGHT)
            ),
            WINDOW_TITLE,
            sf::Style::Titlebar | sf::Style::Close
    ),
    player(WINDOW_WIDTH / 2 - 112 / 2.f,
        WINDOW_HEIGHT - 75.f, "images/playerShip2_green.png"),
    hp_text(500, 5, 24, sf::Color::Yellow)
{
    window.setFramerateLimit(60);
    meteor_sprites.reserve(METEORS_QTY);
    for (size_t i = 0; i < METEORS_QTY; i++) {
        meteor_sprites.push_back(new Meteor());
    }

}
void Game::play() {
    while (window.isOpen()) {
        check_events();
        update();
        check_collisions();
        draw();
    }
}
void Game::check_events() {
    sf::Event event;
    while (window.pollEvent(event)) {
        if (event.type == sf::Event::Closed) window.close();
        else 
            if (event.type == sf::Event::MouseButtonPressed &&
                event.mouseButton.button == sf::Mouse::Left)
            {
                sf::Time elapsed = clock.getElapsedTime();
                if (elapsed.asMilliseconds() > 250) {
                    laser_sprites.push_back(new Laser(player.getPosition().x +
                        player.getWidth() / 2 - 5, player.getPosition().y));
                    clock.restart();
                }
            }
            
    }
}
void Game::update() {
    switch (game_state) {
    case PLAY:
        player.update();
        for (size_t i = 0; i < METEORS_QTY; i++) {
            meteor_sprites[i]->update();
        }
        for (auto it = laser_sprites.begin(); it != laser_sprites.end(); it++) {
            (*it)->update();
        }
        for (auto it = bonus_sprites.begin(); it != bonus_sprites.end(); it++) {
            (*it)->update();
        }
        for (auto it = exp_sprites.begin(); it != exp_sprites.end(); it++) {
            (*it)->update();
        }
        check_collisions();
        hp_text.update(std::to_string(static_cast<int>(player.getHp())));
        break;
    case GAME_OVER:
        break;
    }
}
void Game::draw() {

    window.clear();
    switch (game_state) {

    case PLAY:
        for (size_t i = 0; i < METEORS_QTY; i++) {
            meteor_sprites[i]->draw(window);
        }
        for (auto it = laser_sprites.begin(); it != laser_sprites.end(); it++) {
            (*it)->draw(window);
        }
        for (auto it = bonus_sprites.begin(); it != bonus_sprites.end(); it++) {
            (*it)->draw(window);
        }
        for (auto it = exp_sprites.begin(); it != exp_sprites.end(); it++) {
            (*it)->draw(window);
        }
        player.draw(window);
        hp_text.draw(window);
        break;
    case GAME_OVER:
        window.draw(game_over.getSprite());
    }
    window.display();
}
void Game::check_collisions() {
    
    for (size_t i = 0; i < METEORS_QTY; i++) {
        if (player.getHitBox().intersects(
            meteor_sprites[i]->getHitBox()))
        {
            player.reduceHp(meteor_sprites[i]->getWidth()/3);
            meteor_sprites[i]->spawn();
        }
    }
    
    for (auto it = bonus_sprites.begin(); it != bonus_sprites.end(); it++) {
        if (player.getHitBox().intersects((*it)->getHitBox())) {
            player.reduceHp(-50);
            (*it)->setDel(true);
        }
    }
    
    bonus_sprites.remove_if([](Bonus* bonus) {return bonus->getDel(); });
    
    if (player.isDead()) game_state = GAME_OVER;
    
    laser_sprites.remove_if([](Laser* laser) {return laser->getPosition().y < 0; });
    
    bonus_sprites.remove_if([](Bonus* bonus) {
        return bonus->getPosition().y > WINDOW_HEIGHT; });
    
    for (auto it = laser_sprites.begin(); it != laser_sprites.end(); it++) {
        for (size_t i = 0; i < METEORS_QTY; i++) {
            if ((*it)->getHitBox().intersects(meteor_sprites[i]->getHitBox()))
            {
                Explosion* new_explosion = 
                    new Explosion(meteor_sprites[i]->getCenter());
                exp_sprites.push_back(new_explosion);
                meteor_sprites[i]->spawn();
                
                size_t chance = rand() % 100;
                if (chance < 10) {
                    
                    Bonus* new_bonus = new Bonus(static_cast<Bonus::BonusType>(0),
                        meteor_sprites[i]->getPosition());
                    bonus_sprites.push_back(new_bonus);
                }
            }
        }
    }
    
    exp_sprites.remove_if([](Explosion* exp) {return exp->getDel(); });
}
\$\endgroup\$
1
  • \$\begingroup\$ @Richard Added game.h \$\endgroup\$
    – Paul Nick
    Commented Dec 13, 2022 at 8:16

2 Answers 2

4
\$\begingroup\$

Loop like a native

In modern C++, we have range-based for. We can replace

    for (auto it = laser_sprites.begin(); it != laser_sprites.end(); it++) {
        (*it)->draw(window);
    }

with a simpler and more readable equivalent:

    for (auto *sprite: laser_sprites) {
        sprite->draw(window);
    }

Ensure resources are released

I'm worried about the vast number of new that don't seem to have corresponding delete - have you run this under Valgrind to check for leaks?

I recommend replacing most of the raw pointers with smart pointers or references, so that it's clear where the ownership lies and so that the storage is reclaimed automatically.

\$\endgroup\$
5
  • \$\begingroup\$ I'm amateur, not a professional software engineer. Thanks, i will check my code with Valgrind. So, i have to use smart pointers, it's a good idea. \$\endgroup\$
    – Paul Nick
    Commented Dec 13, 2022 at 10:38
  • \$\begingroup\$ Instead of recommending smart pointers, here you should not use pointers at all. You can store objects directly in STL containers, for example std::list<Laser> laser_sprites. \$\endgroup\$
    – G. Sliepen
    Commented Dec 13, 2022 at 13:43
  • \$\begingroup\$ I admit I didn't look into the library to see if the types were suitable as container members (std::copyable etc). If they are, then what G. Sliepen says is correct, @Paul. (Then you'll use for (auto& sprite: etc.) \$\endgroup\$ Commented Dec 13, 2022 at 13:53
  • \$\begingroup\$ Sorry, I meant std::movable of course, rather than copyable. \$\endgroup\$ Commented Dec 14, 2022 at 8:17
  • \$\begingroup\$ @G.Sliepen i used to store objects. But i had problems with Texture. \$\endgroup\$
    – Paul Nick
    Commented Dec 15, 2022 at 16:23
2
\$\begingroup\$

Object pools

When using many short lived objects you can often gain an advantage by using a pool to hold objects. All objects will have a lifetime equal to the level that uses them.

The pool reduces the number of times you need to create / assign and delete / unassign objects.

It also reduces the complexity of object management, removing and adding to and from storage (eg vector). Inactive items have a \$O(1)\$ removal cost (as they bubble up) rather than the \$O(n)\$ cost of std::remove_if

Very simple example.

The following shows the most basic implementation of an object pool using the std::vector container.

  • Use a vector to hold pointers to objects.
  • Hold a count of active objects.
std::vector<MyObj* >objs;
std::size_t count{0};

Use a Spawn method to either create a new obj, or use an existing object.

MyObj* Spawn() {
    MyObj* o;
    if (count < objs.size()) {
        o = objs[count];  // use existing inactive object
    } else {
        o = new MyObj{};  // create a new object and add to pool
        object.push_back(o);
    }
    count++;
    return o;
}

The obj will need an Init function to set the state.

MyObj* o = Spawn();
o->Init(x, y, dx, dy); // set starting state   

Note you can also pre allocate objects when entering the level.

Bubble

In the update loop you iterate over active objects, when an object's state is inactive (could be Update returns false) you swap its pointer with the next active. In effect active objects bubble to the start of the vector inactive object towards the end.

This ensures that the first count objects are always active.

void Update() {
    std::size_t head = 0, tail = 0; 
    while (head < count) {
        MyObj* o = objs[head];
        if (o->Update()) {            // swap if active
            if (head != tail) {
                objs[head] = objs[tail];  // move inactive up
                objs[tail] = o;         // move active down
            }
            tail++;
        }
        head ++;
    }
    count = tail;  /* new count of active objects */
}

To iterate just use active count

void draw() {
    std::size_t head = 0; 
    while (head < count) { objs[head++]->Draw(); }
}

When you no longer need the pool you can delete all the objects then

for (MyObj* o: objs) { delete o; }
objs.clear();
objs.shrink_to_fit();

Custom container

As far as I know there is no STL container that serves this purpose. The best solution is to implement your own container with the above logic. This will allow you to create an even more optimised Memory/Obj management scheme and hide the pointers from code that does not own them.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.