【问题标题】:C++ Game State SystemC++ 游戏状态系统
【发布时间】:2011-03-15 10:48:12
【问题描述】:

好的:总的来说,我对 C++ 和静态语言还很陌生。来自多年的 ruby​​(和其他动态语言),我不知道这是否可能。

我一直在为……以及一款游戏制作游戏状态系统。我想让系统在没有任何(或很少)更改的情况下轻松剪切和粘贴到其他游戏中。

我想要改进的两件事是状态切换的方式和状态指针的保存方式。

可能有任意数量的状态,但在内存中总会有至少 2 到 3 个处于活动状态的状态。

丑陋一号。

目前我有一个状态管理器类,里面有这样的东西:

void StateManager::changeState(StateID nextStateID)
{
    // UNFOCUS THE CURRENT STATE //
    if (currentState())
    {
        currentState()->onUnFocus();

        // DESTROY THE STATE IF IT WANTS IT //
        if(currentState()->isDestroyedOnUnFocus()) {
            destroyCurrentState();
        }
    }

    if (m_GameStates[nextStateID]) {
        // SWITCH TO NEXT STATE //
        setCurrentState(nextStateID);
    }
    else
    {
        // CREATE NEW STATE //
        switch (nextStateID)
        {
        case MainMenuStateID:
            m_GameStates[MainMenuStateID] = new MainMenuState;
            break;
        case GameStateID:
                        m_GameStates[MainMenuStateID] = new GameStates;
            break;
        };
        setCurrentState(nextStateID);
    }

    // FOCUS NEXT STATE //
    currentState()->onFocus();
}

这种方法可行,但我觉得不是很好。

是否可以传递类型?然后调用 new 就可以了?

new NextGameState;  // Whatever type that may be.

多态性在这里有帮助吗?所有状态都来自class State

丑陋之二。

我认为需要改进的另一件事是我存储状态的方式。

State* m_GameStates[MaxNumberOfStates];

所有状态都被初始化为 NULL,所以我可以测试一个状态是否存在,如果没有,它会在需要时创建一个。

效果很好,我可以调用当前状态:

m_GameStates[m_CurrentState];

但是,我不喜欢这个有两个原因。当任何时候只有 2 或 3 个指针处于活动状态时,拥有一个充满 NULL 指针的数组似乎有点浪费。 [编者注:第二个原因是什么?]

我曾考虑将其转换为vector_ptr,但没有这样做,因为它会在检查状态是否存在时产生额外的复杂性。而且这个向量似乎强化了第一号丑陋。因为我需要一个列表来检查每个状态。

任何建议或指导表示赞赏。

谢谢, 菲尔。

【问题讨论】:

  • 为什么会有2-3个状态同时活跃?
  • 对于您的第一个问题,您能否显示包含此开关的函数的签名?为了正确回答这个问题,我们需要知道函数长什么样,变量是什么类型。
  • @Georg,一些状态将保持活动状态以允许快速切换、主菜单/暂停菜单等。 @jalf pastie.org/1042747 全开关功能。
  • 好的,我将完整功能编辑到您的问题中。 :)
  • StateIDtypedef 还是 int 或者它是如何定义的?

标签: c++ state


【解决方案1】:

使用 enum(eration) 来定义所有可能的状态(类似于带有常量的列表)。 只需为一个对象创建 一个 变量来保存状态并在需要更改时更改该变量。

【讨论】:

  • 我试图避免定义所有可能状态的列表。好吧,我正在尝试找出是否有更好的方法。
【解决方案2】:

一说到States,我就想到了State pattern

基本上,您可以从 State 基类派生一堆对象。与状态相关的所有操作都针对状态管理器维护的当前状态发生。状态将通过管理器从一个状态移动到另一个状态。

例如,您可以有一个 Paused 和 Unpaused 状态,每个状态都有一个 buttonPressed 事件。当您按下按钮时,当前状态将传递事件。如果它处于暂停状态,并且该按钮是暂停按钮,请移至未暂停。 Unpaused 反之亦然。

【讨论】:

    【解决方案3】:
    void StateManager::changeState(StateID nextStateID)
    {
         leaveState(actualState); 
         enterState(nextStateID);
    }
    

    我真的很喜欢这个 - 尽可能简单。 ;-)

    我想告诉你的——我认为在 changeState 函数中创建/删除你的统计数据太有逻辑了——它只是应该改变状态,对吧?

    编辑: 来回答你的 2 个问题——我不认为使用这个数组真的很浪费——你说的是 3 个字段,而不是 300 个左右。所以如果你喜欢使用数组 - 去吧。如果你不这样做,地图将是我的选择,如果你想检查是否创建了一个状态并且你不限于幻数“maxStates”,它会让事情变得容易。您可以检查是否有足够的内存,然后创建 X 状态,而不是固定 2-3。

    【讨论】:

      【解决方案4】:

      要生成状态,您需要一个工厂。这样,状态 id 就可以保持通用。为了存储状态,我会使用 std::map

      【讨论】:

        【解决方案5】:

        对于您的第一个问题,是的,您可以传入一个类型,但有一些注意事项。

        我在您的问题下添加了一条评论,要求提供更多信息。在我们明白之前,我真的不能说应该怎么做,但是请阅读模板。

        你可以制作一个函数模板,可以传入一个类型,比如这样:

        template <typename T>
        void Foo() {
          T* x = new T();
          ...
        }
        
        Foo<int>() // call Foo with the type T set to 'int'
        

        这有一些限制,因为类型必须在编译时指定,但它是一个非常强大的语言特性。

        另一个选项可能效果更好,因为您似乎在变量 (MainState) 和类型 (MainMenu) 之间存在关联,可能是使用特征类。同样,我不确定在您的情况下究竟是如何完成的,因为我们还没有看到函数的全部内容(特别是 MainState 是什么类型,以及它是如何/何时创建的?)

        也可以通过多态性来解决问题,但同样,我需要查看更多上下文来提出解决方案。

        第二个问题,可以使用标准库map

        #include <map>
        
        // I'm not sure what type m_CurrentState is, so use its type instead of KeyType below
        std::map<KeyType, State*> m_GameStates;
        
        // and to perform a lookup in the map:
        GameStates[m_CurrentState];
        

        最后,一个非常重要的建议:

        停止到处使用指针。停止调用new 来创建新对象。 作为一般规则,应在堆栈上创建对象(而不是 Foo* f = new Foo;,只需执行 Foo f;

        您通常不想使用指针,而是只复制对象本身。或者,创建引用而不是指针。

        当你确实需要使用动态内存分配时,你仍然不应该直接使用new。相反,创建一个包装对象,在内部在其构造函数中使用new 分配它需要的内容,并在析构函数中再次释放它。

        如果你做的正确,它几乎可以解决内存管理的所有问题。

        通用技术称为RAII

        【讨论】:

        • 感谢指针的建议,这一切看起来真的很酷,谢谢。
        • 我个人认为模板对于这个来说有点矫枉过正。它使非常容易和简单的思考变得困难(更困难)。
        • @InsertNickHere:也许吧。我提到它是因为@PhilCK 询问“是否可以传递类型?”,这几乎是模板的#1 用例。你是对的,在这种特定情况下它可能不是最好的解决方案,但我认为至少值得一提这个选项。 :)
        • 我还要补充一点,您的地图查找会插入一个默认项目,如果未找到您需要使用 find 来检查是否存在。
        【解决方案6】:

        【讨论】:

          猜你喜欢
          • 2016-04-30
          • 2020-07-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-17
          • 1970-01-01
          相关资源
          最近更新 更多