我怎样才能修复程序的架构,以便我可以在其他 classes 中使用 Base class 的变量?

How can I fix the architecture of the program so that I can use the variables of the Base class in other classes?

我从一个人那里拿了一个游戏的设计,现在我遇到了问题。

下面是classBase的实现,其中定义了window的所有初始参数,并将其所有字段引入全局作用域。

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;

它包含在 class 游戏中,其中调用了它的构造函数。

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;
}

主游戏循环在 class Game 中,这个 class 必须为对象向量中包含的每个对象调用 Draw、Action、Update 方法。(对象向量在Base.h)

但是如果我创建任何新对象 class 并尝试在其中实现其 Draw、Action、Update 方法,那么我将无法在其中使用 Base.h 中的所有变量, 带到全局范围,没有它们我无法正确实现我的对象,我如何更改体系结构以使一切正常?

/// 例如,如果我希望我的对象的位置更改为光标的当前位置,那么我需要一个来自 Base.h 的变量,我将其称为 B::cur_p,但编译器只是简单地给出错误:“未知验证器”。

首先,如果你想从任何地方访问你的变量,你可以让它们成为真正的全局变量,把它们放在 header 中,等等。如果我没看错,这就解决了整个问题因为变量和方法是分开的。

全局变量的答案是没有全局变量(就像优化一样,只有当你知道它值得付出代价时才使用它们)。这不利于可维护性,也不利于测试! (并且在大型项目中对组件依赖和构建时间不利)如果你想了解架构,现在是阅读依赖注入的好时机。例如https://en.wikipedia.org/wiki/Dependency_injection

这是您游戏的代码示例:

#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;
}