4
\$\begingroup\$

I have worked on a SFML Game/Game-Engine Project for 2 months now and am looking for some feedback in terms of...

  • Is this a valid approach for a Game class?
  • Are there any glaring C++ coding practice mistakes?

The following code is from my Game class which is the main organizational unit of the Game/Engine. I use a Scene(State) approach, meaning that every Scene/Screen in the Game is a closed of unit with it's Entities being updated from the Game class.

Game.h

#ifndef GAME_H
#define GAME_H

#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
#include <string>
#include <vector>
#include <map>

#include "../game/Entity.h"
#include "../game/FpsCounter.h"
#include "../game/EScene.h"
#include "../game/Scene.h"
#include "../game/InputHandler.h"
#include "../game/WindowStyle.h"
#include "../menu/VBox.h"
#include "../menu/HBox.h"
#include "../menu/ButtonMenu.h"
#include "../animation/TranslateAnimation.h"

namespace ProjectSpace
{
/**
 * @brief      Core class of the Game. Contains main Game-Loop and Event-Loop.
 */
class Game
{
public:

    /**
     * @brief      Constructs a Game detecting the monitor's resolution automatically.
     *
     * @param[in]  windowTitle  Title of the Game's window.
     */
    Game(std::string windowTitle, WindowStyle style = DEFAULT);

    /**
     * @brief      Constructs a Game.
     *
     * @param[in]  screenWidth   Width of the Game's window.
     * @param[in]  screenHeight  Height of the Game's window.
     * @param[in]  windowTitle   Title of the Game's window.
     */
    Game(unsigned int screenWidth, unsigned int screenHeight, std::string windowTitle, WindowStyle style = DEFAULT);

    /**
     * @brief      Destroys every Scene of the Game.
     */
    ~Game();

    /**
     * @brief      Starts the Game/Game-Loop.
     */
    void start();

    /**
     * @brief      Sets the current scene.
     *
     * @param[in]  scene  The scene
     */
    void setCurrentScene(EScene scene);

private:
    /**
     * @brief      Helper function for initializing everything before starting the Game. Creates all Sprites and stuff...
     */
    void init();

    sf::RenderWindow window;         // Window of the Game.
    FpsCounter fpsCounter;           // Fps Counter of the Game.
    InputHandler inputHandler;       // InputHandler for input Actions that are always available in the game (Not Scene dependant).
    VBox menu;
    std::map<EScene, Scene*> scenes; // All Scenes of the Game.
    Scene* currentScene;             // Scene that is currently active (is drawn and updated).

    TranslateAnimation* menuForward;
    TranslateAnimation* menuBackward;
    ButtonMenu* buttonMenu;

    float windowWidth;
    float windowHeight;
    };
}

#endif

Game.cpp

#include "Game.h"

#include <SFML/System/Clock.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/Window/VideoMode.hpp>

#include "../menu/TextBox.h"
#include "../animation/FadeAnimation.h"
#include "../menu/Button.h"
#include "../menu/ButtonMenu.h"
#include "InputHandler.h"
#include "Scene.h"
#include "Factory.h" 

