【问题标题】:How do I get non-global objects to interact from within a function?如何让非全局对象在函数内进行交互?
【发布时间】:2010-09-11 17:08:48
【问题描述】:

我正在尝试使用 C++ 创建一个 Breakout 克隆,因此有几个对象(如球、桨、powerupicon、块等)。我知道将它们放在全局范围内是不好的做法,因此它们在 main() 中初始化。问题在于需要从其他函数(如 redraw() 或 reset_game())中对这些对象进行处理。我能想到的唯一方法是将对象的引用传递给每个函数,但我不确定这是否是最好的方法。

基本上,在函数中处理这些对象的最佳方法是什么?通过引用传递它们?使它们成为全局但在名称空间内?还是完全不同的东西?

【问题讨论】:

    标签: c++ oop object


    【解决方案1】:

    一种简单(不一定灵活或强大)的方法是定义一个基本game_object 类,该类定义您与游戏对象的接口,并存储这些对象。你的对象继承自它。:

    class game_object
    {
    public:
        virtual ~game_object() {}
    
        virtual void update() = 0;
        virtual void draw() = 0;
    
        // get and set position
        virtual vector3 position() = 0;
        virtual void position(const vector3&) = 0;
    
        // etc...
    };
    
    class ball : public game_object
    {
    public:
        void update() { /* update the ball */ }
        void draw() { /* draw the ball */ }
    
        // etc
    };
    
    // etc
    

    现在您有了使用游戏对象的通用方式。接下来,您可以将它们存储在一个映射中(如果您有 Boost、TR1 或 C++0x,最好是 unordered_map),并使该映射全局可用:

    // game_object_manager.h
    typedef std::map<std::string, game_object*> game_object_manager;
    
    extern game_object_manager manager;
    

    您在某处的翻译单元(main.cppgame_object_manager.cpp)中定义它。最后,您可以通过按名称插入内容来使用它:

    // somewhere, add the ball
    manager.insert(std::make_pair("ball", new ball()));
    

    并使用它:

    game_object* b = manager["ball"];
    if (!b) /* there is no ball object */
    
    // use b as ball */
    

    同样,这是一个简单的解决方案,不一定可靠、最佳实践或灵活。但是对于第一场比赛,它会工作得很好。 (为了改进它,您需要研究异常安全、智能指针和其他东西(游戏书)。我建议您在尝试制作游戏之前这样做。)

    【讨论】:

    • 我喜欢你的解决方案(继承部分),但建议不要使用全局变量,而是将管理器作为对所有需要它的函数的引用传递 - 迟早你有 2 个玩家或服务器 - 需要多个管理器实例。
    • @David:就像我说过几次,这不是一个灵活的解决方案,而且在现代游戏引擎设计中相当糟糕。不过,我不同意你关于一位以上经理的论点。两名球员意味着你需要两名经理怎么办?这是游戏中的相同对象。
    • 据我所知,这似乎是最好的解决方案。我正在制作一个 Breakout 克隆,主要是作为 C++ 和 OOD 的练习,并且绝不期望与行业标准或任何东西相提并论。在我真正开始学习任何编程课程之前,我还有几年的时间:D
    【解决方案2】:

    引用cppreference:

    static 关键字有四种不同的使用方式:

    • 为函数中的局部变量创建永久存储,
    • 创建类数据的单个副本,
    • 声明与非成员函数类似的成员函数,并且
    • 指定内部链接。

    还有其他例子。

    void foo()
    {
        static int counter = 0;
        cout << "foo has been called " << ++counter << " times\n";
    }
    
    int main() 
    {
        for( int i = 0; i < 10; ++i )
            foo();
    }
    

    【讨论】:

      【解决方案3】:

      如果它是您担心的初始化顺序,您可以在全局范围内有一个指向它们的指针,可能在命名空间中,并在 main 中为它们分配内存。

      【讨论】:

        【解决方案4】:

        游戏编程模式中关于Singletons 的章节可能会对您有所帮助,特别是"What We Can Do Instead" 部分。

        【讨论】:

          【解决方案5】:

          我会创建一个包含所有这些信息的管理器对象。就像一个类Game 有一个成员paddlebricksitems 等。然后你可以让Game 有一个返回当前游戏实例的类方法currentGame。这有点像将它放在“全局范围”中,但以一种更 OOP 的方式。

          例如:

          class Game {
            public:
              Paddle *paddle;
              vector<Brick> *bricks;
              vector<Item> *items;
          
              static Game *currentGame();
              static void setCurrentGame(Game *newGame);
          
            private:
              static Game *currentGameInstance;
          }
          
          static Game *Game::currentGame() {
            return currentGameInstance;
          }
          
          static void Game::setCurrentGame(Game *newGame) {
            currentGameInstance = newGame;
          }
          

          (我对 C++ 很生疏,希望语法正确)

          这样你就可以在任何你喜欢的地方做Game::currentGame()-&gt;paddle

          【讨论】:

            【解决方案6】:

            “最佳”有点主观;您已经命名了标准方法(即:命名空间内的全局方法或有效等效方法,或传递给每个函数)。

            在设计方面,我倾向于通过逻辑分组将对象组合在一起(例如:“游戏状态”对象中的一些东西,“UI 资产”对象中的其他东西等),然后有效这些对象的全局实例。我建议始终为对象使用有效的命名空间:这可以是字面意思,也可以在单例或命名实例工厂范式之类的内部。通常,对象越“本地化”,我就越倾向于将它们显式传递给使用/操作它们的函数;对象的“应用级别”越多,我就越倾向于使它们成为有效的全局对象。

            希望对您有所帮助。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-04-30
              • 2021-09-27
              • 1970-01-01
              • 1970-01-01
              • 2016-04-23
              相关资源
              最近更新 更多