Home > Enterprise >  How can I fix the architecture of the program so that I can use the variables of the Base class in o
How can I fix the architecture of the program so that I can use the variables of the Base class in o

Time:09-29

I took the design of a game from one person and now I have a problem.

Below there is an implementation of the class Base, in which all the initial parameters of the window are defined, and all its fields are brought into the global scope.

Base.h :

#pragma once
#include "Define.h"
#include "Audio.h"
#include "Font.h"
#include "Texture.h"
#include "GameObject.h"


class Base {

public:

    static sf::RenderWindow wnd;
    static Texture texture;
    static Font font;
    static Audio audio;
    static sf::Event event;
    static int scr_w;
    static int scr_h;
    static v2f cur_p;
    static v2f cur_p_wnd;
    static vector<unique_ptr<GameObject>> objects;
    static float time;

    static void SystemUpdate() {
        time = float(clock.getElapsedTime().asMicroseconds()) / 1000.f, clock.restart();
        cur_p = wnd.mapPixelToCoords(sf::Mouse::getPosition(wnd));
        cur_p_wnd = v2f(sf::Mouse::getPosition(wnd));
    }

    static void CloseEvent() {
        if (event.type == sf::Event::Closed || (event.type == sf::Event::KeyPressed && event.key.code == Keyboard::Escape)) wnd.close();
    }

    static bool IsKeyPressed(const sf::Keyboard::Key& code) {
        if (event.type == sf::Event::KeyPressed)
            if (event.key.code == code) return true;
        return false;
    }

    static bool IsKeyReleased(const sf::Keyboard::Key& code) {
        if (event.type == sf::Event::KeyReleased)
            if (event.key.code == code) return true;
        return false;
    }

    template <class T>
    static void new_object(T* obj) {
        B::objects.push_back(unique_ptr<GameObject>(obj));
    }

    
    template <class T>
    static void delete_object(T* obj) {
        for (auto itr = B::objects.begin(); itr != B::objects.end();   itr) {
            if ((*itr)->ID == obj->ID) {
                B::objects.erase(itr);
                return;
            }
        }
    }

    static bool IsMouseInRect(Shape& s) {
        return s.getGlobalBounds().contains(B::wnd.mapPixelToCoords(sf::Mouse::getPosition(B::wnd)));
    }


    Base(string init) {
        if (init == "init") {
            
            scr_w = sf::VideoMode::getDesktopMode().width;
            scr_h = sf::VideoMode::getDesktopMode().height;
            font = Font();
            texture = Texture();
            audio = Audio();
            wnd.create(sf::VideoMode(scr_w, scr_h), "Sea Battle", sf::Style::Close, sf::ContextSettings(0, 0, 8));
            cur_p = v2f(0, 0);
            cur_p_wnd = v2f(0, 0);
            wnd.setMouseCursorVisible(true);
            wnd.setFramerateLimit(30);
            srand(::time(0));
        }
    }
    
    Base(void) {}
        
private:
    static sf::Clock clock;
};


sf::RenderWindow                  B::wnd;
Texture                           B::texture;
Font                              B::font;
Audio                             B::audio;
sf::Event                         B::event;
int                               B::scr_w;
int                               B::scr_h;
v2f                               B::cur_p;
v2f                               B::cur_p_wnd;
float                             B::time;
sf::Clock                         B::clock;
vector<unique_ptr<GameObject>>    B::objects;

It is included to the class Game, in which its constructor is called.

Game.cpp :

#include "Game.h"

Game::Game() {

    B("init");
    
    game_state = GameState::Planning;

    GameObject* background = new GameObject(v2f(5000, 5000));
    background->getRect().setFillColor(Color(255, 255, 255));
    new_object(background);

    new_object(new Field());
}

void Game::Update() {
    switch (game_state) {
        case GameState::MainMenu:

            break;
        case GameState::Planning:

            for (auto itr = B::objects.begin(); itr != B::objects.end();   itr) {
                (*itr)->Update();
            }
            
            break;
        case GameState::Settings:
            //
            break;
        case GameState::Game:
            //
            break;
        default: break;
    }
    UpdateUI();
}

void Game::Action() {
    switch (game_state) {
    case GameState::MainMenu:

        break;
    case GameState::Planning:

        for (auto itr = B::objects.begin(); itr != B::objects.end();   itr) {
            (*itr)->Action();
        }
        
        break;
    case GameState::Settings:
        //
        break;
    case GameState::Game:
        //
        break;
    default: break;
    }
    ActionUI();
}