namespace ProjectSpace
{
Game::Game(std::string windowTitle, WindowStyle style)
: window{sf::VideoMode{sf::VideoMode::getDesktopMode().width, sf::VideoMode::getDesktopMode().height}, windowTitle, style},
fpsCounter{"rsrc/fonts/arial.ttf"}, currentScene{nullptr}, windowWidth{(float)sf::VideoMode::getDesktopMode().width},
windowHeight{(float)sf::VideoMode::getDesktopMode().height}
{
    window.setFramerateLimit(60);

    init();
}

Game::Game(unsigned int screenWidth, unsigned int screenHeight, std::string windowTitle, WindowStyle style) 
: window{sf::VideoMode{screenWidth, screenHeight}, windowTitle, style}, fpsCounter{"rsrc/fonts/arial.ttf"},
currentScene{nullptr}
{
    // Makes sure that not more than 60 Frames per Second are calculated. Save Ressources.
    window.setFramerateLimit(60);

    init();
}

Game::~Game()
{
    for (auto const& p : scenes)
    {
        delete p.second;
    }
}

void Game::start()
{
    sf::Clock clock{};

    // This is main Game-Loop
    while (window.isOpen())
    {
        // At the start of each iteration "clock.restart()" will return the time that the last iteration took.
        // This is so that all objects of the current iteration know how much time has passed since the last frame.
        sf::Time time = clock.restart();
        float frameTime = time.asSeconds();

        // std::cout << "frameTime: " << frameTime << std::endl;

        // This is the Event-Loop (Hier muessen wir nochmal gucken, was wir damit anstellen koennen. Ist wohl ziemlich wichtig in SFML.)
        sf::Event event;
        while (window.pollEvent(event))
        {
            // For example by pressing the Close-Button on a Window, a Closed Event is created.
            if (event.type == sf::Event::Closed)
            {
                window.close();
            }
        }

        currentScene->update(time);

        inputHandler.update(time);
        fpsCounter.update(time);
        menu.update(time);
        menuForward->update(time);
        menuBackward->update(time);
        buttonMenu->update(time);

        // Default way of drawing stuff in SFML. Clear the window, draw you'r stuff and then display it.
        window.clear();

        window.draw(*currentScene);
        window.draw(fpsCounter);
        window.draw(menu);

        window.display();
    }
}

void Game::setCurrentScene(EScene scene)
{
    if (scenes.count(scene) == 0)
    {
        std::cout << "@Game::setCurrentScene(): Given scene is not known to Game." << std::endl;
        return;
    }

    currentScene = scenes[scene];
}

void Game::init()
{
    // Creating Levels
    scenes[EScene::DEBUG] = Factory::CREATE_DEBUG_SCENE(window);
    scenes[EScene::LEVEL_ONE] = Factory::CREATE_DEBUG_SCENE_2(window);
    currentScene = scenes[EScene::DEBUG];

    // Creating Menu
    Button* btn = new Button{[this]()
    {
        window.close();
    }, window, "EXIT"};

    Button* btn2 = new Button{[this]()
    {
        window.close();
    }, window, "EXIT"};

    Button* btn3 = new Button{[this]()
    {
        setCurrentScene(EScene::DEBUG);
    }, window, "Debug Level"};

    Button* btn4 = new Button{[this]()
    {
        setCurrentScene(EScene::LEVEL_ONE);
    }, window, "Level 1"};

    menu.addMenuElements({btn, btn2, btn3, btn4});
    menu.setPosition(-10, 50);
    menu.setSpacing(5);

    buttonMenu = new ButtonMenu{{btn, btn2, btn3, btn4}, &inputHandler};

    menuForward = new TranslateAnimation{&menu, sf::Vector2f{-250, 50}, sf::Vector2f{-10, 50}, 0.5f};
    menuBackward = new TranslateAnimation{&menu, sf::Vector2f{-10, 50}, sf::Vector2f{-250, 50}, 0.5f};

    // Setting up input handler
    inputHandler.add([this]()
    {
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
        {
            window.close();
        }

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::E))
        {
            menuBackward->stop();
            menuForward->start();
        }

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::R))
        {
            menuForward->stop();
            menuBackward->start();
        }
    });
}
}

GitHub Repo: https://github.com/Dante3085/ProjectSpace

\$\endgroup\$
  • \$\begingroup\$ Why did you check in all the binaries? \$\endgroup\$ – yuri Apr 28 at 7:34
  • \$\begingroup\$ @yuri I put SFML and mingw in there because I work on multiple systems and with 2 other guys, so that they just have to clone the repo for it to compile and run on windows. As for any other weird looking things in the repository, it's inexperience on my part. \$\endgroup\$ – Sparda Dante3085 Apr 28 at 9:13
  • \$\begingroup\$ What about the build artifacts? Do they need to be checked in? \$\endgroup\$ – yuri Apr 28 at 10:05
  • \$\begingroup\$ @yuri Definetly not. You're certainly right. I've put *.o in the gitignore a few days ago, so object files don't get commited anymore, but I am not shure how to erase them from origin-repo on github. I've made that mistake long before I had a gitignore. \$\endgroup\$ – Sparda Dante3085 Apr 28 at 11:24
  • \$\begingroup\$ You can remove them with git rm and then commit that. Ideally you would make a new repo that never includes any artifacts or binaries and instead distribute those in some other form. \$\endgroup\$ – yuri Apr 28 at 11:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.