void Game::Draw() {
    wnd.clear();

    switch (game_state) {
    case GameState::MainMenu:

        break;
    case GameState::Planning:

        for (auto itr = B::objects.begin(); itr != B::objects.end();   itr) {
            (*itr)->Draw(wnd);
        }
        
        break;
    case GameState::Settings:
        //
        break;
    case GameState::Game:
        //
        break;
    default: break;
    }


    DrawUI();
    wnd.display();
}

void Game::UpdateUI() {}
void Game::ActionUI() {}
void Game::DrawUI() {}


void Game::Play() {
    while (wnd.isOpen()) {
        SystemUpdate();
        Update();
        while (wnd.pollEvent(event)) {
            CloseEvent();
            Action();
        }
        Draw();
    }
}

Game::~Game(){}

int main() {
    Game game;
    game.Play();
    return 0;
}

The main game loop is in the class Game, and this class must call the Draw, Action, Update methods for each object contained in the object vector.(objects vector is in the Base.h)

But if I create any new object class and try to implement its Draw, Action, Update methods in it, then I will not be able to use in them all the variables in Base.h, brought to the global scope, and without them I cannot properly implement my objects, how can I change the architecture so that everything works?

/// For example, if I want the position of my object to change to the current position of the cursor, then I need a variable from Base.h, which I will refer to as B::cur_p, but the compiler simply gives an error: "unknown authenticator".

CodePudding user response:

The answer to globals is NO globals (just like optimization, only use them if you know its worth the cost). It is bad for maintainability and bad for testing! (and in large projects bad for component dependency and bad for build times) If you want to learn about architecture, now is a good time te read up on dependency injection. e.g. https://en.wikipedia.org/wiki/Dependency_injection

Here is an example in code for your game :

#include <vector>

//-----------------------------------------------------------------------------
// just some structs to be able to use names from your Game to recognize.
struct RenderWindow {};
struct Texture {};
struct Font {};
struct Audio {};

struct GameObject 
{
    void update() {};
};
// etc

//-----------------------------------------------------------------------------
// global_data_itf.h

// model of a screen, those values belong together
struct screen_size_t
{
    unsigned int width{ 3440 };
    unsigned int height{ 1440 };
};

// Now define an interface for accessing your data.
// This also allows you
// to create mocks (e.g. with different screen sizes) for testing
// not something you can do with static or real global variables
//
// Note you should really make functions const and return const&
// if data is readonly!
//
class global_data_itf
{
public:
    virtual RenderWindow& render_window() = 0;
    virtual Texture& texture() = 0;
    virtual Font& font() = 0;
    virtual Audio& audio() = 0;
    virtual const screen_size_t& screen_size() = 0;

    // unique_ptr probably not needed. Give GameObject a move constructor
    // and emplace gameobjects
    virtual std::vector<GameObject>& objects() = 0;

    virtual float& time() = 0;

    virtual ~global_data_itf() = default;
protected:
    global_data_itf() = default;
};

//-----------------------------------------------------------------------------
// global_data.h

// #include "global_data_itf.h"

// For the game you need a default implementation of the global_data_itf
// this is it.
class global_data final :
    public global_data_itf
{
    // todo constructor with proper initialization

    virtual RenderWindow& render_window() override
    {
        return m_render_window;
    }

    virtual Texture& texture() override
    {
        return m_texture;
    }

    virtual Font& font() override
    {
        return m_font;
    }

    virtual Audio& audio() override
    {
        return m_audio;
    }

    virtual const screen_size_t& screen_size() override
    {
        return m_screen_size;
    }

    virtual std::vector<GameObject>& objects() override
    {
        return m_objects;
    }

    virtual float& time() override
    {
        return m_time;
    }

private:
    RenderWindow m_render_window;
    Texture m_texture;
    Font m_font;
    Audio m_audio;
    screen_size_t m_screen_size;
    std::vector<GameObject> m_objects;
    float m_time;
};

//-----------------------------------------------------------------------------
// Game.h

// #include "global_data_itf.h"

class Game
{
public:

    explicit Game(global_data_itf& data);
     void Update();

private:
    global_data_itf& m_data;
};

//-----------------------------------------------------------------------------
// Game.cpp

// include "game.h"

Game::Game(global_data_itf& data) :
    m_data{ data }
{
}

void Game::Update()
{
    for (auto& object : m_data.objects())
    {
        object.update();
    }
}

//-----------------------------------------------------------------------------
// main.cpp

// #include "global_data.h"
// #include "game.h"

int main()
{
    global_data data;
    Game game(data);    // inject the dependency on data into game.

    // if you have other classes needing access to global data
    // you can inject global_data into those classes as well.

    return 0;
}

CodePudding user response:

Well at first, if you want to access your variables from wherever, you can make them real global variables, put them in a header, etc. If I got you right, this fixes the whole problem as variables and methods go separately.

  • Related